ZIM-НИЙ SAAALEЗимние скидки: до −50% на старт и −20% на продление
до 31.01.2026 Подробнее
Выберите продукт

Linux memory pressure: OOM killer, swap, page cache и лимиты cgroups

Когда в Linux заканчивается память, система редко «падает» сразу: сначала идут reclaim и очистка page cache, затем возможна подкачка в swap, и только потом срабатывает OOM killer. Разбираем диагностику по логам, PSI и cgroups v2 и меры профилактики OOM.
Linux memory pressure: OOM killer, swap, page cache и лимиты cgroups

Зачем разбираться с 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

Сценарий «память заканчивается» обычно развивается ступенчато — и это как раз то, что нужно уметь распознавать.

  1. Reclaim file-cache: ядро пытается освободить память, выкидывая page cache и другие file-backed страницы (обычно это относительно дёшево).
  2. Swap-out anon (если swap включён и политика позволяет): часть анонимной памяти уезжает в swap. Это может спасти от OOM, но увеличивает задержки.
  3. Compaction (не всегда): если нужен большой непрерывный блок памяти, ядро уплотняет страницы; на этом месте иногда видны «странные лаги».
  4. Direct reclaim: процессы сами начинают тратить CPU/время на освобождение памяти, и latency растёт.
  5. 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 «на ровном месте».

Схема типов памяти в Linux: anon, file, slab, swap и page cache

Как понять, что у вас именно 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, а не один «впритык» жёсткий лимит.

FastFox VDS
Облачный VDS-сервер в России
Аренда виртуальных серверов с моментальным развертыванием инфраструктуры от 195₽ / мес

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.

Проверка лимитов и событий памяти cgroups v2 через memory.current, memory.max и memory.events

Как снизить риск 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/событий. Тогда «внезапно всё упало» превращается в управляемую историю с предсказуемыми сигналами заранее.

Поделиться статьей

Вам будет интересно

IPv6 на сервере: SLAAC vs static, privacy extensions и firewall без сюрпризов OpenAI Статья написана AI (GPT 5)

IPv6 на сервере: SLAAC vs static, privacy extensions и firewall без сюрпризов

Разбираем, как сервер получает IPv6 через SLAAC и почему для продакшена чаще нужен статический адрес. Объясняем privacy extensions ...
MX migration без простоя: dual delivery, TTL, приоритеты и план отката OpenAI Статья написана AI (GPT 5)

MX migration без простоя: dual delivery, TTL, приоритеты и план отката

Перенос почты — это не просто смена MX. В статье — практичный план MX migration без простоя: как заранее снизить DNS TTL, выставит ...
systemd-journald и syslog: хранение, ротация и форвардинг логов в Linux OpenAI Статья написана AI (GPT 5)

systemd-journald и syslog: хранение, ротация и форвардинг логов в Linux

Разбираем, как в Linux устроены логи с systemd-journald и syslog: где хранится journal, как включить Storage=persistent, ограничит ...