Swap, overcommit, OOM killer — что делать когда RAM не хватает
Если у вас 16 GB RAM, и одна программа хочет 20 GB — что делает Linux? Не падает. Либо:
- Использует swap — неактивные страницы пишутся на диск, освобождая RAM.
- Включает OOM killer — убивает один из процессов, чтобы освободить память для остальных.
В этом уроке разберём три связанные концепции: swap (диск как продолжение RAM), overcommit (Linux позволяет выделять больше памяти, чем физически есть), и OOM killer (как kernel решает, кого убить при нехватке памяти).
Для junior data engineer это критично знать. Когда ваш Spark executor неожиданно убивается, когда Postgres backend исчезает с logs «killed by signal 9», когда сервер тормозит после многочасовой работы — ответы во взаимодействии RAM, swap и OOM killer.
Что такое swap
Swap — область на диске (раздел или файл), куда kernel может «выгружать» неактивные страницы памяти. Когда нужно освободить RAM, kernel выбирает страницы (например, давно не используемые) и пишет их в swap. Когда процесс обращается к swapped странице — major page fault, страница загружается обратно.
# Текущая ситуация со swap:
free -h
# total used free shared buff/cache available
# Mem: 62Gi 45Gi 2Gi 3Gi 14Gi 11Gi
# Swap: 8Gi 1Gi 7Gi
# Лучше -- swapon:
swapon --show
# NAME TYPE SIZE USED PRIO
# /dev/sda5 partition 8G 1G -2
# /swapfile file 2G 0 -3
# Сколько свопа использует каждый процесс:
for pid in $(ls /proc/ | grep -E '^[0-9]+$'); do
swap=$(awk '/VmSwap/ {print $2}' /proc/$pid/status 2>/dev/null)
if [ -n "$swap" ] && [ "$swap" -gt 1000 ]; then
echo "$pid $(cat /proc/$pid/comm 2>/dev/null) ${swap} KB"
fi
done | sort -k3 -n -r | head -10
# Покажет топ-процессов по swap usage
Anonymous vs file-backed — что попадает в swap
В swap идут anonymous pages — те, что не имеют backing file. Это:
- Heap (malloc-аллокации)
- Stack
- .bss (uninitialized globals)
- mmap anonymous (без file fd)
File-backed страницы (загруженный код, mmap файла) не идут в swap. Они «свопаются» в свой файл — если страница clean, её можно просто отбросить, при следующем обращении загрузим из файла. Если dirty — запись в file (обычный write-back).
Важный момент: на нагруженном Linux может казаться, что почти вся RAM занята (free -h показывает мало free), но это часто page cache — кэш файлов. Эта память «бесплатная» — может быть отброшена под потребности процесса. Смотрите колонку available — это сколько реально может быть выделено новой allocation без swap.
free -h
# total used free shared buff/cache available
# Mem: 62Gi 45Gi 2Gi 3Gi 14Gi 11Gi
# 'used' включает page cache, поэтому free низкое
# 'available' -- 11 GiB реально доступно для новых программ
swappiness — настройка агрессивности swap
vm.swappiness — параметр от 0 до 100, контролирующий, насколько активно kernel свопает anonymous страницы vs evict page cache.
# Текущая swappiness:
sysctl vm.swappiness
# 60 (default)
# Уменьшить (для production-серверов с БД):
sudo sysctl vm.swappiness=10
# или persistent:
echo 'vm.swappiness=10' | sudo tee -a /etc/sysctl.conf
# Для отдельных сценариев:
# - Database server: swappiness 1-10 (хотим, чтобы БД оставалась в RAM)
# - General workstation: swappiness 60 (default)
# - System с медленным диском: swappiness 5-10
# - Server с быстрым NVMe: можно 30-60
Overcommit — Linux может «обещать» больше
Linux позволяет процессам выделять больше памяти, чем физически есть (RAM + swap). Это называется overcommit. Идея: программы часто запрашивают больше, чем реально используют (sparse structures, лень malloc).
# Текущий overcommit mode:
sysctl vm.overcommit_memory
# 0 -- default heuristic
sysctl vm.overcommit_ratio
# 50 -- при mode=2 разрешено RAM*50% + swap
# Сделать strict (для production -- предсказуемее):
sudo sysctl vm.overcommit_memory=2
sudo sysctl vm.overcommit_ratio=100 # разрешить RAM*100% + swap
# Тогда malloc будет возвращать NULL, если запрос превышает limit
# Программы должны обрабатывать NULL -- но многие не обрабатывают -> segfault
Когда какой mode:
- mode=0 (default) — для general-purpose систем, работает в 99% случаев.
- mode=1 — для специфических workloads (Redis recommends
overcommit_memory=1чтобы избежатьfork()failures на full memory). - mode=2 — для production баз данных, систем с критичной предсказуемостью.
OOM killer — кто умрёт первым
Когда память закончилась, kernel запускает OOM killer (Out of Memory killer). Он выбирает процесс и убивает его с SIGKILL, освобождая память.
# Свой oom_score:
cat /proc/self/oom_score
# Score для всех больших процессов:
for pid in $(ls /proc/ | grep -E '^[0-9]+$' | head -50); do
score=$(cat /proc/$pid/oom_score 2>/dev/null)
if [ -n "$score" ] && [ "$score" -gt 100 ]; then
echo "$score $(cat /proc/$pid/comm 2>/dev/null) (PID $pid)"
fi
done | sort -rn | head -10
# Топ-10 кандидатов на OOM kill
# Защитить процесс от OOM:
echo -1000 | sudo tee /proc/PID/oom_score_adj
# -1000 = никогда не убивать (kernel сам уже не запустится без RAM, поэтому осторожно)
# Сделать процесс жертвой:
echo 1000 | sudo tee /proc/PID/oom_score_adj
# Будет убит первым при OOM
# Проверить, был ли OOM kill недавно:
sudo dmesg | grep -i 'killed process' | tail -10
# [12345.678] Out of memory: Killed process 4321 (postgres) total-vm:... rss:... ...
Heuristic OOM killer:
OOM killer прioritizes:
- Процессы с большим RSS — освободят больше памяти.
- Процессы с высоким oom_score_adj — пользователь явно пометил.
- Дочерние процессы — меньше «системного» вклада.
- Не root процессы — предпочитает не убивать system services.
Kernel НЕ убивает (обычно):
- init (PID 1)
- kthreadd и его потомков
- Процессы с oom_score_adj = -1000
Когда swap полезен и когда нет
Swap полезен:
- На desktop — даёт возможность поработать, когда память кончилась. Лучше медленно, чем краш.
- Для редко используемых сервисов на сервере — они уйдут в swap, активные останутся в RAM.
- Для hibernation (suspend to disk) — нужен swap чтобы записать RAM.
Swap вреден:
- На latency-critical серверах — swap I/O вызывает spike’и latency.
- На сервере с быстрой OOM-стратегией — лучше пусть kernel убьёт зомби-job чем все будут swapping.
- На облачных VMs с медленным диском — swap I/O может быть катастрофой.
# Полностью отключить swap:
sudo swapoff -a
# Сразу освобождает swap, выгружает данные обратно в RAM
# Persistent -- закомментируйте swap entries в /etc/fstab
# Включить обратно:
sudo swapon -a
Реальный пример: OOM kill Postgres
Production-сценарий: у вас 32 GB RAM, Postgres настроен с shared_buffers 24 GB, и запущен Spark с executor 8 GB. Под нагрузкой Spark пытается выделить дополнительные 4 GB:
Total used: 24 (Postgres) + 8 (Spark) + 2 (system) + 4 (новая) = 38 GB > 32 GB
Что произойдёт:
- Linux попытается освободить кеш — but Postgres uses shared_buffers, не cache.
- Попытается swap — но swappiness=10, медленно. Latency spike.
- Память всё равно не хватает. OOM killer запускается.
- Postgres имеет высокий RSS (~24 GB) — oom_score высокий. Postgres убит SIGKILL.
dmesg | grep oom покажет:
[12345] Out of memory: Killed process 4321 (postgres) total-vm:25600000kB
Решение:
- Capacity planning: не allocate Postgres shared_buffers больше чем 75% RAM. Оставлять buffer для другого.
- oom_score_adj для critical processes:
echo -500 > /proc/postgres-pid/oom_score_adj— защищаем Postgres. - cgroups limits: Spark в cgroup с CPU+Memory limits, чтобы не превысил allocation.
- Monitoring: alert на
free memory < 10%, реагировать ДО OOM.
Попробуй сам
# 1. Текущая ситуация:
free -h
# Колонка 'available' -- реальный буфер
# Колонка 'used' включает page cache
swapon --show
# Список swap areas
# 2. Сколько swap использует каждый процесс:
ps -eo pid,vsz,rss,comm --sort -rss | head -10
# 3. Триггерим swap (НЕ В PRODUCTION):
# Заполним RAM
stress-ng --vm 4 --vm-bytes 80% --timeout 30s &
# В другом окне:
vmstat 1
# Колонки si (swap in) и so (swap out) -- если активны, swapping происходит
# Колонка 'wa' -- iowait, тоже растёт при swap
# 4. oom_score процессов:
for pid in $(pgrep -f 'firefox|chrome|code' | head -5); do
echo "$(cat /proc/$pid/oom_score 2>/dev/null) $(cat /proc/$pid/comm)"
done | sort -rn
# 5. Защитить процесс от OOM:
# Например, ваш main shell:
sudo bash -c "echo -500 > /proc/$$/oom_score_adj"
cat /proc/$$/oom_score_adj
# -500 -- очень низкий, защищён
# 6. Посмотреть OOM events:
sudo dmesg | grep -iE 'oom|killed process' | tail -20
# 7. Симулировать low-memory scenario:
# (НЕ на production!)
echo 1 | sudo tee /proc/sys/vm/drop_caches
# Сбрасывает page cache. used уменьшится, but доступная память увеличится
# Полезно перед benchmarks