Ubuntu 24.04: Memory ballooning surprises — set sane limits and stop swap storms (case #76)

Було корисно?

Все починається з «ВМ відчувається повільною». Через десять хвилин це стає «чому kswapd жере весь процесор», а за годину хтось пропонує «просто додайте swap», ніби це свята вода для пожежі.

Ubuntu 24.04 цілком здатна працювати стабільно під навантаженням пам’яті. Але якщо змішати ballooning, overcommit, cgroup-ліміти та оптимістичні налаштування за замовчуванням, можна згенерувати swap storm, який виглядає одночасно як проблема з CPU, диском і мережею. Це не магія. Це облік, і це виправити.

Що насправді означає «memory ballooning surprises»

Ballooning у теорії простий: гіпервізор забирає оперативну пам’ять у ВМ, яку «ніби не використовують», і віддає комусь іншому. На практиці це переговори з неповною інформацією.

Всередині гостя (тут Ubuntu 24.04) драйвер balloon «закріплює» сторінки так, що гість думає, ніби в нього менше доступної пам’яті. Якщо гостю пізніше потрібна ця пам’ять назад, гіпервізор може «здути» балон — якщо зможе. Це «якщо» і породжує збої.

Swap storm — це коли система проводить більше часу, переміщуючи сторінки між ОЗП і swap, ніж виконуючи корисну роботу. Класична картина:

  • тиск на ОЗП зростає → анонімні сторінки підміняються у swap
  • робоче навантаження торкається підмінених сторінок → вибухають major faults
  • очікування на введення/виведення зростає → латентність запитів стрибає
  • CPU витрачається на reclaim і page faults → машина «має CPU», але нічого не завершує

Тепер додайте сюрпризи ballooning:

  • «Доступна» пам’ять гостя швидко знижується (балон надувається), іноді швидше, ніж гість може чисто звільнити пам’ять.
  • Гість починає свопити, хоча на хості загалом ще є пам’ять — або хост теж під тиском, і обидва шари одночасно проводять reclaim.
  • Метрики виглядають суперечливо: гість показує свопінг; хост показує вільну пам’ять; латентність додатка виглядає як проблема диска; CPU зайнятий, але непродуктивний.

Ось моя думка: якщо вам потрібна передбачуваність, припиніть ставитися до пам’яті як до рекомендації. Встановіть жорсткі ліміти там, де потрібно, розрахуйте swap свідомо і визначте один шар, який першим займатиметься reclaim. Інакше ви отримаєте розподілену систему сторінкового пам’яті — і ніхто цього не просив.

Анекдот #1 (коротко, по темі): Swap — це як склад для коробок під час переїзду: корисно, доки вам зараз же не потрібен тостер.

Цікаві факти та історичний контекст (коротко, корисно, трохи нудно)

  1. Ballooning з’явився до ери «хмари». VMware популяризував ballooning на початку 2000-х, щоб підвищити коефіцієнти консолідації — поки робочі навантаження не стали спіковими й чутливими до латентності.
  2. Virtio-balloon — це кооперативний механізм. Він покладається на гостя, щоб віддати сторінки назад. Якщо гість вже бореться, ballooning може посилити біль, бо роботу виконує сам гість.
  3. Свопінг у Linux — це не «баг». Історично Linux використовував swap для збільшення page cache; сучасні ядра більш нюансовані, але сам факт активності swap не означає неодмінно помилку.
  4. cgroup v2 зробив контроль пам’яті суворішим і більш видимим. Тепер важче випадково обійти ліміти і легше бачити події — якщо ви на це дивитесь.
  5. PSI (Pressure Stall Information) — досить нова штука. Її злили приблизно в епоху Linux 4.20, щоб кількісно визначати «час, проведений у очікуванні ресурсів», роблячи «система гальмує» вимірюваним.
  6. systemd-oomd — це opinionated user-space killer. Він може діяти раніше за kernel OOM killer, щоб зберегти систему, що дивує тих, хто вважає, що OOM трапляється тільки «коли повністю закінчилась пам’ять».
  7. За замовчуванням overcommit різний у різних середовищах. Гіпервізори overcommit-ять; Linux може overcommit-ати; контейнери теж. Накладайте їх і отримаєте матрьошку оптимізму.
  8. Swap storms погіршилися з появою швидких дисків. NVMe робить swap «менш жахливим», що спокушає покладатися на нього; часто це просто переносить вузьке місце на CPU і варіацію латентності.

Швидкий план діагностики: знайти вузьке місце за хвилини

Це порядок дій, який виграє в production, бо відокремлює «ми під тиском пам’яті» від «ми заплутались». Робіть це спочатку в гості, потім на хості, якщо можете.

1) Підтвердити, що це саме тиск пам’яті, а не «велике використання RAM»

  • Перевірте швидкість swap-in і major faults (справжній біль).
  • Перевірте PSI memory (час прострочення).
  • Перевірте CPU на reclaim (kswapd і direct reclaim).

2) Визначити, де відбувається reclaim (гість? хост? обидва?)

  • У гості підтвердьте статус драйвера balloon і поточний розмір балона, якщо він видимий.
  • На хості перевірте ціль balloon для ВМ і активність swap на хості.

3) Визначити, яка площина керування фактично застосовує ліміти

  • cgroup v2: memory.max / memory.high для сервісів і контейнерів
  • systemd-oomd: активність і логи
  • Параметри пам’яті/ліміти в гіпервізорі / політика ballooning

