Unicorn
Для обработки HTTP запросов от git и пользователей GitLab использует Unicorn, Ruby сервер с prefork. Unicorn — это демон, написанный на Ruby и C, который может загружать и выполнять Ruby on Rails приложение, в нашем случае — GitLab Community Edition или GitLab Enterprise Edition.
Unicorn имеет мультипроцессную архитектуру для поддержки многоядерных систем (процессы могут выполняться параллельно на разных ядрах) и для отказоустойчивости (аварийно завершившийся процесс не приводит к завершению GitLab). При запуске основной процесс Unicorn загружает в память Ruby и Gitlab, после чего запускает некоторое количество рабочих процессов, которые наследуют этот «начальный» слепок памяти. Основной процесс Unicorn не обрабатывает входящие запросы — это делают рабочие процессы. Сетевой стек операционной системы получает входящие подключения и распределяет их по рабочим процессам.
В идеальном мире основной процесс один раз запускает пул рабочих процессов, которые затем обрабатывают входящие сетевые подключения до скончания веков. На практике рабочие процессы могут аварийно завершиться или же быть убиты по превышению времени ожидания. Если основной процесс Unicorn обнаруживает, что один из рабочих процессов обрабатывает запрос слишком долго, то он убивает этот процесс с помощью SIGKILL (kill -9). Вне зависимости от того, как завершился рабочий процесс, основной процесс заменит его на новый, который наследует все то же «начальное» состояние. Одна из особенностей Unicorn — возможность заменять дефектные рабочие процессы не обрывая сетевых подключений с запросами пользователей.
Пример таймаута рабочего процесса, который можно найти в unicorn_stderr.log. Идентификатор основного процесса 56227:
[2015-06-05T10:58:08.660325 #56227] ERROR -- : worker=10 PID:53009 timeout (61s > 60s), killing
[2015-06-05T10:58:08.699360 #56227] ERROR -- : reaped #<Process::Status: pid 53009 SIGKILL (signal 9)> worker=10
[2015-06-05T10:58:08.708141 #62538] INFO -- : worker=10 spawned pid=62538
[2015-06-05T10:58:08.708824 #62538] INFO -- : worker=10 ready
Основные настройки Unicorn для работы с процессами — это количество процессов и таймаут, по истечении которого процесс будет завершен. Описание этих настроек можно посмотреть в этом разделе документации GitLab.
unicorn-worker-killer
В GitLab есть утечки памяти. Эти утечки проявляются в долго работающих процессах, в частности — в рабочих процессах, создаваемых Unicorn (при этом в основном процессе Unicorn таких утечек нет, так как он не обрабатывает запросов).
Для борьбы с этими утечками памяти GitLab использует unicorn-worker-killer, который модифицирует рабочие процессы Unicorn чтобы они проверяли использование памяти через каждые 16 запросов. Если объем используемой памяти рабочего процесса превышает установленный лимит, то процесс завершается и основной процесс Unicorn автоматически заменяет его на новый.
На самом деле это неплохой способ борьбы с утечками памяти, так как дизайн Unicorn позволяет не терять запрос пользователя при завершении рабочего процесса. Более того, unicorn-worker-killer завершает процесс между обработкой запросов, так что это никак не отражается на работе с ними.
Вот так в файле unicorn_stderr.log выглядит рестарт рабочего процесса по причине утечки памяти. Как можно видеть, процесс с идентификатором 125918 после самоанализа принимает решение завершиться. Пороговое значение памяти при этом составляет 254802235 байт, то есть порядка 250 мегабайт. GitLab использует в качестве порогового значения случайное число в диапазоне от 200 до 250 мегабайт. Основной процесс GitLab с идентификатором 117565 затем создает новый рабочий процесс с идентификатором 127549:
[2015-06-05T12:07:41.828374 #125918] WARN -- : #<Unicorn::HttpServer:0x00000002734770>: worker (pid: 125918) exceeds memory limit (256413696 bytes > 254802235 bytes)
[2015-06-05T12:07:41.828472 #125918] WARN -- : Unicorn::WorkerKiller send SIGQUIT (pid: 125918) alive: 23 sec (trial 1)
[2015-06-05T12:07:42.025916 #117565] INFO -- : reaped #<Process::Status: pid 125918 exit 0> worker=4
[2015-06-05T12:07:42.034527 #127549] INFO -- : worker=4 spawned pid=127549
[2015-06-05T12:07:42.035217 #127549] INFO -- : worker=4 ready
Что еще бросается в глаза при изучении этого лога: рабочий процесс обработал всего 23 запроса, прежде чем завершиться из-за утечек памяти. На данный момент это является нормой для gitlab.com
Такой частый рестарт рабочих процессов на серверах GitLab может стать причиной беспокойства для сисадминов и devops, но на практике это чаще всего нормальное поведение.
Комментарии (15)
slonopotamus
10.11.2015 10:21+7Я извиняюсь, а какие такие религиозные причины мешают просто взять и починить memory leak'и?
Caravus
10.11.2015 10:41+2Я вот честно сказать тоже не понимаю. Везде где вижу упоминания про этот unicorn — везде пишут про проблемы с памятью, но при этом продолжают пользоваться. Мыши кололись но продолжали жрать кактус? Вместо того чтоб починить иточник проблемы — придумали костыли, которые решают проблему с памятью за счёт нагрузки на ЦП.
FractalizeR
10.11.2015 10:58Вероятно, это не так уж и просто. Не думаю, что просто так вот взяли и не починили.
vladon
10.11.2015 11:01Ruby?
creker
10.11.2015 11:47Пытались и неоднократно, как они пишут. Исправить не получилось до их пор, поэтому костыль.
printercu
10.11.2015 11:32+1Зачем еще один перевод? Еще остались пользователи юникорна, которые не знают про worker-killer? Нового ничего они не предлагают. Если пропустить через s/gitlab/other-unicorn-user/g, то всё так же останется правдой.
tzlom
10.11.2015 11:43Это не unicorn, это руби.Gitalab CE нельзя запустить на 5$ DO instance потому что не влазит в память при загрузке (конечно можно своп но сервер свопящийся уже на старте это грустно)
IDMan
10.11.2015 12:19+1Я запустил на 10$ инстансе Linode — это примерно вдвое лучше по характеристикам, и он постоянно валится в 500-ю ошибку, в логах «не хватает памяти», и спасает только перезапуск сервиса. Причем работаю на инстансе только я один, максимум двое. Такая требовательность поражает.
Prototik
Всё же, он обработал не всего 23 запроса, а вообще работал 23 секунды, за которые мог успеть обработать запросов гораздо больше. Если бы какое либо приложение на моем сервере бы рестартилось каждые 23 запроса (пусть и без разрывов связи) — я бы снёс такое приложение не задумываясь.
Мой GitLab живёт по 3-4 часа на воркер и обрабатывает несколько сотен тысяч запросов за это время.