Макс Лапшин (levgem) wrote,
Макс Лапшин
levgem

Category:

Про эрланг vs XXX: интроспекция для отладки

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

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

Так же важно отметить описанный мной класс задач, потому что от остальных он отличается: частым и беспорядочным сетевым I/O, необходимостью хранить много данных в памяти для write-through кеша, мультиплексированием и агрегацией потоков данных внутри себя.




зацикливание



Зацикливания происходят крайне редко и это во многом связано с тем, что если делать всё по OTP, да желательно без своих рекурсивных функций, то им (зацикливаниям) попросту неоткуда взяться.

Устранение проблемы с зацикленным процессом как правило достаточно несложно через интроспекцию. Во-первых, к нему больше не проходят gen_server:call, во-вторых он в топе etop-а, в-третьих у него в current_function не ожидание сообщения, а что-то типа proplists:get_value или другой пользовательской функции. Это не очень нормально, если это постоянно так.

системные лимиты



Что же ещё частенько возникает проблемного с серверным демоном. Заканчиваются сокеты? Да, бывает, и софт на эрланге, если его специально не насиловали, во все логи истерично орет про emfile. Несложно гуглится, что это означает. Поиск проблемы как правило так же несложен: вытаскиваем список портов, ищем какого класса эти порты (сокет, файл), смотрим кто к ним прилинкован и выясняем откуда проблема. Проблема решается за один человекодень начинающим программистом.

утечка памяти



Самый частый класс ошибок с которыми я сталкивался — это именно утечка памяти. Это означает, что втекает быстрее, чем вытекает. За этим прячутся разные проблемы: например включили активный приём из сети, забыв про flow control. Или ошибка в алгоритме, когда вместо того, что бы обрабатывать маленькими порциями, пытаемся сразу выгрести весь большой объём данных. Или опять решили сумничать и вместо того, что бы сделать gen_server:call, воспользовались посылкой сообщения и запихнули миллионное сообщение в очередь принимающего процесса.

Бывают и ещё более тонкие проблемы. Например, я как-то раз выгреб ошибку: каждый входной пакет из сети я засовывал в буфер декодера и пытался выгрести только один кадр, а так получилось, что в прилетающих пакетах было стабильно по 1-2 кадра, т.е. буфер быстро вырос и всё упало.

Поиск таких ошибок как правило просто до безобразия:

1) Запускаем монит и ждем, пока он просигнализирует, что превысили 50% памяти (мы ведь не забыли, что своп на продакшн сервере от лукавого?)

2) заходим remsh (и только remsh, никаких аттачей) на сервер и форсим полную сборку мусора. Аттач (to_erl) использовать нельзя, потому что если вы выгребете в контекст процесса пару десятков гигабайт бинарников, то они в шелле и останутся. Лучше подсоединиться по TCP и потом просто прибить эту новую ноду

3) если после сборки память ушла с 16 гигабайт до 500 мегабайт, то смотрим нет ли у нас накапливающих ссылки процессов. Смотреть прийдется в следующий раз, когда пропищит монит, потому что в этот раз мы всё разрушили своим наблюдением. Как смотреть — ниже.

4) если память никуда не ушла, перебираем все процессы и сортируем их по следующим показателям: потребление памяти самим процессом, суммарный объём бинарей на которые он ссылается, длина очереди сообщений.

4.1) Если у процесса в очереди 10 тыс сообщений, то я считаю что это однозначный швах. Некоторые считают по-другому и считают, что mailbox вполне пригодное место для долговременного складирования данных. У меня ситуация такая, что вся нагрузка очень равномерная и если мы сейчас с ней не справляемся, то всё, точка, сервер перегружен, легче будет только когда бизнес разорится. Поэтому в эрливидео наличие тысячи сообщений в очереди процесса — это явный индикатор того, что процесс надо прибить, всё плохо. Я уже писал про то, как мы подходим к процессам. Главная проблема в длинных mailbox в том, что разрушается обратная связь по ограничению нагрузки.

4.2) Если в куче эрланговского процесса (heap, memory) хотя бы 100 мегабайт — это беда, это именно архитектурный факап, это надо лечить. Беда это по той причине, что такой процесс скорее всего хранит данные не там, где нужно. Такие объёмы лучше хранить в ets или даже в скомпиленных beam(!). Впрочем, ситуации бывают разные, так что если очень нужно, то делайте как нужно.

4.3) В эрланге можно посмотреть, на какие бинари ссылается процесс и сколько они все весят. Бинарей у него по ссылкам могут быть гигабайты, но надо убедиться в том, что они все нужны. Выше я уже писал: надо запустить полную сборку мусора и посмотреть, ушли они или нет. Если не ушли, то можно вытянуть состояние процесса через sys:get_state (это могут быть многие гигабайты!) и после этого можно посмотреть, где накопились данные.


5) у меня как правило главный обжора виден сразу. Это чаще всего процесс, который берет видео и пихает в забитый сокет. В сокет по какой-то причине не пихается и видео накапливается. Если процесс просто висит и ждет сообщений, его можно поинспектировать, забрав его state. Дальше изучаем, что сожрало память и чиним баг. Пару часов времени и всё готово. Штатное использование gen_tcp очень легко приводит к проблемам. На сокете обязательно надо выставлять send_timeout и проверять результат.


6) если все процессы где-то в районе 1-5 мегабайт, а сожраны десятки гигабайт памяти, ищем дальше в ets. Туда очень удобно класть данные и ещё удобнее их оттуда не стирать. Смотрим за количеством ключей, перебираем ключи во времени и выясняем, какой класс ключей не стирается. Чиним и радуемся. Потом меняем самопальную ets-у на проверенный gen_tracker, который на самом деле gproc с человеческим лицом и всё становится хорошо.

7) есть ещё другой, особый класс утечек, когда данные оседают в портах. Такое тоже бывает, но я не сталкивался, так что не смогу рассказать про это

Как перебирать процессы



Расскажу подробнее, как же всё таки перебирать процессы.

Главная функция — это erlang:processes(), список всех процессов. Итак, при поиске обжоры надо запросить данные по всем процессам, отсортировать и показать инфу про top5.

Получение информации о процессе происходит с помощью erlang:process_info(Pid) или более уточняющей формы: erlang:process_info(Pid,binary). Про процесс нам понадобятся такие данные, как: binary, message_queue_len, memory и dictionary.

При перебирании всех процессов надо помнить, что половина может успеть сдохнуть, так что process_info заворачиваем в catch и ошибки выбрасываем.

Дальше начинаются ньюансы. Когда мы выбрали самые пухлые процессы, надо как-то показать себе чем они занимаются. Причем, если у процесса забита очередь сообщений, ему нельзя сделать gen_server:call. Для этого я во все процессы пишу в process dictionary тег name. Например: put(name, {live_stream, StreamName}) и сразу по содержимому dictionary становится понятно, чего тут вообще творится.

О чём не рассказано



Я не рассказал о trace, потому что не пользуюсь им, я не рассказал о тонкостях управления аллокаторами памяти. Я не рассказал о dtrace, потому что ничего не знаю, но очень хочу, потому что там, на темной стороне dtrace обещают какие-то совершенно чумовые печеньки.

Но самое главное, что совершенно непонятно как отказываться от эрланга, если таких чумовых и простейших штук, на которых вообще весь юникс взлетел, нет больше ни в одном рантайме?
Tags: erlang, erlyvideo, fp
Subscribe
  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic

    Your reply will be screened

    Your IP address will be recorded 

  • 21 comments