4) Вирішити негайні пом’якшувальні заходи

  • Якщо латентність критично висока: зменшіть ціль balloon або додайте реальної ОЗП (не просто swap).
  • Якщо один сервіс жере ресурси: обмежте його через cgroups або перезапустіть з меншим heap/кількістю воркерів.
  • Якщо хост під тиском: зупиніть overcommit, мігруйте або зніміть навантаження — хостовий swap робить сумними всі гості.

Практичні завдання: команди, виводи та рішення (12+)

Це не «запустив і заспокоївся» команди. Кожна має свою інтерпретацію і рішення. Використовуйте їх під час інциденту і в постмортемі.

Завдання 1: Швидко перевірити загальні значення пам’яті і swap

cr0x@server:~$ free -h
               total        used        free      shared  buff/cache   available
Mem:            15Gi        13Gi       210Mi       220Mi       1.8Gi       820Mi
Swap:          8.0Gi       6.4Gi       1.6Gi

Що це означає: «available» менше 1 GiB плюс активне використання swap вказує на реальний тиск, а не просто кеш.

Рішення: Якщо латентність висока і використання swap зростає, переходьте до Завдань 2–4 негайно. Якщо swap високий, але стабільний і PSI низький — можливо, все гаразд.

Завдання 2: Виміряти швидкість swap-in/out і циклічність reclaim

cr0x@server:~$ vmstat 1 5
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 3  1 671088  180000  12000 860000 420  980  1800  2100 3200 8800 18 12 10 58  2
 2  1 672000  175000  12000 850000 300  760  1400  1800 3100 8200 16 11 12 59  2
 4  2 675000  165000  11000 845000 520 1200  2200  2400 3500 9300 20 14  8 56  2
 2  1 677000  160000  11000 840000 410  900  1700  2000 3200 8600 18 12 10 58  2
 3  2 679000  155000  10000 835000 600 1300  2400  2600 3600 9700 21 15  7 55  2

Що це означає: Ненульові si/so постійно вказують на активний свопінг. Високий wa означає, що IO wait домінує. Це профіль swap storm.

Рішення: Пом’якшіть шляхом зменшення тиску пам’яті (більше RAM / менше ballooning / обмежити грізні процеси). Налаштування swappiness не врятує під час шторму.

Завдання 3: Шукати major page faults (лічильник «я торкнувся підмінених сторінок»)

cr0x@server:~$ sar -B 1 3
Linux 6.8.0-xx-generic (server)  12/31/25  _x86_64_ (8 CPU)

12:01:11 AM  pgpgin/s pgpgout/s   fault/s  majflt/s  pgfree/s pgscank/s pgscand/s pgsteal/s   %vmeff
12:01:12 AM   1800.00   2400.00  52000.00   980.00  14000.00   9200.00     0.00   6100.00    66.30
12:01:13 AM   1600.00   2100.00  50500.00  1020.00  13200.00   8900.00     0.00   5800.00    65.10
12:01:14 AM   2000.00   2600.00  54000.00  1100.00  15000.00   9800.00     0.00   6400.00    65.30

Що це означає: majflt/s близько 1000 — катастрофа для більшості сервісів. Кожен major fault — це «очікування диска».

Рішення: Зупиніть кровотечу: зменшіть ballooning, вбийте/перезапустіть memory hog або масштабуйтесь. Інакше отримаєте каскадні таймаути і повтори.

Завдання 4: Прочитати PSI memory pressure (гостя — правдивий індикатор)

cr0x@server:~$ cat /proc/pressure/memory
some avg10=35.20 avg60=22.10 avg300=10.88 total=987654321
full avg10=12.90 avg60=7.40 avg300=3.20 total=123456789

Що це означає: «some» = задачі, які затримуються через тиск пам’яті; «full» = ніхто не може виконатись, бо пам’ять — вузьке місце. Підтримуване нетривіальне «full» — це пожежа.

Рішення: Якщо full avg10 вище ~1–2% для латентних систем, розглядайте це як інцидент у production. Зменшуйте тиск; не сперечайтесь із математикою.

Завдання 5: Перевірити, чи kswapd спалює CPU

cr0x@server:~$ top -b -n1 | head -n 15
top - 00:01:40 up 12 days,  3:22,  1 user,  load average: 8.22, 7.90, 6.40
Tasks: 243 total,   4 running, 239 sleeping,   0 stopped,   0 zombie
%Cpu(s): 21.3 us, 14.9 sy,  0.0 ni,  6.8 id, 56.0 wa,  0.0 hi,  1.0 si,  0.0 st
MiB Mem :  16384.0 total,   15870.0 used,    220.0 free,    180.0 buff/cache
MiB Swap:   8192.0 total,    6600.0 used,   1592.0 free.    820.0 avail Mem

  PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
  431 root      20   0       0      0      0 R  85.0   0.0 120:22.11 kswapd0
 2331 app       20   0 5020.0m 3900.0m  4200 S  60.0  23.8  88:10.22 java
 1102 postgres  20   0 2800.0m 1500.0m  9800 S  25.0   9.2  40:11.03 postgres

Що це означає: Високий IO wait плюс гарячий kswapd0 означає, що reclaim трясеться. CPU «зайнятий» побутовими завданнями.

