35
Баннерокрутилка: как это было на Erlang

Баннерокрутилка на Erlang

Embed Size (px)

Citation preview

Page 1: Баннерокрутилка на Erlang

Баннерокрутилка:как это было на Erlang

Page 2: Баннерокрутилка на Erlang

Задача

● Выдача ссылок на новости● База из тысяч новостей ● Ссылки выбираются произвольно● Распределение – неравномерное

● Форматирование ссылок перед выдачей● Ссылки должны иметь формат блока● Дополнительные поля: идентификатор блока, …

Page 3: Баннерокрутилка на Erlang

Ta-da!

● Требования: >= 5000 запросов в секунду● Не справляемся – должны быстро выдавать

пустую страницу, чтобы не «ломать» вёрстку● Никакого кэширования

● Срок выполнения: полтора месяца.● При этом у разработчика баннерокрутилки

есть и другие задачи

Page 4: Баннерокрутилка на Erlang

Структура решения

● Структуры в памяти, хранящие новостии статистику показов

● Множество потоков, обрабатывающихHTTP-запросы

● Контроллер● добавляющий и удаляющий новости● собирающий статистику● на основе TCP-сокетов

Page 5: Баннерокрутилка на Erlang

Структура решения

Множество потоков

Page 6: Баннерокрутилка на Erlang

Erlang

● DSL для многопоточных приложений● Встроенные в язык примитивы для посылки

и приёма сообщений● Встроенные в язык шаблоны поведения● Встроенная в язык in-memory БД

Page 7: Баннерокрутилка на Erlang

Структура решения

● Структуры в памяти, хранящие новости и статистику показов: mnesia/ets/dict!

● Множество потоков, обрабатывающих HTTP-запросы: gen_server!

● Контроллер: gen_tcp!

Page 8: Баннерокрутилка на Erlang

Структура решения

● Структуры в памяти, хранящие новости и статистику показов: mnesia/ets/dict!

● Множество потоков, обрабатывающих HTTP-запросы: gen_server!● который уже написан за нас! mochiweb/misultin

● Контроллер: gen_tcp!

Batteries included

Page 9: Баннерокрутилка на Erlang

Batteries included, though not all

Page 10: Баннерокрутилка на Erlang

Основная часть решения

● HTTP-сервер Mochiweb получает запрос, создаёт поток

● Поток забирает из Mnesia данные● Поток производит выборку, сортирует

данные, форматирует строки, выдаёт, умирает.Все счастливы.

«In theory, there's no difference betweentheory and practice. In practice, there is».

L. A. van der Snepscheut.

Page 11: Баннерокрутилка на Erlang

Факап #1

У каждой новости есть коэффициент важности. В соответствии с этим коэффициентом необходимо выдавать новость чаще или реже остальных.

● Перед выдачей нужно назначать взвешенные произвольные числа каждой новости и делать по ним выборку.

● Новостей много.

Page 12: Баннерокрутилка на Erlang

Факап #1

● Mnesia построена на основе ETS● http://www.erlang.org/doc/man/ets.html:

«In the current implementation, every object insert and look-up operation results in a copy of the object.»

● Т. е. в копировании сотен новостей из потока в поток. Slow as hell.

Page 13: Баннерокрутилка на Erlang

Эврика!

● Вместо ETS напишем собственную структуру данных на основе gen_server, dict, queue, blackjack и hookers.

● Повесим её в виде отдельного потока● Будем делать там грубую предвыборку

новостей, которые потом быстро скопируются в рабочий поток

Page 14: Баннерокрутилка на Erlang

● Результат:

рост производительности в 3 раза

● Вывод:● всегда думай, какие объёмы данных копируешь!● профилируй!

Page 15: Баннерокрутилка на Erlang

Основная часть решения v0.2

● HTTP-сервер Mochiweb получает запрос, создаёт поток

● Поток отправляет запрос в gen_server● gen_server производит предвыборку

новостей и присылает результат● Поток производит выборку, сортирует

данные, форматирует строки, выдаёт, умирает.Все счастливы.

Page 16: Баннерокрутилка на Erlang

Факап #2

Новости – это текст.

Текст – это строки.

● Строки в Erlang – это связные списки символов

● IO в Erlang – это очень медленно

Page 17: Баннерокрутилка на Erlang

Эврика!

● Если вы пишете на Erlang,то строка символов записывается так:

"Hello world!~n"

● Конкатенация записывается так:

"Hello " ++ Username ++ "!~n"

Page 18: Баннерокрутилка на Erlang

Эврика!

● Если вы пишете на Erlang веб-приложения,то строка символов записывается так:

<<"Hello world!~n">>

● Конкатенация записывается так:

[<<"Hello ">>, Username, <<"!~n">>]

Page 19: Баннерокрутилка на Erlang

Почему:

● <<>> – встроенный бинарный тип● <<"">> – бинарная строка● Списки символов нужно обрабатывать

