Будни техподдержки

Mar 23, 2015 13:51

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

У них странная проблема: после запуска флюссоника моментально съедаются все ядра и потребление памяти растет с такой скоростью, что 30 гигабайт примерно за 100-120 секунд заканчиваются и флюссоник падает. Точнее сначала был включен своп, а с ним очень весело: пока он включен, программа не может умереть, потому что всё постоянно свопится.

Запускаю флюссоник в foreground, память жрется быстро, но в ets таблицах утечек нет, там всё по минимуму. Выключаю все каналы, запускаю заново, всё работает. По одному запускаю каналы и нахожу тот, после включения которого всё падает.



Поиск проблемы

Первая мысль: что-то не то с источником и его обработка приводит к такому сверхбыстрому потреблению ресурсов. Вытащили наружу урл потока, забрал у себя на ноутбуке, всё работает без проблем.

Потом я догадался (блин) посмотреть в access.log. Там полно запросов к плейлисту архива длиной почти в сутки. К флюссонику можно обратиться по специально сформированному урлу (по протоколу HLS) и получить список mpegts сегментов из которых состоит архив. Выясняется, что один единственный клиент дергает этот урл во много потоков и укладывает таким образом сервер, потому что сегментов в этом плейлисте порядка 10 тыс, а сам плейлист где-то под 700 кб размером (нежатый).

Позорище, конечно, полное. Целевое время для отдачи данных из памяти у нас - единицы, максимум десятки миллисекунд. Т.е. всё что больше 100 мс - повод для грусти, а тут до 6000 мс!

Сначала я решил, что долго вытаскивается список сегментов из архива, но это не так. У нас там всё очень хорошо сделано и оптимизировано, так что список сегментов тянется за 10 мс даже под полной нагрузкой (все 8 ядер на 95%). Значит, тормозит кусок кода, который из 10 тыс сегментов делает текстовый плейлист размером в 700 кб.

Воспроизвел ситуацию у себя на ноутбуке, всё повторилось. Считанные миллисекунды даже на чтение с SSD и долгие сотни миллисекунд на работу с текстом. Профилировка (fprof) сразу ткнула в предателей: io_lib:format и модуль calendar.

Оптимизация

Итак, мы выяснили, что для получения структуры архива за сутки (более 10 гигабайт данных на диске), флюссоник тратит 10-15 мс на вытаскивание данных из собственной базы и 600 мс (на холодном ноутбуке) на превращение в текстовый плейлист и по профилировке, виноват io_lib:format и calendar, превращающий UTC в year, month, day, hour, minute, second. А тормозило всё в одном месте, где таймстемп сегмента превращался в iso8601 время этого сегмента:

Timestamp = {UTC div 1000000, UTC rem 1000000, 0},
{{Y,M,D},{H,_Min,_Sec}} = calendar:now_to_datetime(Timestamp),
iolist_to_binary(io_lib:format("~4.10.0B-~2.10.0B-~2.10.0BT~2.10.0B:~2.10.0B:~2.10.0BZ", [Y, Mo, D, H, Mn, S]))

Первая реакция была взять наш со stolen io_libc: https://github.com/maxlapshin/io_libc
Воткнул, всё стало очень хорошо, вместо 600 стало 250.

Воткнул gmtime в NIF, стало всё ещё лучше, почти 150 мс: https://github.com/maxlapshin/io_libc/commit/42a7387e4b44552d86b733b9d18256f49091df39

Заодно решил попросить о помощи у коммьюнити и мне посоветовали способ, которым я и сам раньше пользовался, но тут чего-то сглупил и забыл про него: это прямое составление бинарей, захардкоженная шаблонизация.

iso8601({{Y,Mo,D}, {H,Mn,S}}) ->
<<(integer_to_binary(Y))/binary, $-,
($0 + (Mo div 10)), ($0 + (Mo rem 10)), $-,
($0 + (D div 10)), ($0 + (D rem 10)), $T,
($0 + (H div 10)), ($0 + (H rem 10)), $:,
($0 + (Mn div 10)), ($0 + (Mn rem 10)), $:,
($0 + (S div 10)), ($0 + (S rem 10)), $Z>>.

Этот уродливый костыль оказался самым быстрым, хотя и не таким гибким как io_libc. В итоге на бенчмарках время упало с 600 до 100 мс, потребление памяти в разы.

Сделали, собрали, выкатили клиенту. CPU в норме (150% из 800%), память держится стабильно. А взбесившаяся приставка ограничилась 15 запросами в секунду по 700 килобайт, что примерно в районе 100 мегабит.

erlyvideo, erlang, flussonic

Previous post Next post
Up