Рішення: Не оптимізуйте додаток поки що. Виправте тиск пам’яті спочатку; інакше ви налаштовуєте плейлист тонучого корабля.

Завдання 6: Подивитись, що ядро думає про пам’ять і reclaim

cr0x@server:~$ egrep 'MemTotal|MemAvailable|SwapTotal|SwapFree|Active|Inactive|Dirty|Writeback|AnonPages|Mapped|SReclaimable' /proc/meminfo
MemTotal:       16384000 kB
MemAvailable:     780000 kB
SwapTotal:       8388604 kB
SwapFree:        1654320 kB
Active:          8920000 kB
Inactive:        5120000 kB
Dirty:            184000 kB
Writeback:          2400 kB
AnonPages:      11200000 kB
Mapped:           420000 kB
SReclaimable:     520000 kB

Що це означає: Великий показник AnonPages означає анонімну пам’ять (купи, не кеш). Саме звідси виникає тиск swap.

Рішення: Якщо це в основному анонімні сторінки, потрібні менше процесів, менші купи або більше ОЗП. Якщо це в основному кеш — можливо, можна звільнити чисто.

Завдання 7: Знайти найбільших споживачів пам’яті (RSS, не VIRT)

cr0x@server:~$ ps -eo pid,comm,rss,pmem --sort=-rss | head
 2331 java        3998200 24.3
 1102 postgres    1543000  9.4
 4120 node        1022000  6.2
 1887 redis-server  620000  3.8
 2750 python       410000  2.5
  987 systemd-journald 180000  1.1
 1450 nginx        90000  0.5
  812 snapd        82000  0.5
  701 multipathd   62000  0.3
  655 unattended-upgr 52000  0.3

Що це означає: RSS — це реальна resident-пам’ять. Один великий процес може дестабілізувати весь гість, коли ballooning зменшує запас.

Рішення: Якщо один процес домінує, обмежте його (cgroup), переконфігуруйте або перезапустіть свідомо — раніше, ніж система зробить це за вас.

Завдання 8: Перевірити cgroup v2 memory ліміти для systemd-сервісу

cr0x@server:~$ systemctl show myapp.service -p MemoryMax -p MemoryHigh -p OOMPolicy -p ManagedOOMMemoryPressure
MemoryMax=infinity
MemoryHigh=infinity
OOMPolicy=stop
ManagedOOMMemoryPressure=auto

Що це означає: Нема жорстких обмежень. Додаток може «з’їсти» гостя. У поєднанні з ballooning це російська рулетка.

Рішення: Для критичних хостів встановіть MemoryHigh (м’яке гальмування) і MemoryMax (жорстка межа) для передбачуваності.

Завдання 9: Перевірити фактичне memory.current і memory.events cgroup

cr0x@server:~$ CG=$(systemctl show -p ControlGroup --value myapp.service); echo $CG
/system.slice/myapp.service
cr0x@server:~$ cat /sys/fs/cgroup$CG/memory.current
4219031552
cr0x@server:~$ cat /sys/fs/cgroup$CG/memory.events
low 0
high 182
max 3
oom 1
oom_kill 1

Що це означає: Сервіс часто діставав memory.high і навіть досягав memory.max. Відбулося OOM kill всередині cgroup.

Рішення: Якщо high швидко зростає — налаштуйте сервіс (heap/воркери) або підніміть ліміти. Якщо max/oom_kill збільшується — ви недостатньо забезпечені або неправильно налаштовані.

Завдання 10: Подивитись використання swap по процесах (smaps rollup)

cr0x@server:~$ sudo awk '/^Swap:/ {sum+=$2} END {print sum " kB"}' /proc/2331/smaps_rollup
1540000 kB

Що це означає: Цей процес має ~1.5 GiB у swap. Хоча він може «бігти», при торканні замінених регіонів він, ймовірно, затримується.

Рішення: Якщо це латентний сервіс, перезапуск (після зменшення пам’яті) може бути найменш поганим варіантом. Також виправте upstream причину (ballooning/ліміти).

Завдання 11: Перевірити swappiness і інші knobs для reclaim

cr0x@server:~$ sysctl vm.swappiness vm.vfs_cache_pressure vm.watermark_scale_factor
vm.swappiness = 60
vm.vfs_cache_pressure = 100
vm.watermark_scale_factor = 10

Що це означає: Swappiness 60 — типовий дефолт. Це не «помилка», але й не оптимальне для ballooned ВМ з латентними додатками.

Рішення: Після інциденту розгляньте vm.swappiness=1030 для багатьох серверних навантажень, але тільки після виправлення розмірів і лімітів.

Завдання 12: Підтвердити активні swap-пристрої і їх пріоритет

cr0x@server:~$ swapon --show --bytes
NAME        TYPE  SIZE        USED       PRIO
/swapfile   file  8589934592  7088373760   -2

Що це означає: Один swapfile, низький пріоритет (нормально). Якщо є декілька бекендів swap, пріоритет вирішує, куди йдуть сторінки першочергово.

Рішення: Якщо ви додаєте zram, дайте йому вищий пріоритет за дисковим swap, щоб використовувати компресію перед повільним IO.

Завдання 13: Спостерігати латентність диска під час свопінгу

cr0x@server:~$ iostat -x 1 3
Linux 6.8.0-xx-generic (server)  12/31/25  _x86_64_ (8 CPU)

