Спочатку це виглядає як «невелике уповільнення». Потім ваші графіки перетворюються на модерністську абстракцію: середнє навантаження росте, латентність стрибкоподібно зростає, диски виглядають завантаженими без видимої причини, і віртуальна машина ніби тягнеться крізь мед.
В Ubuntu 24.04 у віртуалізованих середовищах балонування пам’яті може перетворитися з «корисної функції оверкоміту» на «генератор swap-штормів». Виправлення рідко містить містику. Зазвичай це питання лімітів, обліку й твердого рішення про те, що має відбуватися, коли пам’яті справді бракує.
Що насправді відбувається, коли балонування шкодить
Балонування пам’яті існує тому, що гіпервізори люблять грати в тетріс з оперативною пам’яттю. Віртуальній машині може бути виділено 16 ГБ, але більшість часу вона використовує 4–8 ГБ. Балонування дозволяє хосту відібрати «невикористану» частину й позичити її іншим ВМ. Теоретично всі в плюсі.
На практиці балонування може породжувати специфічний вид муки:
- Хост відбирає пам’ять у гістьової ОС у найгірший момент (тому що сам хост під тиском).
- Гостьове ядро бачить менше доступної пам’яті і починає звільняти сторінки.
- Якщо звільнення не знаходить достатньо чистого page cache, починається свапінг анонімної пам’яті.
- Swap I/O відбувається на віртуальних дисках, які часто лежать на розділеному сховищі, що підсилює затримки.
- Тепер гість повільніший, отже прогрес повільніший, тому він довше перебуває під тиском і свапить ще більше.
Само по собі балонування не є злом. Непомірне балонування з оптимістичним оверкомітом і слабкими запобіжниками — це зло. Найпростіше описати так: у вас управління пам’яттю відбувається в двох місцях (хост і гість) з двома ядрами, які не ділять спільного розуму.
Ось те, що дивує людей в Ubuntu 24.04: екосистема навколо сигналів пам’яті стала більш «допоміжною». cgroup v2 — стандарт, systemd діє більш проактивно, а поведінка OOM може відрізнятися від старішої LTS. Нічого з цього не є поганим. Просто ваші старі припущення можуть голосно провалитися.
Цитата для честності (перефразована ідея): підхід Ґіна Кранца щодо надійності: «Ми маємо бути суворими й компетентними — без виправдань.» Це також застосовується до лімітів пам’яті.
Жарт №1: Балонування пам’яті — це як «тимчасовий» стіл на кухні. Тимчасовий, поки вечеря не зіпсується й ви не знайдете виделок.
Цікаві факти та контекст (те, що пояснює дивність)
- Балонування не нове. VMware популяризував balloon-драйвери десятиліття тому; virtio-balloon у KVM зробив це поширеним в open stacks.
- Linux за замовчуванням не трактує swap як фіаско. Ядро буде свапити, щоб зберегти файловий кеш і згладити сплески; це раціонально, поки бекенд сховища не робить свап дорогим.
- cgroup v2 змінив правила гри. Облік пам’яті став жорсткішим і більш єдиним. Якщо встановити memory.max, це справжня стіна, а не ввічлива порада.
- «Available» пам’ять — це не «free». Метрика «available» у Linux оцінює, що можна звільнити без свапінгу. Люди досі панікують через «free». Не слід.
- kswapd — симптом, а не лиходій. Високе навантаження kswapd означає, що ядро намагається звільнити сторінки під тиском. Коренева причина майже завжди: замало RAM для робочого набору.
- Swap-шторми заразні. Одна ВМ, яка інтенсивно свапить, може погіршити затримки розділеного сховища, що уповільнить інші ВМ, збільшить їхній тиск пам’яті і змусить їх свапити. Вітаємо, ви винайшли інцидент на рівні кластера.
- Балонування може виглядати як витік пам’яті. Зсередини гостя здається, що ОЗП зникла. Тому що так і сталося.
- zram — сучасний компроміс. Стиснений swap у RAM зменшує I/O, але збільшує навантаження на CPU. Чудово для деяких робочих навантажень; жахливо для інших.
- THP може ускладнювати звільнення. Transparent Huge Pages може зробити звільнення й компактну збірку дорожчими під тиском, залежно від навантаження й налаштувань.
Швидкий план діагностики (перший/другий/третій)
Перший: підтвердіть, що машина дійсно під тиском пам’яті
- Шукайте активність swap-in/out і великі помилки сторінок.
- Підтвердіть, чи «available» пам’ять низька й лишається такою.
- Перевірте PSI (pressure stall information), щоб побачити, чи система затримується на звільненні пам’яті.
Другий: доведіть, чи причетне балонування
- Перевірте присутність драйвера virtio_balloon і статистику балона.
- Корелюйте поведінку «host reclaimed» (якщо видно) зі зниженням пам’яті в гості.
- На платформах типу Proxmox/OpenStack порівняйте налаштовану пам’ять і ефективну, а також зміни цілі балона.
Третій: визначте, що споживає пам’ять і чи його можна звільнити
- Топ процесів за RSS і за анонімною пам’яттю.
- Файловий кеш проти анонімного використання (page cache — ваш друг, поки ним не стане).
- cgroup-ліміти та обмеження для окремих сервісів.
Вирішіть бажаний результат
- Якщо потрібна передбачувана латентність: зменшіть оверкоміт, встановіть жорсткі ліміти й віддайте перевагу OOM kill перед swap-штормами.
- Якщо потрібна максимальна щільність: дозволяйте балонування, але встановіть мінімум, використовуйте zram обачно і моніторте PSI як джерело доходу.
Практичні завдання: команди, виводи та рішення
Ось завдання, які я фактично виконую, коли ВМ «якимось чином стала повільною». Кожне містить тлумачення виводу та наступне рішення. Запускайте їх по черзі, поки не отримаєте узгоджену картину. Якщо не можете пояснити картину — не крутіть ручки.
Завдання 1: Перевірте загальний стан пам’яті (і не читайте неправильно)
cr0x@server:~$ free -h
total used free shared buff/cache available
Mem: 7.7Gi 6.9Gi 112Mi 204Mi 731Mi 402Mi
Swap: 4.0Gi 3.6Gi 128Mi
Що це означає: Available лише 402 MiB і swap сильно задіяний. Ви вже в «режимі звільнення».
Рішення: Негайно перейдіть до vmstat і PSI, щоб побачити, чи триває активне трясіння чи це наслідок минулої події.
Завдання 2: Виявити активний swap-шторм чи «swap стався раніше»
cr0x@server:~$ vmstat 1 10
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
2 1 3718920 112640 55872 634320 64 128 220 310 410 790 12 8 55 25 0
3 2 3721048 98304 53120 621104 256 512 880 1200 610 1200 14 10 35 41 0
4 2 3723096 94208 51456 609440 512 1024 1300 1900 720 1400 12 9 28 51 0
3 1 3725144 90112 49664 601112 128 256 600 840 540 1100 10 7 45 38 0
Що це означає: Ненульові si/so протягом кількох зразків разом з високим wa вказують на активний свапінг і I/O wait. Це не «старий swap».
Рішення: Потрібно зупинити тиск. Додайте пам’ять / зменште балонування / зменшіть навантаження / або прийміть OOM для обмеженої області впливу.
Завдання 3: Перевірте PSI, щоб підтвердити затримки (Ubuntu 24.04 має його)
cr0x@server:~$ cat /proc/pressure/memory
some avg10=18.24 avg60=12.10 avg300=5.43 total=218443992
full avg10=6.12 avg60=3.98 avg300=1.22 total=68423992
Що це означає: «some» означає, що завдання інколи затримуються; «full» означає, що система повністю стоїть, чекаючи пам’яті. Немалі значення full сильно корелюють із помітним для користувача болем.
Рішення: Ставте це як інцидент. Не думайте «можливо це CPU», поки не вирішите питання пам’яті.
Завдання 4: Перевірте наявність драйвера балона (з боку гостя)
cr0x@server:~$ lsmod | grep -E 'virtio_balloon|vmw_balloon'
virtio_balloon 24576 0
Що це означає: virtio balloon доступний. Це не доводить, що він активно інфлюється, але робить балонування правдоподібною причиною.
Рішення: Перевірте статистику балона, щоб побачити, чи гість вважає, що пам’ять у нього була взята.
Завдання 5: Інспектуйте статистику virtio balloon (якщо доступна)
cr0x@server:~$ grep -H . /sys/devices/virtio*/balloon*/{num_pages,actual,free_page_hint} 2>/dev/null
/sys/devices/virtio0/virtio0/balloon/num_pages:2097152
/sys/devices/virtio0/virtio0/balloon/actual:1572864
/sys/devices/virtio0/virtio0/balloon/free_page_hint:1
Що це означає: Поле «actual» вказує сторінки, що зараз забалоновані (віджаті). Якщо воно зростає під час тиску хоста, ваша ВМ бачить менше використовуваної пам’яті.
Рішення: Якщо забалоновані сторінки значущі відносно загального обсягу, не думайте про RAM як про «гарантовану». Встановіть мінімум або вимкніть балонування для ВМ із критичними вимогами до латентності.
Завдання 6: Підтвердіть налаштовану пам’ять проти того, що бачить ядро
cr0x@server:~$ grep -E 'MemTotal|MemAvailable' /proc/meminfo
MemTotal: 8049136 kB
MemAvailable: 411224 kB
Що це означає: MemTotal — це те, що гість наразі вважає своєю пам’яттю. Якщо ви «налаштували 16G», а MemTotal ≈ 8G, то в гру вступає балонування (або hotplug).
Рішення: Узгодьте очікування з реальністю. Якщо ВМ потребує 16G — не дозволяйте хосту ставитись до неї як до скарбнички.
Завдання 7: Визначте топ-споживачів пам’яті за RSS (швидка триаж)
cr0x@server:~$ ps -eo pid,comm,rss --sort=-rss | head -n 10
PID COMMAND RSS
2481 java 2381440
1822 postgres 1024320
1999 node 612480
1320 snapd 188224
911 systemd-journal 123456
2766 python3 112320
Що це означає: У вас великі користувачі анонімної пам’яті (наприклад, JVM) і станозберігаючі сервіси. Вбивання не того процесу може «виправити свапінг» і одночасно «знищити бізнес».
Рішення: Для відомих важких споживачів (JVM, БД) перевірте налаштування heap/буферів. Тиск пам’яті через балонування може виявити надто оптимістичні налаштування додатків.
Завдання 8: Перевірте проблеми kernel reclaim (компакція, THP)
cr0x@server:~$ grep -E 'compact|thp' /proc/vmstat | head
compact_migrate_scanned 184221
compact_free_scanned 91234
compact_isolated 1422
thp_fault_alloc 1221
thp_collapse_alloc 18
Що це означає: Висока активність компактації під тиском може збільшити зайнятість CPU у reclaim і гальмувати навантаження. THP-алокація/колапс може додавати накладні витрати.
Рішення: Якщо бачите важку компактацію поряд із PSI «full», подумайте про тонке налаштування THP або забезпечте більше запасу пам’яті, а не «оптимізуйте свап».
Завдання 9: Шукайте OOM або майже-OOM події (journal)
cr0x@server:~$ journalctl -k --since "2 hours ago" | egrep -i "oom|out of memory|kswapd|memory pressure" | tail -n 20
Dec 30 09:41:12 server kernel: Memory cgroup out of memory: Killed process 1999 (node) total-vm:3128448kB, anon-rss:598112kB, file-rss:2048kB
Dec 30 09:41:12 server kernel: oom_reaper: reaped process 1999 (node), now anon-rss:0kB, file-rss:0kB, shmem-rss:0kB
Що це означає: Ядро вбило процес через OOM у memory cgroup. Це не випадковість; це примусове застосування ліміту (або витік, що вдарив у стіну).
Рішення: Якщо ви віддаєте перевагу цьому перед swap-штормами — добре, зробіть це свідомо: встановіть ліміти пам’яті для сервісів і задокументуйте їх. Якщо ні — підвищте ліміти або зменшіть балонування.
Завдання 10: Перевірте swap-пристрої та пріоритети
cr0x@server:~$ swapon --show --bytes
NAME TYPE SIZE USED PRIO
/swap.img file 4294967296 3865051136 -2
Що це означає: Використовується swapfile, низький пріоритет. Якщо він розміщений на повільному або навантаженому сховищі, свапінг буде карати всіх.
Рішення: Вирішіть, чи залишати swap (часто так, але менший) і чи використовувати zram, або вимкнути swap для ВМ з критичною латентністю (з запобіжниками).
Завдання 11: Перевірте параметри поведінки свапу (swappiness, vfs cache pressure)
cr0x@server:~$ sysctl vm.swappiness vm.vfs_cache_pressure
vm.swappiness = 60
vm.vfs_cache_pressure = 100
Що це означає: Значення типові. Під балонуванням ядро може свапити раніше, ніж ви бажаєте для певних навантажень.
Рішення: Для баз даних/латентних ВМ подумайте про зменшення swappiness (наприклад, 10–20) після того, як задасте адекватні ліміти. Тюнінг без лімітів — це марнотратство бажань.
Завдання 12: Підтвердіть обмеження cgroup v2 (система загалом)
cr0x@server:~$ stat -fc %T /sys/fs/cgroup
cgroup2fs
Що це означає: Ви на cgroup v2. Ubuntu 24.04 за замовчуванням так налаштована.
Рішення: Використовуйте контролі v2 (memory.max, memory.high, memory.swap.max). Не застосовуйте по-старому поради з cgroup v1 дослівно.
Завдання 13: Перевірте, чи активний systemd-oomd (він може змінювати результати)
cr0x@server:~$ systemctl is-enabled systemd-oomd
enabled
Що це означає: Користувацький OOM-демон може втручатися на основі сигналів тиску. Це може бути корисно (швидше відновлення) або заплутувати (неочікувані вбивства).
Рішення: Якщо у вас критичні одноарендні робочі навантаження, налаштуйте oomd для scope/slice або вимкніть його навмисно — не відкривайте це під час інциденту.
Завдання 14: Виявити насичення I/O через свап (кут «це сховище»)
cr0x@server:~$ iostat -xz 1 3
Linux 6.8.0-41-generic (server) 12/30/2025 _x86_64_ (4 CPU)
avg-cpu: %user %nice %system %iowait %steal %idle
11.01 0.00 7.32 39.88 0.00 41.79
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
vda 42.0 1120.0 0.0 0.00 18.5 26.7 51.0 2496.0 2.0 3.77 44.2 48.9 3.3 95.0
Що це означає: Високе %util, велике await і багато дрібних записів: класичний churn від свапу на віртуальному диску.
Рішення: Виправте тиск пам’яті спочатку. Налаштування сховища не наздожене ВМ, яка сама себе вбиває свапом.
Завдання 15: Підтвердіть, чи ви в ВМ і який гіпервізор (допомагає підібрати ручки)
cr0x@server:~$ systemd-detect-virt
kvm
Що це означає: KVM-гость. Балонування ймовірно на основі virtio; qemu-guest-agent може бути релевантним залежно від платформи.
Рішення: У KVM-стеках вирішіть: вимкнути балонування для критичних навантажень, або встановити мінімум і моніторити зміни цілі балона.
Завдання 16: Перевірте memory.high/memory.max для певного сервісу (реальний винуватець у сучасних системах)
cr0x@server:~$ systemctl show -p ControlGroup -p MemoryMax -p MemoryHigh nginx.service
ControlGroup=/system.slice/nginx.service
MemoryMax=infinity
MemoryHigh=infinity
Що це означає: Немає явного контролю пам’яті. Якщо nginx не є великим споживачем, це нормально. Для важких сервісів «infinity» означає, що вони можуть змагатися, доки вся коробка не постраждає.
Рішення: Встановіть ліміти пам’яті для небагатьох сервісів, які можуть непередбачувано роздутися (воркери, JVM, збірки, батч-завдання).
Встановіть адекватні ліміти: гіпервізор, гість і cgroup v2
Балонування стає придатним, коли ви задаєте межі. У продакшені «необмежене шарування» — це просто оптимістичний шлях до інциденту.
1) Визначте контракт пам’яті: гарантований, бурстовий або best-effort
Кожна ВМ має мати один із цих контрактів:
- Гарантований: Пам’ять резервується. Балонування вимкнено або мінімум встановлено близько до максимуму. Використовується для баз даних, сервісів контрольної площини, SLO з латентності.
- Бурстовий: Дозволено певне відбирання, але є жорстка підлога. Використовується для веб-шарів, кешів, аплікаційних серверів, які можуть скидати навантаження.
- Best-effort: Балонування увімкнено, низький мінімум, може свапити/отримати OOM. Для dev, CI, батчів, тимчасових воркерів.
2) На гіпервізорі: перестаньте обіцяти одну й ту саму RAM всім
Який би стек ви не використовували — raw libvirt, Proxmox, OpenStack — концепція однакова:
- Встановіть максимум пам’яті (що VM може мати).
- Встановіть мінімум/резервацію пам’яті (що вона має завжди тримати).
- Обмежте overcommit на хості до такого рівня, який ви переживете, коли навантаження вирівняються не на вашу користь.
Чому? Тому що reclaim не безкоштовний. Гість може бути змушений свапити анонімні сторінки, щоб задовольнити інфляцію балона, а рахунок заплатить ваше сховище.
3) У гостьовій ОС: використовуйте cgroup v2, щоб примусово локалізувати шкоду
Якщо ви не можете повністю контролювати хост (вітаємо на Землі), принаймні обмежуйте шкоду в гості. cgroup v2 дає три великі інструменти:
memory.max: жорсткий ліміт. Перевищення призведе до OOM-kill у цій cgroup.memory.high: м’який ліміт. Ядро буде гальмувати/звільняти, щоб тримати використання поблизу нього.memory.swap.max: обмежує використання свапу на cgroup (потужний спосіб запобігти swap-шторми від одного сервісу).
Для сервісів, керованих systemd, зазвичай встановлюють це через unit-override. Ось як це виглядає для «бурстового, але обмеженого» воркер-сервісу.
cr0x@server:~$ sudo systemctl edit worker.service
[Service]
MemoryHigh=2G
MemoryMax=3G
MemorySwapMax=512M
Що це означає: Сервіс може використовувати пам’ять, але не може поглинути машину. Якщо він виросте понад 3G — його вб’ють, замість того, щоб заганяти всю ВМ у свап.
Рішення: Використовуйте це для ненадійних або сплескових компонентів: фонових робіт, споживачів повідомлень, «ще одного експортеру» і всього, написаного мовою, що може відкрити нескінченність.
4) Робіть рішення про OOM навмисними: ядро проти systemd-oomd
Ubuntu 24.04 може задіяти systemd-oomd, який діє на основі PSI і тиску memory cgroup. Це не класичний kernel OOM killer. Інший тригер, інша поведінка, іноді раніше, часто чистіше.
Що робити:
- Якщо ви хочете суворої ізоляції сервісів — тримайте oomd увімкненим і керуйте slice-ами.
- Якщо у вас один критичний моноліт і oomd може вбити не те — налаштуйте його або вимкніть, але тільки після того, як матимете альтернативну ізоляцію (ліміти, резервації).
cr0x@server:~$ systemctl status systemd-oomd --no-pager
● systemd-oomd.service - Userspace Out-Of-Memory (OOM) Killer
Loaded: loaded (/usr/lib/systemd/system/systemd-oomd.service; enabled; preset: enabled)
Active: active (running) since Mon 2025-12-30 08:01:12 UTC; 2h 10min ago
Що це означає: Він запущений і буде діяти, якщо конфігуровані slice-и перетнуть пороги тиску.
Рішення: Якщо ви цього не планували, сплануйте зараз. «Неочікувані вбивства процесів» — це не стратегія моніторингу.
Зупиніть swap-шторми: вибір swap, swappiness і контроль тиску
Swap не є однозначно добрим чи злим. Це інструмент. У ВМ він також перетворюється на пастку продуктивності, оскільки I/O для свапу зазвичай — найповільніший і найзавантаженіший шлях у всьому стеку.
Виберіть модель swap
Зазвичай ви хочете одну з цих моделей:
- Малий swap + низький swappiness: Достатньо, щоб уникнути катастрофічного OOM під час невеликих сплесків, але не стільки, щоб підтримувати довготривале трясіння.
- zram swap: Свап у RAM зі стисненням. Зменшує диск I/O, але витрачає CPU. Добре для dev і сплескових навантажень; оцінюйте для CPU-насичених продакшн-сервісів.
- Без swap (рідко коректно): Лише коли у вас строгі резервації і ви віддаєте перевагу миттєвому відмові над деградованою поведінкою. Працює найкраще з хорошими лімітами й алертингом.
Завдання: Перевірте, чи ви вже використовуєте zram
cr0x@server:~$ lsblk -o NAME,TYPE,SIZE,MOUNTPOINT | grep -E 'zram|swap'
zram0 disk 2G
Що це означає: Існує zram-пристрій. Він може бути або не бути налаштованим як swap.
Рішення: Підтвердіть через swapon --show. Якщо у вас і zram, і дисковий swap — встановіть пріоритети свідомо.
Завдання: Встановіть swappiness постійно (лише після виправлення лімітів)
cr0x@server:~$ sudo tee /etc/sysctl.d/99-memory-sane.conf >/dev/null <<'EOF'
vm.swappiness=15
vm.vfs_cache_pressure=100
EOF
cr0x@server:~$ sudo sysctl --system | tail -n 5
* Applying /etc/sysctl.d/99-memory-sane.conf ...
vm.swappiness = 15
vm.vfs_cache_pressure = 100
Що це означає: Ядро віддаватиме перевагу звільненню кешу перед свапінгом анонімної пам’яті порівняно з поведінкою за замовчуванням, хоча воно все одно може свапити під реальним тиском.
Рішення: Якщо ВМ свапить тому, що балонування відібрало RAM, налаштування swappiness допоможе на межах. Основне виправлення — зупинити крадіжку RAM спочатку.
Обмежте swap на сервіс (недооцінене виправлення)
Swap-шторми зазвичай починаються з одного жадібного процесу. Якщо ви обмежите його swap, він не зможе розмазати свої проблеми по всій машині.
cr0x@server:~$ sudo systemctl set-property batch-jobs.service MemorySwapMax=0
cr0x@server:~$ systemctl show -p MemorySwapMax batch-jobs.service
MemorySwapMax=0
Що це означає: Цей сервіс не може використовувати swap. Якщо йому не вистачить пам’яті — його вб’ють у межах cgroup.
Рішення: Застосовуйте це до навантажень, для яких «повільно свапитись 30 хвилин» гірше, ніж «впасти швидко і перезапуститися». Батчі — ідеальний кандидат.
Зрозумійте, чому swap-шторми відчуваються як провал сховища
Як інженер сховища, скажу прямо: ВМ, що сильно свапить, не відрізнити від атаки на сховище — тільки вона самостійна і автентифікована.
Swap породжує:
- Дрібні випадкові записи (page-outs) і читання при fault-ах.
- Високі черги і збільшену затримку.
- Конкурентні пристрої бекенду (особливо на спільних SSD-пулах або мережевому сховищі).
Якщо бачите «інцидент затримки сховища» і одна гостьова машина свапить — виправте контракт пам’яті гостя спочатку. Потім поговоріть про IOPS.
Жарт №2: Swap-шторми — єдина метеосистема, що формується в приміщенні, і вона все одно вміє валити ваш сервіс.
Три корпоративні міні-історії (як це провалюється в реальних компаніях)
Міні-історія №1: Аварія через хибне припущення
Компанія мала охайне припущення: «Налаштована пам’ять — це гарантована пам’ять». Вони довго запускали старі Ubuntu-гості на KVM-кластері з увімкненим балонуванням, і більшість ВМ ніколи не скаржились. Дашборди показували, що кожна ВМ «має 16 GB», тож люди розмірковували під цю ідею.
Потім стався інцидент на фізичному хості: один фізичний вузол втратив канал DIMM і кластер перебалансувався. Оверкоміт лишався увімкненим. Планувальник щільно підсів навантаження, щоб зберегти ємність. Балонування інфлювалося на кількох гостях одночасно, бо хосту знадобилася пам’ять негайно, а не ввічливо.
Всередині критичної Ubuntu 24.04 ВМ MemTotal ніхто не перерахував у голові, але фактично воно змінилося: available упало, reclaim підійнявся, і ядро почало свапити частини JVM heap. Латентність зросла, що спричинило черги запитів, що збільшило утримання heap. Це був петлевий зворотний зв’язок із кривавою візуалізацією.
Команда спочатку ганялась за CPU steal і «шумним сусідом на сховищі». Обидва були реальними, але вторинними. Первинною подією було вилучення пам’яті через балонування під тиском хоста. Їхнє припущення виявилось хибним: налаштована пам’ять не була обіцянкою; вона була максимумом.
Виправлення було нудним і ефективним: вимкнули балонування для шару з SLO, встановили резервації хоста для критичних ВМ і перестали оверкомітити пам’ять на вузлах зі станозберігаючими сервісами. Витрати трохи зросли. Інциденти — значно впали.
Міні-історія №2: Оптимізація, що відплатилась
Інша організація вирішила бути хитрою: увімкнули балонування скрізь і зменшили базові розміри ВМ, роблячи ставку на «Linux використовує кеш і може його віддати». Вони технічно були праві, але операційно необережні.
Декілька тижнів виглядало добре. Вони вмістили більше ВМ на хості. Фінанси посміхались. Потім рутинний деплой додав нову збірку, що підняла пам’ять для фонового індексера. Не витік — просто більше структур даних.
Під балонуванням ці ВМ жили на межі. Коли індексер піднявся, гість віддав кеш і почав свапити. Пропускна здатність індексера впала, отже він працював довше і довше залишався «гарячим» по пам’яті. Тим часом swap I/O вдарив по тому самому пулу сховища, що використовувався для томів баз даних.
«Оптимізація» перетворилась на інцидент з деградацією для кількох сервісів: веб-запити таймаутилися, коміти в базі сповільнились, і on-call дивився на графіки сховища, намагаючись зрозуміти, чому write latency підскочила під час «низького трафіку». Насправді це не був низький трафік — це був високий свап.
Вони відкотили деплой і побачили часткове відновлення, але справжнє рішення було архітектурним: розділити шари за контрактом пам’яті, встановити підлоги балонування і застосувати per-service memory caps, щоб фонове завдання не могло перетворити весь кластер на повільний каток.
Міні-історія №3: Нудна, але правильна практика, що врятувала день
Ця історія менш драматична, і саме в цьому її сенс. Команда запускала Ubuntu 24.04 гості для внутрішніх CI-раннерів і невеликого продакшн API. Вони мали жорстке правило: продакшн-ВМ мали балонування вимкненим, свап обмежено, а системні ліміти service-ів визначені. CI-раннери були best-effort і одноразові.
Одного дня кластер гіпервізорів відчув несподіваний тиск пам’яті після оновлення прошивки вендора, що змінило характеристики енергоспоживання/продуктивності. Хост почав агресивно відбирати пам’ять. Декілька best-effort ВМ одразу сповільнились.
Продакшн залишився стабільним. Не тому, що мав «більше пам’яті», а тому, що мав реальну резервацію і без балонування reclaim. API-ВМ мали невеликий swap, низький swappiness і memory.max на службі обробки логів. Коли тиск зріс, фоновий юніт помер і перезапустився. Латентність API ледве зрушилася.
Звіт по інциденту був короткий: «Хостовий тиск пам’яті вплинув на best-effort шар. Продакшн не зачеплено завдяки резерваціям і лімітам сервісів.» Без геройства, без риття ядра, без емоційних Slack-ланцюжків.
Вони не отримали призу за це. Вони продовжували випускати фічі за графіком, поки інші знову вчилися, що передбачуваність купується обмеженнями.
Поширені помилки: симптом → коренева причина → виправлення
1) Симптом: середнє навантаження злітає, використання CPU виглядає помірним
Коренева причина: Тиск пам’яті, який зупиняє роботу, і I/O wait через свапінг. Load включає завдання, що чекають на I/O і reclaim, а не лише виконання CPU.
Виправлення: Підтвердіть через vmstat (si/so), iostat (await/util) і PSI. Потім зменшіть балонування і/або додайте запас пам’яті. Зменшувати swappiness — лише після фікса контракту.
2) Симптом: «Ми налаштували 16 GB, але MemTotal ≈ 8 GB»
Коренева причина: Балонування/конфігурація hotplug означає, що гість наразі не тримає максимум пам’яті.
Виправлення: Встановіть мінімум/резервацію балона на гіпервізорі для цієї ВМ або вимкніть балонування для цього шару. Перевірте MemTotal і статистику балона після змін.
3) Симптом: використання swap продовжує рости, навіть якщо трафік стабільний
Коренева причина: Робочий набір перевищує RAM після вилучення балона; ядро свапить анонімні сторінки, щоб вижити.
Виправлення: Збільште гарантовану пам’ять або зменшіть reclaim. Якщо ріст пов’язаний з одним сервісом — обмежте його memory.max і memory.swap.max.
4) Симптом: випадкові вбивства процесів, що виглядають «непередбачувано»
Коренева причина: systemd-oomd або cgroup memory OOM застосовує ліміти (або правила за замовчуванням для slice-ів).
Виправлення: Зробіть контролі пам’яті явними: встановіть MemoryMax/High на сервісах, визначте slice-и і вирішіть, які компоненти повинні помирати першими. Перегляньте конфіг oomd замість того, щоб припускати лише kernel OOM.
5) Симптом: інцидент латентності сховища без очевидного важкого навантаження
Коренева причина: Churn свапу від ВМ генерує постійний дрібний I/O. На спільному сховищі це виглядає як «шумний сусід».
Виправлення: Знайдіть ВМ, що свапить, через iostat/vmstat в гості й метрики хоста. Припиніть тиск пам’яті. Тюнінг сховища не виправить тривале свапування.
6) Симптом: «Ми вимкнули swap і тепер падаємо частіше»
Коренева причина: Немає буфера для транзієнтних сплесків; також немає пер-сервісної ізоляції, тож один сплеск викликає системний OOM.
Виправлення: Поновіть невеликий swap або zram, але додайте cgroup-ліміти, щоб піки були локалізовані. Віддавайте перевагу fail-fast для некритичних сервісів з MemorySwapMax=0.
7) Симптом: високий CPU у kswapd і часті затримки під час GC або компактації
Коренева причина: Накладні витрати reclaim і компактації під тиском пам’яті, потенційно посилені поведінкою THP.
Виправлення: Спочатку додайте запас і зменшіть балонування. Потім розгляньте зміну режиму THP для конкретних навантажень, якщо компактація стає постійною статтею витрат.
Контрольні списки / покроковий план
Покроково: стабілізувати ВМ з активним свапом прямо зараз
- Доведіть, що це тиск пам’яті: запустіть
free -h,vmstat 1 10іcat /proc/pressure/memory. Якщо PSI full нетривіальний і si/so активні — це swap-шторм. - Знайдіть жаднюгу: використайте
psвідсортований за RSS. Підтвердіть, чи це один процес, чи тотальний тиск. - Перевірте балонування: підтвердіть, що virtio_balloon завантажений і подивіться статистику балона. Якщо MemTotal нижчий, ніж очікувалося, вважайте це вилученням пам’яті до доведення зворотного.
- Зменшіть негайну шкоду: якщо один фоновий сервіс винен — обмежте його через
MemoryMaxі за потребиMemorySwapMax. Перезапустіть сервіс, щоб відновити стабільний стан. - Зупиніть пошкодження сховища: якщо свап б’є диск, зменшіть навантаження або тимчасово масштабніть. Не «налаштовуйте iostat» як основну дію.
- Закріпіть виправлення: вирішіть контракт пам’яті для ВМ і налаштуйте резервації/мінімум балона та гостеві ліміти відповідно.
Контрольний список: рішення конфігурації, що запобігають повторним інцидентам
- Для кожної ВМ: класифікуйте як guaranteed/burstable/best-effort. Запишіть.
- Для guaranteed ВМ: вимкніть балонування або встановіть високий мінімум. Уникайте сильного свапу; тримайте невеликий safety swap, якщо це прийнятно.
- Для burstable: встановіть мінімум балона, використовуйте cgroup memory.high/max для відомих хогів і розгляньте обмеження свапу.
- Для best-effort: дозволяйте балонування, але впровадьте повторні спроби й автоматизацію. Розглядайте OOM як нормальний режим відмови.
- Моніторинг: алертуйте по PSI memory full, стійкому swap-out rate і стрибках disk await/util, корельованих зі свапом.
- Зміни в політиці: будь-яка зміна overcommit на хості — це продакшн-зміна. Пропускайте її через той самий рев’ю-процес, що й міграцію бази даних.
Контрольний список: «не робіть гірше» під час інциденту
- Не перезавантажуйте першим ділом. Ребут може приховати причину і гарантувати простій.
- Не збільшуйте розмір swap як первинну реакцію. Часто це лише подовжує вікно страждань.
- Не «скидайте кеш» рефлекторно. Якщо проблема в анонімній пам’яті, видалення кешу не допоможе і може збільшити читання з диска.
- Не міняйте п’ять sysctl одночасно. Ви не дізнаєтесь, що допомогло, і майбутній ви за це подякує не скаже.
Питання й відповіді
1) Чи завжди балонування пам’яті погане?
Ні. Воно корисне для консолідації й сплескових навантажень. Погано, коли ви ставитесь до нього як до невидимого і дозволяєте йому відбирати пам’ять у сервісів з критичними вимогами до латентності або стану.
2) Як я знаю, що причиною є балонування, а не витік у додатку?
Шукайте невідповідність між «що ви думаєте, що має ВМ» і MemTotal у /proc/meminfo, плюс зміну статистики virtio balloon. Витік показує стійке зростання RSS у процесу, незалежно від змін цілі балона.
3) Чи вимикати swap на Ubuntu 24.04 ВМ, щоб запобігти swap-штормам?
Іноді, але рідко як перший крок. swap-off без резервацій пам’яті і per-service лімітів перетворює «повільно й деградовано» на «швидко і мертво». Краще малий swap + ізоляція, або zram там, де доречно.
4) Який є найкращий метрик для алерта?
Memory PSI «full», що тримається вище невеликого порогу, важко ігнорувати, бо прямо вимірює час, витрачений на затримки через тиск пам’яті. Поєднуйте з показником swap-out rate.
5) Чому в UI гіпервізора виглядає, що VM має багато пам’яті, але гість свапить?
Бо UI часто показує налаштований максимум пам’яті, а не поточний ефективний обсяг після балонування. Довіряйте погляду гостя і статистиці балона.
6) Чи вирішує зменшення vm.swappiness проблеми балонування?
Воно може зменшити, наскільки охоче ядро свапитиме, але не створить RAM з повітря. Якщо балонування відбирає забагато пам’яті, ви все одно відчуєте біль від reclaim — просто в іншому порядку.
7) Чи зручно робити zram за замовчуванням у ВМ?
Залежить. zram міняє диск I/O на CPU. На системах із малою завантаженістю CPU, але переповненим сховищем — це виграш. На CPU-завантажених навантаженнях (або коли CPU steal високий) це може погіршити хвостову латентність.
8) Як розмірювати swap у ВМ, яка не має трястись?
Маленький: стільки, щоб поглинути короткі сплески (або зберегти дамп), але не стільки, щоб дозволити ВМ «виживати» в постійно деградованому стані. Потім використовуйте cgroup swap caps для відомих порушників.
9) Яка найкраща практика для баз даних під балонуванням?
Не балонуйте їх. Резервуйте пам’ять. Якщо доводиться балонувати — встановіть високий мінімум і переконайтесь, що налаштування бази (buffers, caches) не спираються на те, що максимум завжди присутній.
10) Чому все повільніє, навіть якщо лише один сервіс жадний до пам’яті?
Бо глобальний reclaim і swap I/O впливають на всю систему: CPU йде на reclaim, черги I/O заповнюються, і латентність каскадує. Локалізуйте використання пам’яті на рівні сервісів.
Висновок: практичні наступні кроки
Якщо Ubuntu 24.04 у ВМ дивує вас swap-штормами через балонування, ремедіум — не магічний sysctl. Це явний контракт пам’яті та його забезпечення на потрібних шарах.
- Протягом 30 хвилин: пройдіть швидкий план діагностики, зафіксуйте
free,vmstat, PSI іiostat. Доведіть, чи триває свап і чи причетне балонування. - Протягом дня: класифікуйте ВМ як guaranteed/burstable/best-effort і відрегулюйте мінімум балона або вимкніть балонування для гарантованих шарів.
- Протягом тижня: додайте cgroup v2 обмеження пам’яті для кількох сервісів, що можуть домінувати, і обмежте свап для тих сервісів, де «fail fast» кращий за «повільно назавжди».
- Завжди: алертуйте по PSI memory full і стійкому swap-out. Розглядайте swap-шторми як окремий клас інцидентів, а не як таємницю.
Балонування — прийнятне, коли ви можете терпіти сюрпризи. Продакшн зазвичай не може. Встановлюйте ліміти серйозно, і ваше сховище перестане кричати о 3-й ранку.