Зачем разбираться с memory pressure
Проблемы с памятью на проде часто выглядят как «всё было нормально, а потом внезапно упало»: воркеры начинают отдавать 502/504, база внезапно тормозит, контейнеры перезапускаются, а в логах появляется oom killer или сообщения про out of memory. При этом «свободной памяти» в free иногда выглядит много или, наоборот, почти ноль — и оба варианта могут сбивать с толку.
Ключевой термин тут — memory pressure: состояние, когда ядру приходится активно освобождать память (reclaim), выкидывать кеши, скидывать страницы в swap. Если безопасных вариантов уже нет, ядро запускает OOM killer и убивает один или несколько процессов, чтобы вернуть системе возможность работать.
Ниже — практический разбор: как Linux расходует RAM (включая page cache), как читать симптомы давления, почему в контейнерах и systemd-юнитах важны лимиты cgroups memory и что сделать, чтобы OOM не был «внезапным сюрпризом».
Как Linux использует память: anon, file, page cache и «свободно»
Для диагностики важно различать типы памяти, иначе легко сделать неправильный вывод по одному числу.
- Anon memory — анонимные страницы: heap/stack, структуры приложений, in-memory кеши (JVM heap, Redis и т.п.).
- File-backed — страницы, связанные с файлами: mmap, библиотеки и самое заметное — page cache (кеш данных файлов), который Linux активно использует, чтобы ускорять дисковый I/O.
- Slab — кеши структур ядра (например, dentry/inode).
- Swap — подкачка: позволяет временно разгрузить RAM, выгружая неактивные anon-страницы на диск.
Типичная ловушка: «free маленький, значит памяти не хватает». В Linux свободная память — не цель, а признак недоиспользования ресурса: значимая часть RAM может быть занята page cache, который при необходимости освобождается.
Быстрый ориентир по состоянию — available (сколько можно отдать приложениям без сильной деградации), а не free:
free -h
Для разреза по подсистемам полезно смотреть /proc/meminfo:
grep -E 'MemTotal|MemFree|MemAvailable|Buffers|Cached|SwapTotal|SwapFree|Anon|Slab|SReclaimable|Dirty|Writeback' /proc/meminfo
Page cache в основном отражается в Cached (и частично в slab). Он ускоряет чтение, но под нагрузкой начинает конкурировать за RAM с приложениями. Тогда ядро запускает reclaim: чистит кеши, вытесняет страницы и при нехватке времени/ресурсов начинает расти давление.
Что происходит при нехватке памяти: reclaim, swap и затем OOM
Сценарий «память заканчивается» обычно развивается ступенчато — и это как раз то, что нужно уметь распознавать.
- Reclaim file-cache: ядро пытается освободить память, выкидывая page cache и другие file-backed страницы (обычно это относительно дёшево).
- Swap-out anon (если swap включён и политика позволяет): часть анонимной памяти уезжает в swap. Это может спасти от OOM, но увеличивает задержки.
- Compaction (не всегда): если нужен большой непрерывный блок памяти, ядро уплотняет страницы; на этом месте иногда видны «странные лаги».
- Direct reclaim: процессы сами начинают тратить CPU/время на освобождение памяти, и latency растёт.
- Out of memory: когда запросы памяти уже невозможно удовлетворить — ядро выбирает жертву и запускает
oom killer.
OOM killer — не «причина», а последний предохранитель. Он срабатывает, когда система больше не может гарантировать прогресс выполнения из-за нехватки памяти.
Swap: зло, благо или необходимый компромисс
Swap даёт ядру дополнительный манёвр. Без swap при пике anon-памяти (например, резкий рост воркеров или кешей) вы быстрее придёте к OOM. Со swap можно пережить всплеск, но появляется риск задержек из-за подкачки.
На практике умеренный swap часто выбирают, чтобы:
- не ловить мгновенный out of memory при кратковременном пике;
- дать ядру выгрузить действительно неактивные anon-страницы;
- сгладить «зазубрины» потребления RAM.
Проверить, что swap есть и как он используется:
swapon --show
cat /proc/swaps
vmstat 1
Агрессивность использования swap регулируется через vm.swappiness:
sysctl vm.swappiness
Слишком низкое значение может привести к более раннему OOM, слишком высокое — к лишней подкачке. Универсального числа нет: для latency-чувствительных сервисов часто выбирают ниже, для фоновых задач — выше. Опираться стоит на метрики PSI и реальный профиль нагрузки.
Если вы размещаете сервисы на VDS, полезно заранее продумать объём RAM и swap под ваш профиль (воркеры, кеш, база), чтобы пиковые сценарии не заканчивались OOM «на ровном месте».