avg-cpu:  %user   %nice %system %iowait  %steal   %idle
          18.20    0.00   12.10   56.30    2.10    11.30

Device            r/s     rkB/s   rrqm/s  %rrqm r_await rareq-sz     w/s     wkB/s   wrqm/s  %wrqm w_await wareq-sz  aqu-sz  %util
nvme0n1         420.0   16800.0     2.0   0.47    9.80    40.0   520.0   22000.0     3.0   0.57   18.50    42.3   12.10  98.00

Що це означає: %util ~98% і високі await означають, що диск насичений. Навіть NVMe під час свопінгу може стати вузьким місцем.

Рішення: Якщо swap — це навантаження, додавання «швидшого диска» не вирішить проблему. Спочатку зменшіть свопінг.

Завдання 14: Перевірити наявність драйвера balloon у гості (індикатор хостового ballooning)

cr0x@server:~$ lsmod | grep -E 'virtio_balloon|vmw_balloon|xen_balloon'
virtio_balloon         24576  0
virtio                   16384  2 virtio_balloon,virtio_net

Що це означає: Драйвер virtio balloon завантажений, отже ballooning може бути активним залежно від політики гіпервізора.

Рішення: Якщо ви не можете терпіти динамічну пам’ять, погодьтеся з командою гіпервізора про відключення або обмеження ballooning для цього класу ВМ.

Завдання 15: Подивитись недавні події OOM і дії oomd

cr0x@server:~$ journalctl -k -g -i 'oom|out of memory' --since '1 hour ago' | tail -n 20
Dec 31 00:12:02 server kernel: Out of memory: Killed process 4120 (node) total-vm:2103456kB, anon-rss:980000kB, file-rss:12000kB, shmem-rss:0kB, UID:1001 pgtables:3400kB oom_score_adj:0
Dec 31 00:12:02 server kernel: oom_reaper: reaped process 4120 (node), now anon-rss:0kB, file-rss:0kB, shmem-rss:0kB
cr0x@server:~$ journalctl -u systemd-oomd --since '1 hour ago' | tail -n 20
Dec 31 00:11:58 server systemd-oomd[812]: Killed /system.slice/myapp.service due to memory pressure for /system.slice.

Що це означає: Можуть вбити як kernel OOM killer, так і systemd-oomd. Якщо oomd вбив першим — «OOM подія» може статись до того, як RAM впаде до нуля.

Рішення: Вирішіть, яку політику ви хочете: проактивний oomd (зазвичай корисний для збереження відповідання хоста) або «хай kernel сам вирішує» (часто голосніше). Налаштуйте свідомо.

Основні причини: ballooning, overcommit, reclaim і чому виникають swap storm

Ballooning — ставка на стабільність навантаження

Ballooning працює найкраще, коли:

  • гості мають передбачувану «ледачу» пам’ять
  • робочі навантаження не чутливі до латентності
  • хост має достатньо вільного, щоб швидко «здути» балон

Ballooning працює найгірше, коли:

  • використання пам’яті різко зростає (прогрів кешу, деплої, спайки трафіку)
  • хост теж під тиском, тож дефляція балона не встигає
  • гість налаштований з щедрим swap, що маскує ранні сигнали

Подвійний reclaim: гість свопить, хост свопить — програють усі

Найнеприємніші інциденти включають reclaim на двох рівнях. Гість свопить, бо його «фізична» пам’ять зменшилась через balloon. Хост свопить, бо overcommit зайшов занадто далеко. Тепер кожен page-in гостя може викликати host page-in. Латентність стає матрьошкою.

Overcommit пам’яті — рішення політики, а не успадкована опція

Є три різні ідеї overcommit, які плутають:

  • Overcommit гіпервізора: виділити більше vRAM, ніж є фізичного RAM на хості, сподіваючись, що не всі ВМ пікові одночасно.
  • Overcommit в Linux virtual memory: дозволяти процесам алокувати більше віртуальної пам’яті, ніж RAM+swap, за евристикою.
  • Overcommit для контейнерної щільності: планування подів/контейнерів з request менше, ніж limits, ставка на середнє.

Кожен із них може бути розумним. Накладіть усі три — і ви будуєте машину катастроф для пікових навантажень.

Swap storm зазвичай тригериться однією з чотирьох моделей

  1. Ціль balloon змінюється занадто агресивно (подія тиску на хості, автоматичне «right-sizing» або людина, яка хоче бути кмітливою).
  2. Сервіс на heap росте до GC-thrash, потім починає інтенсивно фолтити, коли сторінки в swap.
  3. Файловий кеш не проблема, але ви налаштовуєте кеш-ключі і виснажуєте анонімну пам’ять.
  4. Уповільнення IO змушує reclaim відставати (шумний сусід зі сховищем, деградований RAID, snapshot-шторм), підштовхуючи систему у прямі reclaim-стали.

Є також парафразована ідея, яку варто тримати при проектуванні лімітів: paraphrased idea — Gene Kim наголошував, що надійність походить від зменшення варіації й робить роботу передбачуваною, а не від героїчних зусиль.

Встановіть розумні ліміти: ВМ, контейнери та хост

«Розумні ліміти» означають, що ви вирішуєте, який шар має право сказати «ні» першим і як саме. Моє уподобання для production-систем, на які люди кричать:

  • Жорстке виділення для критичних ВМ (або принаймні суворі мінімальні межі balloon), щоб гості мали передбачувану ОЗП.
  • cgroup-ліміти на сервіс, щоб один runaway-процес не перетворив ОС на тест продуктивності swap.
  • oomd налаштований свідомо, щоб вбивати потрібне раніше, а не kernel влучав у випадкові цілі запізно.

