systemd-oomd на Linux: защита сервера от нехватки памяти по PSI | AdminWiki

systemd-oomd на Linux: защита сервера от нехватки памяти по PSI

11 сентября 2026 9 мин. чтения

Что такое systemd-oomd и как он предотвращает OOM

systemd-oomd, демон в составе systemd 247 и новее, следит за давлением на память через PSI и убивает процессы в проблемной cgroup раньше, чем ядро дойдёт до полного исчерпания RAM и swap. Решение принимается не по объёму свободной памяти, а по метрике memory pressure: сколько времени задачи простаивают в ожидании страниц.

Зачем это нужно на практике. Когда память заканчивается, ядро включает OOM Killer и выбирает жертву по эвристике oom_score. К этому моменту сервер уже десятки секунд молотит по свопу, все сервисы отвечают медленно, а убитым может оказаться процесс СУБД вместо разросшегося воркера. systemd-oomd реагирует на устойчивое давление и снимает всю cgroup целиком, а не один случайный процесс во всей системе.

Работает демон в пользовательском пространстве. Отсюда высокая настраиваемость: порог давления, длительность удержания условия, заполнение swap и список групп, для которых убийство разрешено.

Ключевые отличия от OOM Killer ядра

Критерийsystemd-oomdOOM Killer ядра
Момент срабатыванияДавление на память выше порога дольше заданного окна, например some 60% и 30 секундЯдро не может выделить страницу, reclaim и своп не помогают
УровеньПользовательское пространство, служба systemd-oomd.serviceЯдро, внутри пути выделения памяти
Что убиваетВсе процессы внутри выбранной cgroupОдин процесс с максимальным oom_score
НастройкаManagedOOMMemoryPressure, ManagedOOMMemoryPressureLimit, oomd.confoom_score_adj, vm.overcommit_memory, vm.panic_on_oom
Связь со свопомОтдельный параметр SwapUsedLimitСвоп учитывается косвенно, в эвристике badness
Отключениеsystemctl disable --now systemd-oomdПолностью отключить нельзя

Практический пример разницы. Воркер приложения съел 12 ГБ при 16 ГБ RAM. OOM Killer сработает, когда свободной памяти останется ноль, и может прибить postgres. systemd-oomd увидит, что в cgroup приложения some держится на 65% дольше 30 секунд, и убьёт только эту cgroup, пока база продолжает работать.

Требования к системе: версия systemd, ядро, cgroup v2

Нужны три условия одновременно: systemd 247 или новее, ядро с поддержкой PSI (в ядре с 4.20, метрики на уровне cgroup с 5.2) и включённая унифицированная иерархия cgroup v2. Проверка занимает несколько секунд.

systemctl --version
uname -r
stat -fc %T /sys/fs/cgroup
mount | grep cgroup2

Ответ cgroup2fs от stat означает, что v2 активна. Если вывод показывает tmpfs или mount перечисляет контроллеры cgroup v1, демон не запустится: переключите иерархию параметром ядра systemd.unified_cgroup_hierarchy=1 или cgroup_no_v1=all и перезагрузитесь.

Дистрибутивные нюансы. Fedora включает systemd-oomd с версии 33, Ubuntu 22.04 тоже активирует его по умолчанию, и именно поэтому там периодически «падали» браузеры и IDE: для пользовательских сессий порог срабатывал слишком агрессивно. Debian 12 и RHEL 9 ставят пакет, но службу нужно включать руками.

systemctl status systemd-oomd
systemctl enable --now systemd-oomd

Как PSI (Pressure Stall Information) помогает принимать решения

PSI показывает, какую долю времени задачи ждут ресурс. Для памяти данные лежат в /proc/pressure/memory для хоста и в memory.pressure внутри каждой cgroup.

some avg10=0.42 avg60=1.15 avg300=0.88 total=18421302
full avg10=0.00 avg60=0.31 avg300=0.17 total=5210344

Значения avg10, avg60 и avg300 дают сглаженное давление за 10, 60 и 300 секунд, total содержит суммарное время ожидания в микросекундах с момента загрузки.

Метрики some и full: что они означают

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

systemd-oomd по умолчанию принимает решения по some. Метрика full интересна для диагностики: стабильно ненулевое full в cgroup сервиса означает, что он уже не справляется, даже если убийство ещё не сработало.

Как systemd-oomd использует PSI для выбора жертвы

Демон отслеживает только те cgroup, где явно задано ManagedOOMMemoryPressure=kill. Для каждой из них он читает memory.pressure, сравнивает some с порогом и держит условие в течение окна наблюдения. Как только давление превышает лимит дольше окна, система посылает SIGKILL всем процессам этой cgroup.

Пример с числами. Для cgroup с ManagedOOMMemoryPressureLimit=50% и стандартным окном 30 секунд убийство произойдёт при some около 55%, если такие значения держатся всё окно. Разовый всплеск на 80% в течение двух секунд срабатывания не даст. Такая логика защищает от ложных убийств при коротких пиках нагрузки.