Как понять, что у вас именно memory pressure
Диагностику удобно вести на трёх уровнях: «что убили», «почему началось давление», «кто создаёт давление прямо сейчас».
1) Найти след OOM killer
Если ядро убивало процессы, это почти всегда видно в журнале:
journalctl -k -b | grep -i -E 'oom|out of memory|killed process'
Если journald нет, смотрите dmesg:
dmesg -T | grep -i -E 'oom|out of memory|killed process'
В сообщениях обычно есть PID, имя процесса и иногда причины выбора (условная «badness score»).
2) Посмотреть PSI (Pressure Stall Information)
PSI показывает, как часто задачи не могут выполняться из-за нехватки ресурса. Это намного ближе к «ощущениям пользователей» (задержкам), чем просто «сколько занято».
cat /proc/pressure/memory
Если full заметно больше нуля — это уже серьёзный сигнал: система реально простаивает, ожидая память (обычно из-за тяжёлого reclaim или swap).
3) Понять структуру потребления памяти
Быстрое приближение по процессам:
ps -eo pid,ppid,comm,rss,pmem --sort=-rss | head -n 20
В контейнерной среде помните: RSS процесса не всегда равен тому, что учитывает cgroup (особенно из-за file-cache и shared-памяти). Но для первичного поиска «кто крупнейший» — этого достаточно.
Для более детального анализа пригодятся:
cat /proc/meminfo
cat /proc/vmstat | grep -E 'pgscan|pgsteal|oom|swap'
slabtop -o
Рост pgscan/pgsteal и частые swap-in/swap-out обычно подтверждают именно давление, а не «просто много кеша».
OOM killer: как он выбирает, кого убить
Когда ядро приходит к состоянию out of memory, ему нужно освободить память немедленно. Для этого оно выбирает «жертву» по внутренней метрике: насколько выгодно убить процесс, чтобы быстро вернуть память и минимально повредить системе.
На выбор обычно влияют:
- объём потребления памяти (особенно anon/RSS);
- роль процесса (демон, воркер, дочерний процесс) и контекст;
- значение
oom_score_adj; - в случае cgroups — ограничения группы и события внутри неё.
Текущие значения можно посмотреть так:
cat /proc/1234/oom_score
cat /proc/1234/oom_score_adj
Чем выше oom_score, тем вероятнее процесс будет убит. Через oom_score_adj можно «защитить» критичные демоны или наоборот сделать воркеры более вероятными жертвами. Но злоупотреблять этим опасно: если «убивать некого», вы повышаете шанс получить зависание или более тяжёлый сценарий деградации.
Почему «съел память» часто не тот, кого убили
OOM killer выбирает не «виновника», а кандидата, убийство которого даст эффект быстрее всего. Типовые случаи:
- память раздулась у множества воркеров, а убили один большой процесс;
- основной потребитель — file-cache от активного I/O, но убили приложение с большим RSS;
- в контейнерах один сервис давит памятью, но из-за иерархии и лимитов страдает другой.
Поэтому после OOM важно смотреть не только строку Killed process, а контекст за несколько минут до события: PSI, swap, рост воркеров, пики трафика и фоновые задачи.
cgroups memory: почему лимиты меняют правила игры
cgroups memory ограничивает и учитывает память для группы процессов: контейнера, systemd unit или slice. Поэтому в Docker/Kubernetes «память кончилась» может случиться даже если на хосте RAM ещё есть: лимит достигнут внутри cgroup.
Проверить версию cgroup можно так:
stat -fc %T /sys/fs/cgroup
Если вывод cgroup2fs — это v2.
Ключевые файлы cgroups v2 для памяти
В v2 диагностика во многом делается чтением файлов в дереве cgroup. Самые полезные:
memory.current— текущее потребление;memory.max— жёсткий лимит (илиmax);memory.high— мягкий порог, после которого начинается активное throttling/reclaim;memory.stat— разрез anon/file/slab и т.д.;memory.events— счётчики событий (oom,oom_kill,high,max).
Посмотреть события OOM внутри конкретной systemd-cgroup:
cat /sys/fs/cgroup/system.slice/myservice.service/memory.events
Если растёт oom_kill, это cgroup OOM: лимит группы сработал, даже если хост в целом «жив».
memory.high vs memory.max: практический смысл
memory.max — «стена». Достигли — дальше либо отказы аллокаций, либо OOM внутри группы (с убийствами), либо ошибки в приложении.
memory.high — «предупредительная зона». При превышении ядро активнее возвращает память и замедляет потребителей, давая шанс стабилизироваться без убийств.
Чтобы уменьшить внезапные убийства при кратковременных всплесках, часто помогает комбинация
memory.highи разумногоmemory.max, а не один «впритык» жёсткий лимит.
Page cache внутри cgroup: почему «file cache съел лимит»
Частый сюрприз: лимиты памяти cgroup учитывают не только anon, но и file-cache. То есть сервис, активно читающий и пишущий (логи, сборка ассетов, бэкапы, большие выгрузки), может «набить» лимит page cache и спровоцировать OOM в контейнере.
Смотрите разрез в memory.stat (поля вроде anon, file, slab). Это помогает понять: у вас утечка/раздувание heap или I/O-паттерн, который загоняет в лимит file-cache.
Практика: быстрый чек-лист при инциденте
Если сервер уже «подвисает» или случился OOM, действуйте последовательно — так вы быстрее соберёте фактуру и не будете гадать.
Шаг 1. Зафиксировать факт OOM и контекст
journalctl -k -b | tail -n 200
journalctl -k -b | grep -i -E 'oom|out of memory|killed process'
Запишите время, PID/команду, и уточните: это системный OOM или cgroup OOM (обычно видно по упоминаниям memory cgroup и по memory.events).
Шаг 2. Посмотреть давление (PSI) и подкачку
cat /proc/pressure/memory
vmstat 1
Если si/so стабильно не нулевые и PSI высокий, система тратит время на reclaim/swap и теряет производительность.
Шаг 3. Найти потребителя и тип памяти
ps -eo pid,comm,rss,pmem --sort=-rss | head -n 20
grep -E 'MemAvailable|Cached|SwapFree' /proc/meminfo
В контейнерах/systemd-юнитах параллельно проверьте memory.current/memory.max и memory.stat проблемной группы: так вы поймёте, что именно упирается в лимит — anon или file.

