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