Глобальные ориентиры лежат в /etc/systemd/oomd.conf: DefaultMemoryPressureLimit (по умолчанию 60%), DefaultMemoryPressureDurationSec (окно удержания, типовое значение 30s) и SwapUsedLimit (90% от объёма свопа). Отдельное свойство ManagedOOMSwap в systemd 252 объявлено устаревшим и удалено, за реакцию на своп теперь отвечает SwapUsedLimit.

Пошаговая настройка ManagedOOMMemoryPressure для сервисов и slices

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

Настройка для systemd-сервисов

Разберём nginx. Создайте каталог drop-in и файл /etc/systemd/system/nginx.service.d/oomd.conf со следующим содержимым.

[Service]
ManagedOOMMemoryPressure=kill
ManagedOOMMemoryPressureLimit=50%

Затем перечитайте конфигурацию и перезапустите сервис.

systemctl daemon-reload
systemctl restart nginx
systemctl show nginx.service -p ManagedOOMMemoryPressure -p ManagedOOMMemoryPressureLimit

Свойство ManagedOOMMemoryPressureLimit задаёт индивидуальный порог в процентах, если его не указывать, применяется DefaultMemoryPressureLimit из oomd.conf. Кроме kill допустимо значение auto: действие определяется настройкой системы.

Тот же приём работает для любого сервиса с собственным cgroup. Если сервис уже ограничен через MemoryMax, комбинация получается предсказуемой: сервис сначала упирается в лимит памяти, создаёт давление в своей cgroup, после чего oomd убивает именно его, не задевая соседей. Как настраивать сами лимиты, разобрано в материале про усиление systemd-сервисов и drop-in конфиги.

Настройка для пользовательских slices

Пользовательские сессии удобно ограничивать через user@.service: файл /etc/systemd/system/user@.service.d/oomd.conf.

[Service]
ManagedOOMMemoryPressure=kill
ManagedOOMMemoryPressureLimit=60%

Для уже запущенной сессии примените свойство на лету, без перезапуска и без потери открытых процессов.

systemctl set-property user-1000.slice ManagedOOMMemoryPressure=kill
systemctl set-property user-1000.slice ManagedOOMMemoryPressureLimit=60%
systemctl show user-1000.slice -p ManagedOOMMemoryPressureLimit

Учтите: systemctl set-property пишет параметры в /etc/systemd/system.control и действует до ручного сброса через systemctl revert. Для постоянной конфигурации надёжнее drop-in файл.

Глобальные параметры в oomd.conf

Файл /etc/systemd/oomd.conf задаёт поведение демона для всего хоста.

[OOM]
SwapUsedLimit=90%
DefaultMemoryPressureLimit=60%
DefaultMemoryPressureDurationSec=30s

После правки перезапустите демон и убедитесь, что он поднялся без ошибок.

systemctl restart systemd-oomd
systemctl status systemd-oomd --no-pager

Снижать SwapUsedLimit ниже 90% стоит на серверах, где своп активно используется: если 40% свопа занято постоянно, порог в 90% сработает слишком поздно, а на диске к этому моменту будет очередь запросов.

Наблюдение за работой systemd-oomd: oomctl и journalctl

Диагностика строится на двух утилитах. oomctl показывает текущее состояние и список отслеживаемых групп, journalctl хранит историю решений демона.

Использование oomctl для диагностики

Команда без аргументов выводит общие лимиты и перечень контролируемых cgroup.

oomctl
Dry Run: no
Swap Used Limit: 90.00%
Default Memory Pressure Limit: 60.00%
Default Memory Pressure Duration: 30s
Memory Pressure Monitored CGroups:
  /system.slice/nginx.service
  /system.slice/postgresql.service
  /user.slice/user-1000.slice

Формат зависит от версии systemd, набор полей может отличаться. Детальный дамп с давлением по каждой группе даёт oomctl dump.

oomctl dump

Если в дампе у сервиса давление ползёт к порогу, например 45% при лимите 50%, у вас есть запас времени на разбор утечки памяти. Растущее давление при стабильном потреблении обычно указывает на нехватку CPU или медленный диск, а не на утечку.

Анализ логов через journalctl

Демон пишет в журнал решение и причину убийства.

journalctl -u systemd-oomd --since "1 hour ago" --no-pager
journalctl -u systemd-oomd -f
journalctl -u systemd-oomd | grep -i killed

Типовое сообщение о срабатывании содержит cgroup, фактическое давление и порог: Killed /user.slice/user-1000.slice due to memory pressure 62.10% > 60.00% for more than 30s. По этим строкам удобно считать частоту инцидентов: три убийства за сутки означают, что приложению не хватает памяти и пора пересматривать лимиты, а не порог oomd.