Как снизить риск OOM: практичные меры
1) Привести в порядок лимиты (и не забыть про burst)
Если сервис может кратковременно потреблять больше памяти (пики трафика, прогрев кеша, компиляции), закладывайте burst. В контейнерной среде не ставьте memory.max «впритык». Практичный подход:
- задать
memory.highкак ранний порог, чтобы увидеть деградацию до убийств; - оставить разумный запас до
memory.max; - контролировать параллелизм (воркеры, треды, очереди).
2) Контролировать параллелизм и очереди
Очень часто OOM — следствие лавинообразного роста воркеров: трафик растёт, приложение создаёт больше процессов, каждый забирает память, и суммарно вылетаете в стену. Лимитируйте:
- число воркеров (PHP-FPM, gunicorn, Node.js clusters);
- размеры пулов и очередей;
- конкурентные фоновые задачи (бэкапы, индексации) по расписанию.
3) Следить за page cache и I/O-паттернами
Page cache сам по себе полезен, но под лимитами может стать причиной OOM. Если memory.stat показывает большой file:
- проверьте, нет ли фоновых задач, активно читающих большие файлы;
- уменьшите «болтливость» логов и частоту ротации;
- разнесите тяжёлые I/O операции по времени;
- пересмотрите лимиты памяти для таких задач.
Если проблема связана с кешированием на веб-уровне, иногда полезно отдельно оптимизировать кэш Nginx (размеры, ключи, TTL), чтобы меньше провоцировать лишний I/O и раздувание file-cache. По теме может пригодиться материал: кеширование и маппинг форматов в Nginx для снижения нагрузки.
4) Иметь умеренный swap и мониторить его
Swap не лечит утечки памяти, но снижает вероятность мгновенного OOM при кратких пиках. Если swap отсутствует, убедитесь, что:
- есть запас RAM относительно реальной нагрузки;
- жёстко ограничены burst-сценарии (воркеры, очереди, фоновые задачи);
- настроены ранние алерты по PSI и
MemAvailable.
5) Включить ранние алерты по PSI и событиям cgroup
Лучший момент для реакции — когда давление растёт, но убийств ещё нет. Имеет смысл алертить:
- рост
/proc/pressure/memory, особенноfull; - падение
MemAvailableниже безопасного порога; - рост счётчиков
memory.events:high,max,oom_kill.
Для сервисов, где внезапные перезапуски критичны (например, интернет-магазин или API), чаще выгоднее держать небольшой запас ресурсов и переносить такие нагрузки на более подходящий тариф VDS, чем жить с лимитами «впритык» и лечить последствия OOM.
Мини-словарь: что искать в логах и метриках
- oom killer — механизм ядра, убивающий процессы при критической нехватке памяти.
- out of memory — состояние, когда ядро не может удовлетворить запрос памяти и вынуждено освобождать её радикально.
- cgroups memory — лимиты/учёт памяти для групп процессов; частая причина «OOM в контейнере».
- page cache — кеш файловых данных, ускоряет I/O, но может «съесть» лимит cgroup при активной работе с файлами.
- swap — подкачка, повышает устойчивость к пикам, но может увеличивать задержки при активном использовании.
- PSI — показатель реального давления (stall), когда задачи простаивают из-за нехватки памяти.
Итоги
Чтобы уверенно разбирать инциденты с памятью в Linux, полезно мыслить не «сколько занято», а «есть ли давление и где оно возникает». Начинайте с журналов ядра (факт OOM), добавляйте PSI (реальная деградация), затем смотрите структуру потребления (anon vs file/page cache) и ограничения cgroups memory.
На практике чаще всего помогают: запас по памяти относительно пиков, умеренный swap, корректные лимиты cgroups (особенно сочетание memory.high и memory.max) и мониторинг PSI/событий. Тогда «внезапно всё упало» превращается в управляемую историю с предсказуемыми сигналами заранее.