перед выдачей● Вывод бинарных данных – это просто

вызов writev(2)● Blazingly Fast

Page 20: Баннерокрутилка на Erlang

Почему:

● "Hello" ++ "!\n" => "Hello!\n" => строка● Конкатенация списков – O(n)● Вывод строки => цикл по списку из 7 символов

● [<<"Hello">>, <<"!\n">>] – тип iolist().● Добавление в начало списка – O(1)● Вывод => цикл по списку из 2 элементов● Встроенным функциям I/O всё равно, что

выводить

Page 21: Баннерокрутилка на Erlang

Кроме того

● Строковые операции, наподобие обработки регулярных выражений, всё равно дорогие

● Впрочем, они вообще не очень дешевы.

Обработку данных нужно делать не на этапе выдачи, а на этапе помещения в базу – до тех пор, пока позволяют объёмы памяти и специфика решаемой задачи.

● В данном случае кэширование конструируемых URL новостей и т. п. позволило отыграть 15%

Page 22: Баннерокрутилка на Erlang

● Результат:

рост производительности в 10 (десять) раз

● Вывод:● всегда думай, как обрабатывать строки!● профилируй!

Page 23: Баннерокрутилка на Erlang

Основная часть решения v0.3

● HTTP-сервер Mochiweb получает запрос, создаёт поток

● Поток отправляет запрос в gen_server● gen_server производит предвыборку

новостей и присылает результат● Поток производит выборку, сортирует

данные, форматирует iolist()'ы, выдаёт, умирает.

Page 24: Баннерокрутилка на Erlang

профилируй!

Page 25: Баннерокрутилка на Erlang

Основная часть решения v0.4

● HTTP-сервер Misultin получает запрос, создаёт поток

● Поток отправляет запрос в gen_server● gen_server производит предвыборку

новостей и присылает результат● Поток производит выборку, сортирует

данные, форматирует iolist()'ы, выдаёт, умирает.

Page 26: Баннерокрутилка на Erlang

Misultin

● Реализация gen_server, как и Mochiweb● Интерфейс, абсолютно аналогичный

Mochiweb● Стабильно на 10-15% быстрее

Page 27: Баннерокрутилка на Erlang

Результат

● Один человекомесяц● Быстрое веб-приложение

# ab -qc 7200 -n 450000 http://localhost/block/35237| grep Requests\ per\ secRequests per second: 7693.35 [#/sec] (mean)#

Page 28: Баннерокрутилка на Erlang

Killing feature!

Начиная со второй недели разработки (как только был написан каркас), приложение было готово к работе.

В любой момент не работал только тот функционал, который не был дописан.

● Ни отладки● Ни непредусмотренного поведения● Только профилирование

Page 29: Баннерокрутилка на Erlang

Killing feature!

● Ни отладки● Ни непредусмотренного поведения

В Erlang есть концепция «Let it crash».Близкий перевод – «Ну и хрен с ним».

Page 30: Баннерокрутилка на Erlang

Let it crash

● На обычном языке программирования:

res = web_server.start_link(callback = Fun)

if res == web_server.port_in_use: raise Exception("Port in use")elif res == web_server.socket_error: raise Exception("Socket error")elif res == errno.EACCES: raise Exception("Not enough privileges")

Page 31: Баннерокрутилка на Erlang

Let it crash

● На Erlang:

{ok, Pid} = misultin:start_link([{loop, Fun}]).

Page 32: Баннерокрутилка на Erlang

Let it crash

● На Erlang:

{ok, Pid} = misultin:start_link([{loop, Fun}]).

● Если что-то шандарахнется, то предположениеmisultin:start_link/1 => {ok, _}

окажется неверным, и поток вылетит самс сообщением, например, таким:{badmatch, {error, eacces}}

Page 33: Баннерокрутилка на Erlang

Результат

● Один человекомесяц● Быстрое веб-приложение● Стабильное веб-приложение – за счёт eunit

и «Let it crash»● Минимум кода – за счёт множества

встроенных примитивов и «Let it crash»● Минимум требуемого опыта – Erlang

изучается за 2 недели

Page 34: Баннерокрутилка на Erlang

Уровень программиста

● С одной стороны, Erlang учится за 2 недели● С другой стороны, нужно иметь навыки

программирования. Not all batteries included

Для написания баннерокрутилки в разное время требовался то JSON-декодер, то перекодировка из UTF8 в CP1251, то htmlspecialchars(). Ничего этого в stdlib нет, нужно брать сторонние библиотеки, оценивать их работоспособность и производительность.

Page 35: Баннерокрутилка на Erlang

Напутствие

● Предобрабатывай данные, пока это дёшево!● Не выполняй одни и те же операции дважды!● Используй «Let it crash» в интерфейсах

собственного кода!● Профилируй!● Переписывай медленные операции на C,

PHP, OCaml, whatever: существуют открытые биндинги