Рівень ВМ: не дозволяйте «динамічному» означати «сюрпризне»

На багатьох гіпервізорах ви можете встановити:

  • мінімальна пам’ять (balloon floor)
  • максимальна пам’ять (cap)
  • shares/priority (хто отримує пам’ять першим)

Правила великого пальця, що добре працюють з часом:

  • Встановіть floor принаймні на рівні спостережуваного steady-state RSS + запас безпеки, а не «половина max тому що так здається».
  • Для баз даних і JVM floor повинен бути консервативним; reclaim робочого набору їх болісно вдарить відразу.
  • Вимкніть ballooning для дуже чутливих до латентності або пікових навантажень, якщо тільки ви не довели його безпеку під навантаженням.

Рівень контейнерів: memory.max — ваш ремінь безпеки

Якщо ви запускаєте контейнери під systemd (або навіть просто сервіси), cgroup v2 вже в Ubuntu 24.04. Використовуйте це.

Приклад: встановити м’який і жорсткий ліміт для systemd-сервісу

cr0x@server:~$ sudo systemctl set-property myapp.service MemoryHigh=6G MemoryMax=7G
cr0x@server:~$ systemctl show myapp.service -p MemoryHigh -p MemoryMax
MemoryHigh=6442450944
MemoryMax=7516192768

Що це означає: Сервіс буде пригальмовуватись близько 6 GiB і вбиватись при 7 GiB (залежно від поведінки і політики oomd/kernel).

Рішення: Вибирайте ліміти на основі тестування навантаження і спостережуваного RSS. Якщо не можете тестувати — почніть консервативно і слідкуйте за memory.events.

Рівень хоста: зупиніть host swap перш ніж він почнеться

Host swap — це податок на кожну ВМ, сплачений латентністю. Якщо ваш хост свопить, «гучний сусід» — це сам хост.

Політика хоста, яка працює:

  • уникати співвідношень overcommit, що покладаються на «всі не пікуватимуть одночасно»
  • тримати host swap мінімальним або налаштувати його як аварійний гальмо, а не як щоденну смугу руху
  • моніторити PSI на хості і сигналізувати про стійкий «full» memory pressure

Стратегія swap в Ubuntu 24.04: менше драми, більше контролю

Swap не є злом. Це інструмент. Проблема в тому, коли воно випадково стає вашим основним рівнем пам’яті.

Виберіть підхід до swap, що відповідає режиму відмови

  • Дисковий swapfile: простий, постійний, може поглинати сплески, але під навантаженням спричиняє IO-ампліфікацію.
  • zram: стиснений swap в ОЗП, швидкий, зменшує диск IO, але використовує CPU і знижує ефективну ОЗП при високих коефіцієнтах стиснення.
  • Без swap: змушує OOM відбуватись раніше, може бути прийнятним для безстанних сервісів, ризиковано для систем, що потребують graceful degradation.

Мій типовий підхід для ВМ зі змішаними навантаженнями: малий дисковий swap + zram (опційно) + суворі cgroup-ліміти. Мета — пережити короткі сплески, не дозволяючи системі тихо накопичувати swap-заборгованість.

Зробіть swappiness політикою, а не фольклором

Нижчий swappiness зменшує охоту ядра свопити анонімну пам’ять. Для багатьох production-серверів діапазон 1030 — розумна відправна точка. Для десктопів дефолт годиться. Для ballooned гостей я надаю перевагу зниженню, бо ballooning вже непередбачувано зменшує запас.

cr0x@server:~$ sudo sysctl -w vm.swappiness=20
vm.swappiness = 20
cr0x@server:~$ printf "vm.swappiness=20\n" | sudo tee /etc/sysctl.d/99-swappiness.conf
vm.swappiness=20

Що це означає: Миттєва і постійна зміна.

Рішення: Якщо знизите swappiness, переконайтеся, що у вас достатньо запасу ОЗП; інакше ви просто швидше дійдете до OOM. Це може бути бажаним — якщо ви так запланували.

Коли zram допомагає (і коли ні)

zram може врятувати під час swap storm, бо замінює повільний IO швидкою компресією. Але це не безкоштовно: при тривалому тиску CPU може стати наступним вузьким місцем. Використовуйте його, коли ваш режим відмови — IO wait і major faults, а не коли CPU вже навантажений.

Швидка перевірка: чи є у вас уже zram?

cr0x@server:~$ swapon --show
NAME       TYPE      SIZE   USED PRIO
/swapfile  file        8G   6.7G   -2
/dev/zram0 partition    2G     0B  100

Що це означає: zram існує і має вищий пріоритет (100) ніж swapfile (-2), що правильно, якщо ви обрали цей підхід.

Рішення: Якщо диск swap активно навантажується, а zram не задіяний — пріоритети неправильні. Виправте пріоритети або вимкніть один з бекендів swap.

Анекдот #2 (коротко, по темі): Єдина річ, що росте швидше за використання swap — це впевненість людини, яка «просто збільшила swapfile».

Поведінка OOM: kernel OOM killer проти systemd-oomd

