Баннерокрутилка на erlang
TRANSCRIPT
Баннерокрутилка:как это было на Erlang
Задача
● Выдача ссылок на новости● База из тысяч новостей ● Ссылки выбираются произвольно● Распределение – неравномерное
● Форматирование ссылок перед выдачей● Ссылки должны иметь формат блока● Дополнительные поля: идентификатор блока, …
Ta-da!
● Требования: >= 5000 запросов в секунду● Не справляемся – должны быстро выдавать
пустую страницу, чтобы не «ломать» вёрстку● Никакого кэширования
● Срок выполнения: полтора месяца.● При этом у разработчика баннерокрутилки
есть и другие задачи
Структура решения
● Структуры в памяти, хранящие новостии статистику показов
● Множество потоков, обрабатывающихHTTP-запросы
● Контроллер● добавляющий и удаляющий новости● собирающий статистику● на основе TCP-сокетов
Структура решения
Множество потоков
Erlang
● DSL для многопоточных приложений● Встроенные в язык примитивы для посылки
и приёма сообщений● Встроенные в язык шаблоны поведения● Встроенная в язык in-memory БД
Структура решения
● Структуры в памяти, хранящие новости и статистику показов: mnesia/ets/dict!
● Множество потоков, обрабатывающих HTTP-запросы: gen_server!
● Контроллер: gen_tcp!
Структура решения
● Структуры в памяти, хранящие новости и статистику показов: mnesia/ets/dict!
● Множество потоков, обрабатывающих HTTP-запросы: gen_server!● который уже написан за нас! mochiweb/misultin
● Контроллер: gen_tcp!
Batteries included
Batteries included, though not all
Основная часть решения
● HTTP-сервер Mochiweb получает запрос, создаёт поток
● Поток забирает из Mnesia данные● Поток производит выборку, сортирует
данные, форматирует строки, выдаёт, умирает.Все счастливы.
«In theory, there's no difference betweentheory and practice. In practice, there is».
L. A. van der Snepscheut.
Факап #1
У каждой новости есть коэффициент важности. В соответствии с этим коэффициентом необходимо выдавать новость чаще или реже остальных.
● Перед выдачей нужно назначать взвешенные произвольные числа каждой новости и делать по ним выборку.
● Новостей много.
Факап #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.
Эврика!
● Вместо ETS напишем собственную структуру данных на основе gen_server, dict, queue, blackjack и hookers.
● Повесим её в виде отдельного потока● Будем делать там грубую предвыборку
новостей, которые потом быстро скопируются в рабочий поток
● Результат:
рост производительности в 3 раза
● Вывод:● всегда думай, какие объёмы данных копируешь!● профилируй!
Основная часть решения v0.2
● HTTP-сервер Mochiweb получает запрос, создаёт поток
● Поток отправляет запрос в gen_server● gen_server производит предвыборку
новостей и присылает результат● Поток производит выборку, сортирует
данные, форматирует строки, выдаёт, умирает.Все счастливы.
Факап #2
Новости – это текст.
Текст – это строки.
● Строки в Erlang – это связные списки символов
● IO в Erlang – это очень медленно
Эврика!
● Если вы пишете на Erlang,то строка символов записывается так:
"Hello world!~n"
● Конкатенация записывается так:
"Hello " ++ Username ++ "!~n"
Эврика!
● Если вы пишете на Erlang веб-приложения,то строка символов записывается так:
<<"Hello world!~n">>
● Конкатенация записывается так:
[<<"Hello ">>, Username, <<"!~n">>]
Почему:
● <<>> – встроенный бинарный тип● <<"">> – бинарная строка● Списки символов нужно обрабатывать
перед выдачей● Вывод бинарных данных – это просто
вызов writev(2)● Blazingly Fast
Почему:
● "Hello" ++ "!\n" => "Hello!\n" => строка● Конкатенация списков – O(n)● Вывод строки => цикл по списку из 7 символов
● [<<"Hello">>, <<"!\n">>] – тип iolist().● Добавление в начало списка – O(1)● Вывод => цикл по списку из 2 элементов● Встроенным функциям I/O всё равно, что
выводить
Кроме того
● Строковые операции, наподобие обработки регулярных выражений, всё равно дорогие
● Впрочем, они вообще не очень дешевы.
Обработку данных нужно делать не на этапе выдачи, а на этапе помещения в базу – до тех пор, пока позволяют объёмы памяти и специфика решаемой задачи.
● В данном случае кэширование конструируемых URL новостей и т. п. позволило отыграть 15%
● Результат:
рост производительности в 10 (десять) раз
● Вывод:● всегда думай, как обрабатывать строки!● профилируй!
Основная часть решения v0.3
● HTTP-сервер Mochiweb получает запрос, создаёт поток
● Поток отправляет запрос в gen_server● gen_server производит предвыборку
новостей и присылает результат● Поток производит выборку, сортирует
данные, форматирует iolist()'ы, выдаёт, умирает.
профилируй!
Основная часть решения v0.4
● HTTP-сервер Misultin получает запрос, создаёт поток
● Поток отправляет запрос в gen_server● gen_server производит предвыборку
новостей и присылает результат● Поток производит выборку, сортирует
данные, форматирует iolist()'ы, выдаёт, умирает.
Misultin
● Реализация gen_server, как и Mochiweb● Интерфейс, абсолютно аналогичный
Mochiweb● Стабильно на 10-15% быстрее
Результат
● Один человекомесяц● Быстрое веб-приложение
# ab -qc 7200 -n 450000 http://localhost/block/35237| grep Requests\ per\ secRequests per second: 7693.35 [#/sec] (mean)#
Killing feature!
Начиная со второй недели разработки (как только был написан каркас), приложение было готово к работе.
В любой момент не работал только тот функционал, который не был дописан.
● Ни отладки● Ни непредусмотренного поведения● Только профилирование
Killing feature!
● Ни отладки● Ни непредусмотренного поведения
В Erlang есть концепция «Let it crash».Близкий перевод – «Ну и хрен с ним».
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")
Let it crash
● На Erlang:
{ok, Pid} = misultin:start_link([{loop, Fun}]).
Let it crash
● На Erlang:
{ok, Pid} = misultin:start_link([{loop, Fun}]).
● Если что-то шандарахнется, то предположениеmisultin:start_link/1 => {ok, _}
окажется неверным, и поток вылетит самс сообщением, например, таким:{badmatch, {error, eacces}}
Результат
● Один человекомесяц● Быстрое веб-приложение● Стабильное веб-приложение – за счёт eunit
и «Let it crash»● Минимум кода – за счёт множества
встроенных примитивов и «Let it crash»● Минимум требуемого опыта – Erlang
изучается за 2 недели
Уровень программиста
● С одной стороны, Erlang учится за 2 недели● С другой стороны, нужно иметь навыки
программирования. Not all batteries included
Для написания баннерокрутилки в разное время требовался то JSON-декодер, то перекодировка из UTF8 в CP1251, то htmlspecialchars(). Ничего этого в stdlib нет, нужно брать сторонние библиотеки, оценивать их работоспособность и производительность.
Напутствие
● Предобрабатывай данные, пока это дёшево!● Не выполняй одни и те же операции дважды!● Используй «Let it crash» в интерфейсах
собственного кода!● Профилируй!● Переписывай медленные операции на C,
PHP, OCaml, whatever: существуют открытые биндинги