Перейти до змісту
Пошта Кабінет Обрати тариф

Swap і OOM Killer на сервері: скільки swap потрібно і чому гине база

Swap і OOM Killer на сервері: скільки swap потрібно і чому гине база

Серверу з 1-2 ГБ оперативної пам’яті досить swap на 1-2 ГБ, з 4-8 ГБ на 2-4 ГБ, а vm.swappiness для сервера з базою даних варто знизити з 60 до 10. Swap не лікує нестачу пам’яті, він лише дає час. Коли пам’ять закінчується, OOM Killer вбиває не процес, який її з’їв, а найбільший: на нашому стенді PHP-запит забрав зайві 150 МБ, а ядро вбило MariaDB (212 МБ), бо сам воркер PHP важив лише 108 МБ.

Ця стаття про VPS або власний сервер, де пам’яттю керуєте ви. На віртуальному хостингу swap і OOM Killer налаштовує провайдер, а для кожного акаунта діють власні ліміти пам’яті, тож замість убитої бази ви побачите помилку 508 Resource Limit Is Reached або повідомлення Allowed memory size exhausted.

Як ми перевіряли

10 листопада 2026 року на тестовому стенді: віртуальна машина з ядром Linux 6.18 і 7,8 ГБ RAM без swap. Невеликий VPS ми змоделювали за допомогою memory cgroup з лімітом 320 МБ, у якій працювали MariaDB 10.11.14 (buffer pool 128 МБ, таблиця на 156 МБ), PHP-FPM 8.3.6 (4 воркери, WordPress 6.4.3) і nginx 1.24. Важкий запит моделював PHP-скрипт, що поступово забирає 150 МБ. Кожні 0,5 с ми запитували головну сторінку, а події OOM брали з dmesg і лічильника oom_kill у cgroup. Swap на спільній машині ми не вмикали: команди перевірено на синтаксис, вплив swap на швидкість не вимірювали, zram описано за документацією. Документацію ядра, systemd і Red Hat перевірено того ж дня. Це поведінка тестового стенда, а не дані з серверів RichHost.

Чому OOM Killer вбиває базу, а не винуватця

Після прогріву MariaDB займала 217 МБ резидентної пам’яті (RSS), кожен воркер WordPress від 38 до 46 МБ. Потім один запит почав виділяти пам’ять шматками по 1 МБ, як це робить важкий експорт чи обробка зображень. Ось що записало ядро (вивід скорочено):

[ 6004.066291] php-fpm8.3 invoked oom-killer: gfp_mask=0xcc0(GFP_KERNEL), order=0, oom_score_adj=0
[ 6004.066411] memory: usage 327680kB, limit 327680kB, failcnt 18064
[  pid  ]   uid  tgid total_vm      rss rss_anon rss_file rss_shmem pgtables_bytes swapents oom_score_adj name
[   4307]    33  4307    94991    27764    20764     2110      4890   507904        0             0 php-fpm8.3
[   6440]     0  6440   342132    54366    48160     6206         0   622592        0             0 mariadbd
Memory cgroup out of memory: Killed process 6440 (mariadbd) total-vm:1368528kB, anon-rss:192640kB, file-rss:24824kB, shmem-rss:0kB, UID:0 pgtables:608kB oom_score_adj:0

Пам’ять попросив воркер PHP (pid 4307), а вбито MariaDB. Ядро не шукає винного. Для кожного процесу воно рахує бал: резидентна пам’ять, сторінки у swap і таблиці сторінок, плюс поправка oom_score_adj. Гине процес з найбільшим балом. У стовпці rss значення в сторінках по 4 КБ: 54 366 сторінок бази це 212 МБ, 27 764 сторінки воркера це 108 МБ. Сам PHP-запит після цього спокійно завершився з кодом 200. Ми повторили тест двічі, і обидва рази загинула база.

Із того самого звіту видно, що OOM Killer справді останній крок: перед ним ядро 18 064 рази впиралося в ліміт і витіснило все, що могло. Файловий кеш скоротився зі 159 МБ до 25 МБ, лишилася тільки спільна пам’ять OPcache.

Що побачили відвідувачі: з 60 запитів до головної сторінки один отримав 500 «Error establishing a database connection». На стенді наглядач mysqld_safe підняв базу в ту саму секунду. У systemd-юніті MariaDB в Ubuntu 24.04 стоять Restart=on-abnormal і RestartSec=5s, тож на реальному сервері вікно щонайменше п’ять секунд. Далі база працює з холодним кешем: після перезапуску вона займала 134 МБ замість 217 МБ, і перші запити йшли з диска. Якщо в момент вбивства йшов запис, InnoDB ще проходить відновлення після збою.

Як знайти сліди OOM Killer на своєму сервері