Ubuntu 24.04 часто працює з доступним systemd-oomd. Kernel OOM killer — останній засіб, коли ядро не може виділити пам’ять. systemd-oomd — це політичний двигун у просторі користувача, який може вбити раніше на основі сигналів тиску (PSI) і меж cgroup.

Чому це важливо

Якщо ви очікуєте «OOM тільки коли RAM на 100%», ви будете здивовані. oomd може вбити сервіс, коли система ще технічно жива, але глибоко заблокована. Це не жорстокість. Це тріаж.

Що потрібно налаштувати

  • Переконайтесь, що критичні системні сервіси захищені (або ізольовані), щоб oomd не прицілювався в неправильну ціль.
  • Помістіть основні навантаження в виділені слайси з чіткими бюджетами пам’яті.
  • Вирішіть, чи підходить вам «вбити найбільшого порушника», чи краще «вбити менш важливий» через структуру юнітів і пріоритети.

Перевірити, чи oomd увімкнено

cr0x@server:~$ systemctl is-enabled systemd-oomd
enabled

Що це означає: oomd діятиме, якщо налаштований/запущений тригером тиску і налаштуваннями юнітів.

Рішення: Якщо ви запускаєте multi-tenant навантаження у ВМ — oomd часто ваш друг. Якщо у вас один критичний моноліт з жорсткими SLO — можливо, краще жорстко обмежити пам’ять і дозволити kernel OOM діяти в контрольованому cgroup.

PSI: вимірюємо тиск замість вгадування

PSI показує, скільки часу задачі проводять у затримці через конкуренцію за ресурси. Memory PSI — найкраща метрика «чи система дійсно страждає?», яку я використовував за роки. Вона краща за «використано RAM» і чесніша за «load average».

Як виглядає добре

  • some avg10 низьких одиниць під час сплесків часто прийнятно.
  • full avg10 має бути близьким до нуля для систем з жорсткими вимогами по латентності.

Як виглядає погано

  • full avg10 стійко вище ~1–2% на production-недеревних вузлах зазвичай означає, що tail latency вже зіпсована.
  • some avg10 вище ~20–30% вказує на хронічний тиск. Система проводить третину життя в очікуванні пам’яті. Це не стиль життя.

Перевірте також CPU PSI (swap storm може маскуватися під CPU тиск через reclaim):

cr0x@server:~$ cat /proc/pressure/cpu
some avg10=9.12 avg60=6.10 avg300=2.00 total=55555555
full avg10=0.00 avg60=0.00 avg300=0.00 total=0

Що це означає: CPU зайнятий, але не настільки, щоб блокувати всі задачі. Якщо memory PSI високий, а CPU PSI помірний — винна пам’ять.

Рішення: Не «масштабуйте CPU», щоб виправити memory stall. Ви просто отримаєте швидший stall.

Три корпоративні міні-історії з практики

Міні-історія 1: Інцидент спричинений неправильною припущенням

Компанія: середній SaaS-провайдер з міксом API для клієнтів та фонових воркерів. Платформна команда розгорнула Ubuntu 24.04 гості на KVM-кластері. Вони також увімкнули ballooning, бо кластер «здебільшого не завантажений вночі», і дашборди робили його безпечним.

Неправильне припущення було тонким: «Якщо хост має вільну пам’ять, гість завжди швидко отримає її назад». Насправді аллокатор хоста мав багато вільної пам’яті агреговано, але вона була недоступна в потрібному місці в моменті через конкуренцію ВМ, які одночасно пікували, і швидкі зміни цілей balloon.

API-шифт прийняв перший удар. Запити почали таймаутитися, але CPU не був зашкалений. Інженери ганялися за уявною проблемою мережі. Відкритий натяк був на видноті: major faults стрибнули, і memory PSI full піднявся вище 5% кілька хвилин поспіль.

Зрештою kernel OOM killer звинуватили у «випадковому» вбивстві Node-процесу. Це не було випадково. Це був процес, що найчастіше торкався підмінених сторінок під час пікового трафіку. Постмортем-фікс був нескладний: підняти balloon floor вище steady-state RSS, встановити cgroup-ліміти на сервіси і сигналізувати по PSI та swap-in rate замість «використано RAM».

Міні-історія 2: Оптимізація, що повернулась бумерангом

Інша організація, та сама категорія проблем. Вони пишались контролем витрат. Хтось запропонував зменшити алокації пам’яті ВМ, бо «Linux все одно використовує вільну пам’ять для кешу». Правда, але застосована неправильно.

Вони обрізали vRAM, потім компенсували, збільшивши розмір swap, «щоб не OOM-итись». Перший тиждень був нормальний: менше OOM-подій, менше алертів. Потім прийшло кінець місяця з важчим звтомлінням звітів і батч-процесом, що розігрівав великі структури в пам’яті.

Система не впала; вона стала непридатною. Це найгірший вид відмови, бо викликає повтори й таймаути, а не чистий рестарт. Графіки сховища показували високе використання; всі дивились на SAN як на зрадника.

Корінь: ВМ увійшла у стійкий swap-дебіt. Більший swapfile відтермінував OOM, але дозволив набору процесів вирости за межі того, що ВМ може ефективно обробляти. «Оптимізація» перетворила різкий крах у повільний простій. Остаточний фікс: менший swap, суворіші per-service memory cap-и і політика: для latency-tier сервісів свопінг — це інцидент, а не спосіб виживання.