Для разбора инцидентов с состоянием памяти хоста пригодится материал про фильтры, хранение и ротацию логов journalctl: постоянное хранение журнала критично, потому что в tmpfs события исчезают после перезагрузки.

Детализацию логов поднимают drop-in файлом для самого демона, например /etc/systemd/system/systemd-oomd.service.d/debug.conf.

[Service]
Environment=SYSTEMD_LOG_LEVEL=debug
systemctl daemon-reload
systemctl restart systemd-oomd

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

Подбор порогов и безопасное тестирование

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

Как выбрать пороги для разных типов нагрузки

Тип сервисаManagedOOMMemoryPressureLimitКомментарий
nginx, обработчики очередей50-60%Процессы быстро перезапускаются, потеря одного воркера дешева
СУБД (PostgreSQL, MySQL)70-80%Высокий порог снижает риск убийства базы, но требует запаса RAM
Пользовательские сессии60-70%На десктопах порог ниже 60% приводит к убийству браузеров
Сервисы в контейнерах с MemoryMax60%Работает предсказуемо, сервис умирает внутри своего лимита

Порядок действий: включите мониторинг, дайте системе поработать неделю, сравните давление в oomctl с реальными инцидентами и только потом опускайте порог. Если swap занят больше половины постоянно, снижайте и SwapUsedLimit, иначе реакция на своп придёт с опозданием.

Общие метрики памяти, диска и CPU под нагрузкой с пороговыми значениями собраны в руководстве по настройке производительности Linux-серверов под нагрузкой, оно помогает отделить нехватку памяти от нехватки CPU или дискового I/O.

Безопасное тестирование в изолированной среде

Проверять срабатывание oomd на рабочем сервере нельзя: убийство cgroup затронет живые процессы и может оборвать транзакции. Тестируйте на отдельной виртуальной машине или в контейнере с cgroup v2. Подойдёт недорогая VPS, например облачный сервер Timeweb Cloud, который можно создать на час и удалить после проверки.

Сценарий теста: запустите нагрузку во временном unit с включённой политикой kill и пониженным порогом.

systemd-run --unit=oomd-test --slice=oomd-test.slice -p MemoryHigh=256M \
  -p ManagedOOMMemoryPressure=kill -p ManagedOOMMemoryPressureLimit=20% \
  stress-ng --vm 1 --vm-bytes 1G --timeout 180s

MemoryHigh заставляет ядро тормозить группу и создавать давление, а не убивать процесс сразу. Дальше смотрите результат.

oomctl
journalctl -u systemd-oomd --since "5 min ago" --no-pager

Ожидаемый итог: в журнале появляется запись об убийстве oomd-test.service, а на хосте не пострадал ни один рабочий сервис. После проверки уберите тестовый slice.

systemctl stop oomd-test.service
systemctl reset-failed oomd-test.service

Альтернатива stress-ng, скрипт на Python, который выделяет и трогает страницы в цикле. Главное, чтобы нагрузка шла внутри тестовой cgroup, а не в system.slice.

Сравнение systemd-oomd и OOM Killer: что выбрать

Противопоставлять механизмы не нужно, они решают разные задачи: oomd снимает локальную перегрузку по давлению, ядро страхует от фатального исчерпания памяти.

СитуацияЧто делать
Сервис с утечкой памяти, известный виновник просадокВключить ManagedOOMMemoryPressure=kill и задать MemoryMax
Пользовательские сессии на терминальном сервереНастроить user@.service с порогом 60-70%
Один разовый пик при сборке или бэкапеПоднять DefaultMemoryPressureDurationSec, чтобы окно не совпало с пиком
Сервер с медленным swap на HDDСнизить SwapUsedLimit и добавить мониторинг диска
Полный отказ памяти вопреки всем настройкамOOM Killer ядра остаётся последним рубежом, отключить его нельзя

Проверить, кто именно сработал, помогает сравнение журналов: строки systemd-oomd описывают убийство по давлению, а сообщения ядра вида Out of memory: Killed process говорят о работе классического OOM Killer. Если в логах регулярно появляется второе, пороги oomd выставлены слишком мягко или какая-то cgroup вообще не попала под контроль.

Заключение

systemd-oomd закрывает разрыв между «сервер тормозит из-за свопа» и «ядро прибило не тот процесс». Минимальный рабочий набор: убедиться в наличии cgroup v2 и ядра 5.4+, включить службу, прописать ManagedOOMMemoryPressure=kill для сервисов и пользовательских slices, выставить пороги 50-70% и проверить результат на тестовой ВМ через stress-ng, oomctl и journalctl.

Начинайте с одного сервиса, ведите наблюдение неделю и только затем расширяйте список контролируемых групп. Дополнительно полезно ограничить память самих сервисов: материал про метрики CPU, памяти, диска и сети помогает поймать рост потребления до того, как он превратится в инцидент.

Поделиться:
Сохранить гайд? В закладки браузера