dmesg -T | grep -iE 'killed process|out of memory'
journalctl -k --since "2 days ago" | grep -i oom
grep -i 'killed process' /var/log/kern.log
systemctl status mariadb

Рядок «Out of memory: Killed process» означає, що закінчилася пам’ять усього сервера. «Memory cgroup out of memory» означає, що спрацював ліміт cgroup: так буває з лімітами служб systemd і в контейнерах. Логіка вибору жертви в обох випадках однакова. Якщо база «сама» перезапускається вночі, а в її журналі помилок причини немає, першим перевірте dmesg. Повний порядок дій при збоях є в статті про діагностику проблем хостингу.

Як захистити MariaDB: oom_score_adj і systemd

Бал зсуває параметр oom_score_adj від -1000 до 1000. Ядро додає до балу частку всієї пам’яті: +500 означає плюс половину RAM (у cgroup плюс половину ліміту), -600 означає мінус 60 %, а -1000 повністю прибирає процес зі списку кандидатів. Поточні значення:

cat /proc/$(pidof mariadbd)/oom_score
cat /proc/$(pidof mariadbd)/oom_score_adj
choom -p $(pidof mariadbd)

Порівнюйте oom_score між процесами, а не з нулем: на сучасних ядрах процес майже без пам’яті має близько 666. На стенді MariaDB мала 684, воркери WordPress 669-670.

Пакет MariaDB в Ubuntu 24.04 уже містить підказку: у файлі /lib/systemd/system/mariadb.service є закоментований рядок OOMScoreAdjust=-600 і коментар розробників про те, що ядро охоче вбиває mariadbd, бо він великий. Тобто за замовчуванням захисту немає. Увімкніть його через override:

sudo systemctl edit mariadb
[Service]
OOMScoreAdjust=-600

sudo systemctl edit php8.3-fpm
[Service]
OOMScoreAdjust=500

sudo systemctl restart mariadb php8.3-fpm

Для MySQL служба зазвичай називається mysql. Значення -1000 робить базу недоторканною, і тоді за витік пам’яті в самій базі заплатять усі інші процеси, зокрема sshd, тож можна втратити доступ до сервера. Тому ми радимо для бази від -600 до -900, а для PHP-FPM позитивне значення: менеджер пулу просто запустить новий воркер замість убитого.

На стенді знизити oom_score_adj не вдалося: у контейнері немає capability CAP_SYS_RESOURCE, і запис -1000 повернув «Permission denied» (на звичайному VPS під root і через systemd це працює). Тому ми перевірили зворотний бік: дали процесам PHP-FPM +500. Той самий запит на 150 МБ:

Memory cgroup out of memory: Killed process 4307 (php-fpm8.3) total-vm:371772kB, anon-rss:80732kB, file-rss:8440kB, shmem-rss:19560kB, UID:33 pgtables:480kB oom_score_adj:500
WARNING: [pool cw18] child 4307 exited on signal 9 (SIGKILL) after 264.781050 seconds from start
NOTICE: [pool cw18] child 8588 started

Цього разу важкий запит отримав 502, у журналі nginx з’явився рядок recv() failed (104: Connection reset by peer), а база з прогрітим кешем навіть не помітила події: усі 60 запитів до головної сторінки отримали 200. Арифметика проста: до 108 МБ воркера ядро додало половину ліміту (160 МБ), і 268 МБ переважили 212 МБ бази.

Результати тесту: OOM killer вбиває MariaDB без налаштувань, воркер PHP при oom_score_adj +500 і нікого при memory_limit 128M
Один і той самий запит на 150 МБ у трьох конфігураціях тестового стенда

Перша лінія захисту: memory_limit і pm.max_children

Найкраще, щоб до OOM Killer не доходило взагалі. Той самий запит з memory_limit = 128M у пулі PHP-FPM завершився звичайною фатальною помилкою PHP за 0,35 с:

PHP Fatal error:  Allowed memory size of 134217728 bytes exhausted (tried to allocate 2097152 bytes) in /srv/cw18/www/hog.php on line 4

Один запит з кодом 500, жодного вбитого процесу, лічильник oom_kill не змінився. Тому ставте memory_limit стільки, скільки справді потрібно сайту (для WordPress зазвичай 128-256 МБ), а не -1 чи 1024M про запас.

Друга половина рівняння: скільки воркерів працює одночасно. pm.max_children множить пам’ять одного воркера, і саме тут найчастіше помиляються, бо рахують RSS з top.

RSS чи PSS: скільки насправді важить воркер PHP-FPM

smem -P php-fpm -c "pid command uss pss rss"
  PID Command                         USS      PSS      RSS
 4307 php-fpm: pool cw18             3364     9948    38016
 4309 php-fpm: pool cw18             4828    12067    41632
 4306 php-fpm: pool cw18             5856    13039    42596
 4308 php-fpm: pool cw18             9116    16456    46128