Міні-історія 3: Нудна, але правильна практика, що врятувала день

Фінансова компанія запускала декілька критичних PostgreSQL вузлів як ВМ. Вони мали репутацію консервативних — іноді дратуюче так. Ніякого ballooning на БД-ВМ. Фіксовані алокації пам’яті. Суворі резервації на хості. Нудно.

Одного дня сусідній кластер мав невідношену memory leak у наборі воркерів. Хости почали відчувати тиск. У робочих навантаженнях інших команд радіус ураження був страшним: балони змінювалися, гості свопили і латентність зростала. Але DB-ВМ залишалися стійкими. Їх захистила політика, а не удача.

DB-команді все одно довелось реагувати на наслідки (таймаути додатків, шторм підключень), але їх вузли не приєднались до хаосу. Це важливо: стабільні бази даних дають опції. Ви можете скидати навантаження, відключати трафік і відновлюватися з меншим числом рухомих частин.

Урок постмортему не був гламурним: вони продовжували робити нудні речі — резервувати пам’ять для stateful систем, капати все інше і трактувати overcommit як контрольований ризик з моніторингом. Іноді найкраща оптимізація — відмовитись оптимізувати не те.

Типові помилки: симптом → корінь → виправлення

1) «CPU високий, отже це обчислювальна проблема»

Симптом: високий load average, велика системна частка CPU, висока латентність, але користувацький CPU не надто високий.

Корінь: витрати на reclaim і накладні витрати page fault (direct reclaim, kswapd), часто викликані ballooning або memory leak.

Виправлення: Перевірте PSI і major faults. Зменшіть тиск пам’яті спочатку: підніміть VM floor / зменшіть ballooning / обмежте сервіси / виправте витік. Тільки потім переглядайте CPU.

2) «На хості є вільна пам’ять, отже гість не мусить свопити»

Симптом: гість інтенсивно свопить; хост показує доступну пам’ять; люди сперечаються, чи чиї графіки неправильні.

Корінь: гість бачить ballooned пам’ять; політика гіпервізора і таймінги означають, що «вільно» не дорівнює «доступно для цієї ВМ зараз».

Виправлення: Встановіть balloon floors і припиніть агресивні зміни цілей. Для критичних ВМ відключіть ballooning або використайте статичні резерви.

3) «Просто додамо ще swap»

Симптом: менше OOM kill-ів, але довші таймаути і більші провали продуктивності під навантаженням.

Корінь: swap став милицями, що дозволяють пам’яті ростити footprint понад робочий набір.

Виправлення: Тримайте swap помірним. Використовуйте cgroup-ліміти і oomd, щоб швидко провалюватися і відновлюватись, або забезпечте більше RAM.

4) «Налаштування swappiness врятує шторм»

Симптом: хтось ставить swappiness у 1 під час інциденту; нічого не змінюється.

Корінь: коли ви вже в thrash-режимі, система вже має борг; політичні ключі не повернуть сторінки з swap миттєво.

Виправлення: Негайна міра — зменшити попит пам’яті або додати реальну пам’ять. Застосовуйте зміни swappiness після стабілізації і валідуйте тестами навантаження.

5) «Kernel OOM killer — випадковий»

Симптом: різні процеси вмирають кожного разу; люди роблять висновок, що Linux непередбачуваний.

Корінь: OOM scoring залежить від використання пам’яті і налаштувань; під тиском «найкраща» жертва змінюється з часом і по таймінгу навантаження.

Виправлення: Помістіть робочі навантаження в cgroups з явними бюджетами пам’яті, встановіть OOMScoreAdjust для критичних сервісів і покладайтеся на oomd для ранніх, політично керованих вбивств.

6) «Кеш краде мою пам’ять» (вічна скарга)

Симптом: free показує мало вільної пам’яті; люди панікують і очищають кеш.

Корінь: нерозуміння різниці між Linux page cache і MemAvailable; скид кешу викликає IO-сплески і може погіршити латентність.

Виправлення: Використовуйте MemAvailable, PSI і major faults. Скид кешу — лише в контрольованих тестах, не в бойових пожежах.

Чеклисти / покроковий план

Покроково: стабілізувати активний swap storm (в гостьовій ОС)

  1. Підтвердити реальний тиск: запустіть vmstat, перевірте si/so, запустіть cat /proc/pressure/memory.
  2. Знайти ховака: ps ... --sort=-rss, перевірте per-process swap через /proc/<pid>/smaps_rollup.
  3. Зупинити ріст: обмежте сервіс за допомогою systemctl set-property ... MemoryMax= або зменшіть воркерів/heap.
  4. Координувати з гіпервізором: підніміть виділену пам’ять або зменшіть ціль/підлогу ballooning. Це часто найшвидший реальний фікс.
  5. Стратегічно перезапустити: перезапустіть найсильніше підмінені латентні процеси після зменшення їхнього споживання пам’яті. Перезапуск без зміни апетиту — просто повторення інциденту.
  6. Спостерігати за відновленням: major faults і PSI повинні впасти першими. Використання swap може залишатися високим; це нормально, якщо swap-in припиниться і латентність відновиться.

