linux_aggressive_oom
Линукс-машина периодически намертво виснет под нагрузкой на память. Кажется, что «зависла», но на самом деле система жива — она в дисковом трешинге, и штатный kernel-OOM просыпается слишком поздно, через минуты.
В чём была причина
Свопа на машине не было вообще (Swap: 0B). Когда память заканчивается, ядру некуда вытеснить анонимные страницы, поэтому оно начинает выбрасывать файловый кеш — включая страницы с кодом исполняемых программ. Но этот код тут же нужен снова: ядро читает его с диска, выбрасывает, снова читает. Получается бесконечный цикл чтения с диска — машина не отвечает, хотя формально не зависла.
Вывод, который запомнить: без свопа агрессивный OOM не спасает. Даже если настроить убийство процессов раньше, трешинг кода всё равно успевает подвесить систему.
Как решали
Два независимых слоя — они закрывают разные части проблемы.
zram — сжатый своп прямо в RAM, без диска. Даёт ядру куда вытеснять память вместо выбрасывания кода, и трешинг исчезает. На 19 ГБ RAM выделили 50% → ~9.7 ГБ эффективного свопа практически бесплатно. Алгоритм zstd (на виртуалке сжатие важнее скорости).
earlyoom — userspace-демон, который убивает самый жирный процесс при достижении порога, пока система ещё отзывчива. Это и есть «агрессивный OOM», которого не хватало.
Вместе: zram убирает трешинг, earlyoom гарантирует быстрый kill, если память реально кончилась.
systemd-oomd тоже вариант, но на VM он капризнее (нужны cgroup-лимиты на сервисы), а earlyoom — поставил и забыл.
Что конкретно сделали
Машина: Ubuntu 24.04, 19 ГБ RAM.
sudo apt install -y earlyoom zram-tools
/etc/default/zramswap:
ALGO=zstd
PERCENT=50
PRIORITY=100
/etc/default/earlyoom:
EARLYOOM_ARGS="-r 60 -m 8 -s 8 --avoid '(^|/)(systemd|systemd-.*|sshd|init)$'"
sudo systemctl restart zramswap earlyoom
Пороги earlyoom: SIGTERM при свободной памяти ≤ 8% и свопе ≤ 8%, SIGKILL при ≤ 4%. Условие «и память, и своп почти кончились» — это настоящая точка OOM, ложных срабатываний нет, потому что 9.7 ГБ zram надо сперва забить. --avoid защищает systemd/sshd/init, чтобы случайно не убить то, что держит систему. Оба сервиса в автозапуске, переживают ребут.
Как ловить виновника
Если память кончится, earlyoom залогирует, кого убил:
journalctl -u earlyoom | grep -i 'sending SIGKILL\|killing'
Чтобы поймать рост памяти до OOM (если подозреваешь утечку) — лёгкий логгер топ-процессов по RSS раз в минуту, через user-cron, без sudo:
mkdir -p ~/.local/state/memlog
( crontab -l 2>/dev/null; echo '* * * * * { date +\%F\ \%T; ps -eo rss,comm --sort=-rss | head -6; echo; } >> ~/.local/state/memlog/top.log 2>&1' ) | crontab -
После инцидента смотришь лог за минуты до зависания — видно, что росло.
Проверка
zramctl # DISKSIZE должен быть ~9.7G, а не 256M
systemctl is-active earlyoom zramswap