RSS кожного воркера від 38 до 46 МБ, разом 164 МБ. Але більшість цих сторінок спільні: кеш OPcache і бібліотеки, які кожен воркер записує на свій рахунок. PSS ділить спільні сторінки порівну між процесами, і сума PSS (50 МБ) показує реальне споживання пулу. USS (3-9 МБ) звільниться, якщо завершити конкретний воркер. Якщо smem не встановлено, PSS видно у файлі /proc/PID/smaps_rollup.

Практичне правило: pm.max_children дорівнює пам’яті, яку ви готові віддати PHP, поділеній на середній PSS воркера в пікову годину з запасом на важкі запити. Пам’ять для PHP: RAM мінус buffer pool бази, мінус інші служби і мінус 15-20 % на файловий кеш. Розмір buffer pool підбирайте за обсягом даних, про це в статті про оптимізацію баз даних. Кожна версія PHP має власний пул зі своїми налаштуваннями, тож після зміни версії PHP перевірте pm.max_children у новому пулі.

Скільки swap потрібно серверу

Swap на вебсервері не робоча пам’ять, а буфер. Туди ядро виносить сторінки, яких давно ніхто не торкався, і він дає кілька хвилин, щоб помітити витік до приходу OOM Killer. Якщо сервер живе у swap постійно, сайт гальмує, і рішення одне: більше RAM або менше процесів.

RAM сервера Red Hat (RHEL 9) Наша порада для VPS із сайтами
до 2 ГБ 2 × RAM 1-2 ГБ
2-8 ГБ дорівнює RAM 2 ГБ
8-64 ГБ щонайменше 4 ГБ 2-4 ГБ
понад 64 ГБ залежно від навантаження, щонайменше 4 ГБ 4 ГБ

Таблиця Red Hat розрахована на будь-які системи, тому для сервера з базою ми радимо менше: великий swap лише відкладає OOM Killer, а сервер тим часом повільно перебирає сторінки з диска. Гібернація на сервері не потрібна, тож її колонку ми не враховуємо.

Як створити swap-файл

sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
swapon --show
free -h

На стенді ми виконали перші три команди для файлу на 512 МБ (mkswap відповів Setting up swapspace version 1, size = 512 MiB) і свідомо не запускали swapon: машина спільна для кількох тестів, а swap діє на весь сервер. Тому вимірів швидкості зі swap у статті немає, лише перевірений синтаксис. На Btrfs і деяких старих системах fallocate для swap не підходить, тоді файл створюють командою dd if=/dev/zero of=/swapfile bs=1M count=2048. Якщо ви тільки готуєте сервер, swap зручно додати разом з іншими кроками з чекліста налаштування VPS на Ubuntu 24.04.

swappiness: що означає це число

За документацією ядра vm.swappiness задає відносну ціну swap порівняно зі скиданням файлового кешу: від 0 до 200, за замовчуванням 60. На машині стенда cat /proc/sys/vm/swappiness теж показав 60.

  • 0: ядро не починає свопити, доки вільних і файлових сторінок не стане менше за верхню межу. Swap не вимкнено, він стає останнім резервом.
  • 10: наша порада для сервера з базою. Ядро спершу звільняє файловий кеш, а пам’ять MariaDB і PHP лишає в RAM.
  • 60: значення за замовчуванням, swap дорожчий за кеш, але використовується охоче.
  • 100: swap і файловий кеш однаково дорогі.
  • понад 100: для swap у пам’яті (zram, zswap), документація наводить приклад 133.
cat /proc/sys/vm/swappiness
sudo sysctl vm.swappiness=10
echo 'vm.swappiness=10' | sudo tee /etc/sysctl.d/99-swappiness.conf

Команда sysctl діє до перезавантаження, файл у /etc/sysctl.d закріплює значення. На стенді ми їх не виконували, бо це глобальне налаштування спільної машини.

zram замість swap на диску

zram створює в оперативній пам’яті блоковий пристрій зі стисненням, і swap на ньому працює без звернень до диска. Документація ядра розраховує на стиснення приблизно 2:1 і вважає zram, більший за подвійний обсяг RAM, марним. Для VPS з 1-2 ГБ це розумний варіант: в Ubuntu його вмикають пакетом zram-tools або systemd-zram-generator, а swappiness для такого swap ставлять вище 100. Ми zram не тестували, бо в ядрі стенда немає цього модуля.

Пастки підрахунку пам’яті

Вивід free -h на машині стенда:

               total        used        free      shared  buff/cache   available
Mem:           7.8Gi       2.4Gi       1.8Gi       112Mi       4.2Gi       5.4Gi
Swap:             0B          0B          0B