Покроково: запобігти повторенню (політика)

  1. Виберіть шар для reclaim: вважайте за краще cgroup-ліміти гостя і політики oomd перед несподіваним ballooning на хості.
  2. Встановіть VM floors: забезпечте, щоб мінімальна пам’ять покривала steady-state + запас для сплесків для кожного класу ВМ.
  3. Встановіть бюджети на сервіс: використайте MemoryHigh/MemoryMax і валідируйте через memory.events.
  4. Правильно налаштуйте swap: малий-до-помірний swap; розгляньте zram з правильним пріоритетом; зробіть swap-in rate метрикою для алертів.
  5. Алертинг за PSI: особливо memory full avg10. Воно ловить «повільну смерть» раніше за більшість дашбордів.
  6. Тестуйте навантаження з увімкненим ballooning: якщо ви наполягаєте на ballooning, тестуйте його під піковою конкуренцією з реалістичними коливаннями пам’яті.
  7. Документуйте поведінку вбивств: які сервіси дозволено вбивати першими і як вони відновлюються (systemd restart policies, graceful shutdown timeouts).

Покроково: перевірка здорового глузду після змін

  1. Запустіть контрольований стрес. Зафіксуйте free -h, PSI, sar -B, iostat -x.
  2. Перевірте застосування cgroup: під час стресу memory.events має відображати ваші пороги, а не лише виникати після катастрофи.
  3. Підтвердьте, що хост не свопить під нормальними піками (якщо ви контролюєте хост). Якщо хост свопить — ваша платформа бреше вам.

Питання та відповіді

1) Чи завжди swap поганий на серверах Ubuntu 24.04?

Ні. Swap — це буфер безпеки. Він стає поганим, коли використовується постійно або настільки активно, що викликає major faults і IO wait. Розглядайте swap-in rate і memory PSI full як справжні червоні прапори.

2) Чому мій гість свопить, коли хост показує вільну пам’ять?

Бо доступна «фізична» пам’ять гостя може зменшитись через ballooning, незалежно від графіків вільної пам’яті хоста. Також «вільно» на хості не означає «негайно виділено цій ВМ» під час конкуренції.

3) Чи варто відключати ballooning?

Для критичних stateful систем (БД, черги) та суворих latency SLO: зазвичай так, або принаймні встановіть консервативний floor. Для сплескових stateless флотів ballooning може бути прийнятним за умови моніторингу PSI і сильних per-service лімітів.

4) У чому різниця між MemoryHigh і MemoryMax?

MemoryHigh — це точка пригальмування: ядро почне reclaim всередині cgroup і застосує тиск. MemoryMax — жорстка межа: алокації будуть відмовлені і може спрацювати OOM у цій cgroup.

5) Чому systemd-oomd вбив сервіс, хоча ще була RAM?

oomd може діяти на стійкий тиск (PSI), щоб зберегти відгук системи. Це може статися до того, як RAM впаде до нуля, особливо під час reclaim-стрибків і своп-тряски.

6) Чи достатньо зниження swappiness, щоб зупинити swap storms?

Саме по собі — ні. Воно може зменшити готовність ядра свопити під помірним тиском, але не компенсує недостатній RAM, агресивний ballooning або витік пам’яті. Спочатку виправте розміри і ліміти.

7) Як дізнатись, чи я страждаю від major faults?

Використовуйте sar -B (дивіться majflt/s) і корелюйте з латентністю. Major faults означають, що ваше навантаження реально чекає на swap.

8) На що налаштовувати алерти, щоб вловити це раніше?

Як мінімум: memory PSI (full і some), тренд swap-in rate (vmstat si), major faults і memory.events для ключових сервісів. «Відсоток використаної RAM» — слабкий сигнал сам по собі.

9) Чи варто використовувати zram на production ВМ?

Іноді. Якщо ваші swap storms обумовлені IO і у вас є запас CPU, zram може значно знизити IO wait. Якщо ви вже CPU-bound — zram може просто змінити вузьке місце.

10) Чи може swap storm виглядати як відмова сховища?

Так. Swap storms генерують багато випадкових IO, насичують пристрої і підвищують латентність. Сховище виглядає «повільним», але воно реагує на патологічний попит. Виправте тиск пам’яті і «відмова сховища» часто зникає.

Висновок: наступні кроки, які можна застосувати сьогодні

Якщо запам’ятати одну річ: ballooning у поєднанні зі слабыми лімітами — це спосіб випадково збудувати генератор swap storm. Ubuntu 24.04 дає інструменти, щоб цього уникнути — cgroup v2 контролі, видимість PSI і керовану політику OOM. Використовуйте їх свідомо.

Практичні наступні кроки:

  • На одній проблемній ВМ зробіть бенчмарки: free -h, vmstat 1, sar -B, cat /proc/pressure/memory.
  • Встановіть per-service бюджети пам’яті з MemoryHigh і MemoryMax; перевіряйте через memory.events.
  • Домовтесь про політику пам’яті для ВМ: консервативні balloon floors для критичних систем і явні правила, коли дозволений ballooning.
  • Правильно розрахуйте swap, щоб він був буфером, а не стилем життя. Розгляньте zram лише з відкритими очима.
  • Алертуйте за тиском (PSI) і swap-in, а не лише за «відсотком використання».

Зробіть це — і наступний «ВМ відчувається повільною» стане п’ятихвилинною діагностикою замість трьохгодинної розбирання винних.

← Попередня
Ubuntu 24.04: swappiness і налаштування vm.dirty — малі тюнінги, що справді мають значення
Наступна →
ZFS для медіафайлів: великі блоки, велика компресія, великі перемоги

Залишити коментар