OOM Killer — защитный механизм ядра Linux, призванный решать проблемы с нехваткой памяти. При исчерпании доступной памяти он принудительно «убивает» наиболее подходящий по приоритетам процесс, отправляя ему сигнал KILL. Сообщение об этом отображается в /var/log/syslog (Debian/Ubuntu) или /var/log/messages (Centos/Rhel). Иногда OOM Killer может затрагивать важные процессы, нарушая работу проекта. Как исправить это, узнали у Сергея Юдина, инженера Southbridge. Ниже подробный кейс с примерами кода.  К нам пришёл клиент с проблемой нехватки памяти на сервере, где много разных сервисов: Nginx, PHP-FPM, Redis и MongoDB.  PHP-FPM, обслуживая запросы, плодил дочерние процессы — столько, сколько ему разрешалось, т.е. был лимитирован. Лимиты на количество дочерних процессов рассчитывались из того, что скрипты php используют определенный размер оперативной памяти. Это возможная ошибка при настройке PHP-FPM, так как разработчики постоянно требуют всё больше и больше оперативной памяти. В один прекрасный момент прилетела довольно высокая нагрузка, и память оказалась исчерпана. А дальше OOM Killer, как это часто бывает, нашёл самый «плохой», по его мнению, процесс — MongoDB. И убил его.  Запрос клиента — сделать так, чтобы OOM Killer не убивал MongoDB. Самый «простой» способ решить проблему — посчитать количество нужной оперативной памяти для всех служб и процессов на сервере и, определив нужный размер для PHP-FPM, выставить ему лимиты:  Но так как скрипты достаточно часто меняются, такой расчёт в ближайшем будущем станет ошибочным и приведёт к повторной аварии. Мы установили приоритет для процесса MongoDB, используя параметры systemd unit: Прошла неделя и появилась новая проблема — в момент увеличения нагрузки OOM Killer убил Redis для высвобождения оперативной памяти. Мы сообщили об этом клиенту, и он попросил для Redis тоже сделать OOMScoreAdjust=-1000. Мы всё установили, но спустя ещё неделю пришло уведомление, что произошла авария, и сервер недоступен. Попытались разобраться, в чём причина, но подключиться к серверу не удалось. Других вариантов не было, и мы попросили клиента перезагрузить сервер. После перезагрузки посмотрели по логам и обнаружили, что из-за нехватки оперативной памяти OOM Killer начал искать претендента для убийства. MongoDB, которая занимала 50% оперативной памяти, убить нельзя, потому что она не входит в приоритет. Redis тоже. В итоге OOM Killer начал убивать дочерние процессы PHP FPM, причём выглядело это так: OOM Killer убивал один процесс, и PHP FPM тут же порождал ещё два. Операционная система не могла с этим справиться.   Как правило, в документациях многих приложений рекомендуют отключить swap, что и было сделано на этом проекте. Возможно, если бы swap был, то подключиться к серверу получилось без перезагрузки. Но о swap чуть позже. Мы предлагали ограничить количество дочерних процессов для PHP-FPM ещё до того, как лимитировали MongoDB. Но клиент не хотел делать это по двум причинам: Увеличение времени ожидания. Ограничение дочерних процессов PHP FPM решило бы проблему нехватки оперативной памяти, но тогда пришло бы слишком много запросов. Разберём на примере: пришло 200 запросов, а у нас всего 100 процессов, которые могут обрабатывать данные. Первые запросы обработаются быстро, а следующие будут в очереди, из-за чего timeout возрастёт. Непостоянное количество оперативной памяти, которую могут запрашивать скрипты. Один и тот же скрипт сегодня может попросить 200 Мб, завтра — 500 Мб. А после того, как разработчик внесет в него какие-то изменения, — и 1000 Мб. Этот лимит достаточно сложно вычислить, когда у тебя много процессов.  Мы пришли к тому, что все процессы, которые есть на сервере, нужно ограничивать физически. Распределили оперативную память на все службы, оговорили, кто и сколько должен поедать, и для каждой службы установили два лимита. Ещё подключили swap, при этом запретив MongoDB и Redis использовать его.  Почему приняли решение устанавливать два вида лимитов: MongoDB не может жёстко устанавливать количество оперативной памяти, используемой для своей службы. Только устанавливать лимиты для cash size — максимальный размер внутреннего кэша. Поэтому мы установили лимит для движка WiredTider cash size в 50 Гб. И дополнительно установили жёсткий лимит через systemd unit в 80 Гб. Если MongoDB превысит 80 Гб, придёт OOM Killer и точно её убьет. Выставление лимита storage.wiredTiger.engineConfig.cacheSizeGB не обязывает MongoDB не использовать свободную оперативную память и кэш файловой системы. Вопрос, как Linux работает с памятью, достаточно сложный. Если интересно, мы можем раскрыть его в следующей статье:) А пока оговоримся примерно так: Все данные, которые процессы желают записать на диски, в Linux сначала записываются в оперативную память, а уже потом записываются на диски. Почему прошлый раз упал сервер:  По всем показателям MongoDB использовала то количество оперативной памяти, которое мы выдали. И по нашим расчётам должно было остаться ещё около 40 Гб оперативной памяти, но по факту их не было — память была закэширована. Сначала MongoDB читала и писала в оперативную память, потом скидывала всё на диск. Но когда произошла авария, MongoDB не успела всё сбросить на диск из своего кэша. И не всегда память, которая используется в Linux для записи на диск, высвобождается моментально — это происходит по мере высвобождения ресурса.  Был отключен swap. Если бы swap работал, система бы очень долго откликалась, но всё же была доступна. Мы смогли бы подключиться в ssh и оперативно что-то сделать. Но для этого нужно было сделать так, чтобы MongoDB не использовала swap.  Мы поняли, что для MongoDB, Redis и PHP-FPM нужно установить «не использовать swap». Чтобы это сделать нужно установить определенное количество оперативной памяти для данных служб в cgroups. Мы задали это в systemd unit для MongoDB, Redis и PHP-FPM.  В версиях systemd имеются различия установки тех или иных параметров, мы сделали так: Для MongoDB: Для PHP-FPM: Для Redis: Мы установили лимит в 20 Гб оперативной памяти для PHP-FPM и настроили systemd.unit так, что если процессы, порожденные PHP-FPM, занимают больше 20 Гб памяти, то PHP-FPM и все его дочерние процессы будут убиты, а после перезапущены. В течение нескольких месяцев PHP-FPM убивался и стартовал. Мы мониторили через Zabbix, когда происходят аварии, почему и сколько секунд недоступен какой-то сайт, делали выводы и сообщали клиенту. Спустя время лимиты для сервисов остались прежними, но позволили понять, в какой момент, что и где идёт не так. Мониторинг уведомлял нас, мы сообщали клиенту, клиент шёл и разбирался, в чём проблема.  Через несколько месяцев проблема была решена — скрипты, которые, пожирали много памяти были устранены и оптимизированы клиентом.   Всякий раз, когда мы ограничивали MongoDB или Redis, мы говорили клиенту, что так делать не стоит. Нужно искать причину и решать основную проблему, а не лечить последствия.  То, что мы сделали, — это не best practices. Это просто кейс, основанный на работе с определенным клиентом. Теоретически всё можно было бы сделать по-другому. В этом и плюс, и минус Linux — у него много вариантов решения одной проблемы. Как понять, какой из них наиболее правильный? Мне кажется, тут всё субъективно: оцениваешь, исходя из текущих знаний и опыта. На момент решения кейса, у меня было меньше опыта. Сейчас я бы, наверное, поступил по-другому.   28 июля стартует продвинутый курс «Администрирование Linux Мега» с практикой и траблшутингом от инженера Southbridge Платона Платонова. Даже в базовых темах мы будем разбирать best practices и смотреть в глубину работы с Linux. Вы узнаете про установку Linux с помощью чёрной магии, приёмы ускорения работы в консоли, создание и применение bash-скриптов и многое другое.  Курс в целом по Linux, а не по конкретному дистрибутиву. Он поможет углубить ваши знания в работе с ОС. Всё что мы разберём во время обучения, вы сможете сразу применять на практике. Промокод «READER» даёт скидку 10% при покупке курса. Посмотреть программу и записаться: https://slurm.club/3R8ACXv  Редактор в MobileUp