Колонка free (1,8 ГБ) показує пам’ять, яка зовсім нічим не зайнята. Колонка available (5,4 ГБ) це оцінка ядра, скільки можна віддати новим процесам без swap з урахуванням кешу, який можна скинути. Орієнтуйтеся на available: мале free при великому buff/cache означає нормальну роботу Linux, а не нестачу пам’яті.

Ми перевірили це окремо: у cgroup з лімітом 128 МБ двічі прочитали файл на 600 МБ. Кеш заповнив увесь ліміт (127 МБ), ядро 5113 разів упиралося в межу і витісняло старі сторінки, але лічильник oom_kill лишився нульовим. Файловий кеш не викликає OOM Killer.

Третя пастка описана вище: сума RSS з top або ps для пулу PHP-FPM завищує споживання втричі (164 МБ проти 50 МБ PSS на стенді).

Симптом, команда, рішення

Симптом Що показує команда Що робити
База перезапускається сама, у її журналі причини немає dmesg -T | grep -i 'killed process' показує mariadbd OOMScoreAdjust для бази, менші pm.max_children і memory_limit
Періодичні 502 на важких сторінках у dmesg убито php-fpm, у журналі PHP-FPM «exited on signal 9» Знайдіть важкий скрипт, обмежте memory_limit, додайте RAM
Помилка 500 «Allowed memory size exhausted» журнал помилок PHP Оптимізуйте скрипт або помірно підніміть memory_limit
Сайт гальмує, диск постійно зайнятий vmstat 1: стовпці si і so постійно більші за нуль Бракує RAM: менше воркерів, менший buffer pool або VPS з більшою пам’яттю
free показує лише 100-200 МБ вільних available і buff/cache великі Нічого не робіть, це кеш
Swap зайнятий, але сайт швидкий vmstat 1: si і so нульові Норма: там лежать неактивні сторінки
Swap немає, OOM на кожному піку swapon --show нічого не виводить Swap-файл на 1-2 ГБ і swappiness 10

Якщо вам не хочеться самим стежити за swap, OOM Killer і пулами PHP-FPM, сайт на WordPress чи іншій CMS можна розмістити на віртуальному хостингу: на тарифах RichHost сервер, PHP і бази даних налаштовує команда хостингу, а вам лишаються лише ліміти свого акаунта.

Часті питання

Чи можна зовсім вимкнути swap на сервері?

Можна, але тоді ядру нікуди винести навіть неактивні сторінки, і будь-який пік закінчується вбивством процесу, як у нашому тесті без swap. Невеликий swap з низьким swappiness обходиться дешевше за аварійний перезапуск бази.

Скільки оперативної пам’яті потрібно VPS під WordPress?

Порахуйте: buffer pool бази плюс PSS воркера, помножений на pm.max_children, плюс орієнтовно 300-500 МБ на систему і файловий кеш. На стенді чистий WordPress мав PSS 10-16 МБ на воркер, сайти з важкими плагінами важать більше, тому міряйте свій. Якщо пам’яті бракує постійно, порівняйте, скільки коштує VPS з більшим обсягом RAM.

Чому OOM Killer не вбив процес, який з’їв пам’ять?

Він не шукає винного, а вибирає процес з найбільшим балом: резидентна пам’ять, swap і таблиці сторінок плюс oom_score_adj. Велика база майже завжди важча за один воркер PHP.

Чи буває OOM Killer на віртуальному хостингу?

Пам’яттю сервера там керує провайдер, а сайт упирається в ліміти свого акаунта: memory_limit PHP, кількість процесів, помилку 508. Налаштовувати swap, swappiness чи oom_score_adj на віртуальному хостингу не потрібно, та й неможливо.

Ще за темою

Усі статті
Налаштування Fail2Ban і CSF: jail для SSH, nginx і WordPress з тестом
Серверна безпека01.07.2026

Налаштування Fail2Ban і CSF: jail для SSH, nginx і WordPress з тестом

Як налаштувати Fail2Ban для SSH, nginx і WordPress, що він блокує насправді і шість пасток: самоблокування, IPv6, journald, Cloudflare і зміна шляху…

Аудит безпеки сайту за годину: чекліст із командами і перевіркою
Серверна безпека11.06.2026

Аудит безпеки сайту за годину: чекліст із командами і перевіркою

Як за годину перевірити безпеку сайту на хостингу і власного VPS: відкриті .git, .env і дампи бази, версії WordPress і плагінів, контрольні…

IOPS, затримка і пропускна здатність диска: що важливо для сайту і як виміряти
Продуктивність хостингу03.06.2026

IOPS, затримка і пропускна здатність диска: що важливо для сайту і як виміряти

Що означають IOPS, затримка і пропускна здатність диска, чому для WordPress і MySQL важливі 4K і fsync, а не МБ/с, і як…