Сторінка відповідає. Pod-и зелені. Логи контейнерів нудні. Тоді як ваш хост кричить:
середнє навантаження в стратосфері, SSH чекає 30 секунд, щоб відобразити символ, а kswapd їсть CPU як за гонорар за цикл.
Це особливий тип інциденту в продакшені, коли все «працює» аж до моменту, коли бізнес помічає латентність,
тайм‑аути та загадково «повільні бази даних». Ласкаво просимо до swap‑шторму: без явного краху, лише повільний розпад.
Що таке swap-шторм (і чому він вас обманює)
Swap‑шторм — це тривалий тиск на пам’ять, який змушує ядро постійно виштовхувати сторінки з RAM у swap,
а потім знову підвантажувати їх, з переповторенням циклу. Це не просто «трохи використовують swap». Це коли система витрачає стільки часу
на переміщення сторінок, що корисна робота відходить на другий план.
Неприємність у тому, що багато застосунків продовжують «працювати». Вони відповідають, але повільно. Вони повторюють запити. Вони тайм‑аутяться й перезапускаються.
Оркестратор бачить процеси живими, перевірки здоров’я ледве проходять і думає, що все нормально.
Люди помічають першими: усе відчувається липким.
Два сигнали, що відділяють «використання swap» від «swap-шторму»
- Сплески major page faults (читання сторінок із swap на диск).
- PSI memory pressure показує тривалі затримки (задачі чекають на reclaim / IO).
Якщо дивитися лише на «відсоток використаного swap», вас можуть ввести в оману. Swap може бути зайнятий на 20% і стабільний тижнями без драм.
Навпаки, при 5% використання swap може відбуватися шторм, якщо робочий набір постійно міняється.
Цікаві факти й історичний контекст (бо в цього безладу є історія)
- Раніше OOM у Linux був відомо грубим. OOM‑кілер ядра еволюціонував десятиліттями; він і досі дивує під навантаженням.
- cgroups з’явилися, щоб зупинити «галасливих сусідів». Вони були створені для шардів систем задовго до популярності контейнерів.
- Облік swap у cgroups був контроверсійним. Це додає накладні витрати й мав реальні баги; багато платформ вимикали його за замовчуванням.
- Kubernetes історично відмовлявся від swap. Не тому, що swap—зло, а тому, що передбачувана ізоляція пам’яті ускладнюється зі swap.
- Число «вільної пам’яті» неправильно розуміють з незапам’ятних часів. Linux агресивно використовує RAM для page cache; низький free часто є нормою.
- Pressure Stall Information (PSI) — відносно новий інструмент. Це один з найкращих сучасних інструментів для виявлення «очікування пам’яті» без гадань.
- SSD‑swap зробив шторми тихішими, але не безпечнішими. Швидший swap зменшує біль… поки не маскує проблему і ви не натрапите на write amplification та провали латентності.
- Параметри overcommit — це культурний артефакт. Linux за замовчуванням допускає більше алокацій, ніж фактично торкається код; це працює, поки не перестає.
Чому контейнери виглядають здоровими, поки хост вмирає
Контейнери не мають власного ядра. Це процеси, згруповані cgroups і просторами імен.
Тиск на пам’ять керує хостове ядро, і саме воно робить reclaim і swap.
Ось ілюзія: контейнер може продовжувати виконуватися й відповідати, поки хост інтенсивно свопить, бо
процеси контейнера все ще заплановані і роблять прогрес — але за жахливу ціну.
Використання CPU контейнера може навіть виглядати нижчим, бо він заблокований на IO (swap‑in), а не витрачає CPU.
Основні режими відмов, що породжують шаблон «контейнери норм, хост плавиться»
- Немає лімітів пам’яті (або вони неправильні). Один контейнер росте, поки хост пересилає й свопить усіх.
- Ліміти задані, але swap неконтрольований. Контейнер тримається в межах RAM‑капа, але все одно спричиняє глобальний reclaim через патерни page cache і спільні ресурси.
- Page cache та файловий IO домінують. Контейнери з інтенсивним IO можуть виштовхнути кеш, змушуючи інші робочі навантаження вичерпувати пам’ять і свопитися.
- Overcommit + піки навантаження. Багато сервісів одночасно алокує агресивно; ви не отримуєте миттєвого OOM, натомість — churn.
- Політика OOM уникає вбивань. Система свопить замість швидкого відмовлення, жонглюючи доступністю у найгірший спосіб.
Ще одна деталь: телеметрія на рівні контейнера може вводити в оману. Деякі інструменти показують використання пам’яті cgroup, але не біль хостового reclaim.
Ви бачите контейнери «в межах лімітів», поки хост проводить весь день, переставляючи сторінки.
Жарт #1: Swap — як складське приміщення: здається, що все організовано, поки не зрозумієш, що ти щомісяця платиш за те, чого потребуєш щодня.
Основи пам’яті Linux, які вам справді потрібні
Вам не треба зубрити код ядра. Потрібні кілька концептів, щоб міркувати про swap‑шторм без забобонів.
Робочий набір проти виділеної пам’яті
Більшість застосунків алокують пам’ять, яку не завжди активно торкаються. Ядру байдуже на «виділену» пам’ять, йому важливі
«останні використані» сторінки.
Ваш робочий набір — це сторінки, які ви торкаєтеся часто, і їх виштовхування боляче.
Swap‑шторм відбувається, коли робочий набір не вміщується, або коли він вміщується, але ядро змушене весь час перемішувати сторінки через
конкурентні вимоги (page cache, інші cgroup, або один агрегатор, що постійно засмічує пам’ять).
Анонімна пам’ять проти файл‑підтримуваної
- Анонімна: heap, stack — підлягає swap.
- Файл‑підтримувана: page cache — можна витиснути без swap (просто перечитати з файлу), якщо сторінки не dirty.
Коли ви запускаєте бази даних, кеші, JVM і сервіси з інтенсивними логами на одному хості, анонімне і файл‑підтримуване звільнення
пам’яті взаємодіють доволі «цікаво». «Цікаво» тут означає «постмортем о 2‑й ночі».
Reclaim, kswapd і direct reclaim
Ядро намагається звільняти пам’ять у фоні (kswapd). При сильному тиску процеси самі можуть увійти в
direct reclaim — вони зависають, намагаючись звільнити пам’ять. Саме там губиться латентність.
Чому swap‑шторм відчувається як проблема CPU
Reclaim спалює CPU. Стиснення може спалювати CPU (zswap/zram). Підвантаження сторінок також витрачає CPU і IO.
І потоки вашого застосунку можуть бути заблоковані, що заплутує графіки використання: низький user CPU, високий system CPU, високий IO wait.
cgroups, Docker і гострі краї навколо swap
Docker використовує cgroups для обмеження ресурсів. Але «обмеження пам’яті» — це набір варіантів залежно від версії ядра,
cgroup v1 проти v2 і налаштувань Docker.
cgroup v1 проти v2: практичні відмінності для swap‑штормів
У cgroup v1 пам’ять і swap керувалися окремими параметрами (memory.limit_in_bytes, memory.memsw.limit_in_bytes),
і облік swap міг бути вимкнений. У cgroup v2 пам’ять більш уніфікована й інтерфейс чистіший:
memory.max, memory.swap.max, memory.high, плюс метрики тиску.
Якщо ви на cgroup v2 і не використовуєте memory.high, ви втрачаєте один із найкращих інструментів, щоб не дозволити одній cgroup перетворити
хост на своп‑тостер.
Прапори пам’яті Docker: що вони справді означають
--memory: жорсткий ліміт. При перевищенні cgroup спробує звільнити; якщо не вийде — OOM (всередині тієї cgroup).--memory-swap: у багатьох налаштуваннях — ліміт total memory+swap. Семантика варіюється; на деяких системах ігнорується без обліку swap.--oom-kill-disable: майже завжди погана ідея в продакшені. Це заохочує хост страждати довше.
Те, що контейнер «працює», поки хост плавиться, часто — результат політичного рішення:
ми сказали системі «не вбивати, просто намагайся сильніше». Ядро послухалося.
Цитата, яку варто втатуйте у свої runbook‑и
«Надія — це не стратегія.» — перефразована думка, поширена в інженерних/операційних колах; сенс залишається.
Швидкий порядок діагностики
Це порядок дій, який я використовую, коли хтось каже «хост повільний», і я підозрюю тиск на пам’ять. Він призначений
допомогти швидко прийняти рішення: вбити, обмежити, перемістити або налагодити.
Спочатку: підтвердіть, що це саме swap‑шторм (а не просто «використовується swap»)
- Перевірте активність swap (швидкості swap‑in/out) і major faults.
- Перевірте PSI memory pressure на предмет тривалих затримок.
- Перевірте, чи не насичена підсистема IO (swap — це IO).
По‑друге: знайдіть винну cgroup/container
- Порівняйте використання пам’яті по контейнерах, включно з RSS та кешем.
- Перевірте, які cgroup тригерять OOM або високе reclaim.
- Шукайте патерни навантажень (зростання heap у JVM, батч‑завдання, сплески логів, компакти, перестроювання індексів).
По‑третє: вирішіть миттєве пом’якшення
- Якщо латентність важливіша за завершення: відмовляйте швидко (жорсткі ліміти, дозволяйте OOM, перезапускайте чисто).
- Якщо завершення важливіше за латентність: ізолюйте (виділені вузли, зменшити swappiness, контрольований swap, повільніше але стабільно).
- Якщо ви в сліпому режимі: додайте PSI + метрики пам’яті по cgroup спочатку. Налаштовувати без видимості — гра в азартні ігри.
Практичні завдання: команди, виводи, рішення
Це команди, які я реально виконую на хості. Кожне завдання включає, що означає вивід і яке рішення воно дозволяє прийняти.
Налаштуйте імена інтерфейсів і шляхи під своє оточення.
Завдання 1: Підтвердити наявність swap і скільки використовується
cr0x@server:~$ free -h
total used free shared buff/cache available
Mem: 62Gi 54Gi 1.2Gi 1.1Gi 6.8Gi 2.3Gi
Swap: 16Gi 12Gi 4.0Gi
Значення: Swap активно використовується (12Gi) і доступна пам’ять низька (2.3Gi). Це не доказ шторму, але підозріло.
Рішення: Перейдіть до метрик активності; одне лише використання swap не дає підстав діяти.
Завдання 2: Виміряти активність swap і тиск сторінок
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 12453248 312000 68000 512000 60 210 120 980 1200 2400 12 18 42 28 0
2 2 12454800 298000 66000 500000 180 640 200 1800 1800 3200 8 22 30 40 0
4 2 12456000 286000 64000 490000 220 710 260 2100 1900 3300 9 23 24 44 0
3 3 12456800 280000 62000 482000 240 680 300 2000 2000 3500 10 24 22 44 0
2 2 12458000 276000 60000 475000 210 650 280 1900 1950 3400 9 23 25 43 0
Значення: Нетривіальні si/so (swap‑in/out) щосекунди і високий wa (IO wait). Це активна пагінація.
Рішення: Вважати це штормом. Далі: визначити, чи насичений IO і яка cgroup створює тиск пам’яті.
Завдання 3: Перевірити PSI на затримки пам’яті (рівень хоста)
cr0x@server:~$ cat /proc/pressure/memory
some avg10=18.40 avg60=12.12 avg300=8.50 total=192003210
full avg10=6.20 avg60=3.90 avg300=2.10 total=48200321
Значення: full тиск означає, що завдання часто стоять у паузі, бо reclaim не встигає. Це сильно корелює з піками латентності.
Рішення: Перестаньте шукати «баги CPU». Це питання пам’яті. Знайдіть винного і обмежте/вбийте/ізолюйте.
Завдання 4: Визначити, чи у вас cgroup v1 чи v2
cr0x@server:~$ stat -fc %T /sys/fs/cgroup
cgroup2fs
Значення: Активний cgroup v2. Ви можете використати memory.high і memory.swap.max.
Рішення: Віддавайте перевагу контролям v2; уникайте старих порад для v1, які тут не застосовні.
Завдання 5: Побачити найбільших споживачів пам’яті по контейнерах
cr0x@server:~$ docker stats --no-stream
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
a1b2c3d4e5f6 api-prod 35.20% 1.8GiB / 2GiB 90.00% 2.1GB / 1.9GB 120MB / 3GB 210
b2c3d4e5f6g7 search-indexer 4.10% 7.4GiB / 8GiB 92.50% 150MB / 90MB 30GB / 2GB 65
c3d4e5f6g7h8 metrics-agent 0.50% 220MiB / 512MiB 42.97% 20MB / 18MB 2MB / 1MB 14
Значення: search-indexer близький до свого ліміту пам’яті і виконує величезний блоковий IO (30GB reads/writes), що може бути збиванням page cache, компактом або spill’ом.
Рішення: Заглибитися в метрики cgroup цього контейнера (reclaim, swap, OOM‑події).
Завдання 6: Перевірити ліміти пам’яті + swap (v2) для підозрілого контейнера
cr0x@server:~$ CID=b2c3d4e5f6g7
cr0x@server:~$ CG=$(docker inspect -f '{{.HostConfig.CgroupParent}}' "$CID")
cr0x@server:~$ docker inspect -f '{{.Id}} {{.Name}}' "$CID"
b2c3d4e5f6g7h8i9j0 /search-indexer
cr0x@server:~$ cat /sys/fs/cgroup/system.slice/docker-$CID.scope/memory.max
8589934592
cr0x@server:~$ cat /sys/fs/cgroup/system.slice/docker-$CID.scope/memory.swap.max
max
Значення: Ліміт RAM — 8GiB, але swap — необмежений (max). Під тиском ця cgroup може інтенсивно використовувати swap.
Рішення: Встановити memory.swap.max або налаштувати Docker, щоб обмежити swap для контейнерів, які не повинні сторити.
Завдання 7: Перевірити події по cgroup: чи ви досягаєте reclaim/OOM?
cr0x@server:~$ cat /sys/fs/cgroup/system.slice/docker-$CID.scope/memory.events
low 0
high 1224
max 18
oom 2
oom_kill 2
Значення: cgroup часто досягала high і мала OOM‑вбивання. Це не «випадкова нестабільність»; це проблема розмірів.
Рішення: Підняти пам’ять, зменшити робочий набір або прийняти рестарти, але запобігти глобальному thrash, обмеживши swap і використавши memory.high.
Завдання 8: Спостерігати використання swap по процесах (знайти справжнього хижака)
cr0x@server:~$ sudo smem -rs swap | head -n 8
PID User Command Swap USS PSS RSS
18231 root java -jar indexer.jar 6144M 4096M 4200M 7000M
9132 root python3 /app/worker.py 820M 600M 650M 1200M
2210 root dockerd 90M 60M 70M 180M
1987 root containerd 40M 25M 30M 90M
1544 root /usr/bin/prometheus 10M 900M 920M 980M
1123 root /usr/sbin/sshd 1M 2M 3M 8M
Значення: Java‑індексер має 6GiB у swap. Це пояснює «контейнер живий, але повільний»: він постійно фолить сторінки.
Рішення: Якщо це навантаження не повинно свопитися — обмежте його й примусово OOM/restart. Якщо повинно — ізолюйте на хості з швидшим swap і меншим contention.
Завдання 9: Перевірити насичення диска (swap — це IO; IO — це латентність)
cr0x@server:~$ iostat -xz 1 3
Linux 6.5.0 (server) 01/02/2026 _x86_64_ (16 CPU)
avg-cpu: %user %nice %system %iowait %steal %idle
10.21 0.00 22.11 41.90 0.00 25.78
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 120.0 12800.0 2.0 1.64 18.2 106.7 210.0 24400.0 8.0 3.67 32.5 116.2 9.80 99.20
Значення: %util близько 100% і високий await. NVMe насичений; swap‑in/out буде чергуватися, спричиняючи затримки скрізь.
Рішення: Негайна міра: зменшити тиск пам’яті (вбити винуватця, знизити конкурентність). Довгостроково: відокремити swap від IO‑шляху або використовувати швидше сховище.
Завдання 10: Подивитися, які процеси застрягли в reclaim або IO wait
cr0x@server:~$ ps -eo pid,stat,wchan:20,comm --sort=stat | head -n 12
PID STAT WCHAN COMMAND
18231 D io_schedule java
19102 D io_schedule java
9132 D io_schedule python3
24011 D balance_pgdat postgres
24022 D balance_pgdat postgres
2210 Ssl ep_poll dockerd
1987 Ssl ep_poll containerd
Значення: Стан D + io_schedule вказує на не переривний сон, що чекає на IO. balance_pgdat натякає на direct reclaim.
Рішення: Ваша латентність — на рівні ядра. Припиніть масштабувати трафік; це погіршить чергу. Зменшіть навантаження або зупиніть винуватця.
Завдання 11: Перевірити логи ядра на OOM і попередження reclaim
cr0x@server:~$ sudo dmesg -T | tail -n 12
[Thu Jan 2 10:14:22 2026] Memory cgroup out of memory: Killed process 18231 (java) total-vm:12422392kB, anon-rss:7023120kB, file-rss:10244kB, shmem-rss:0kB
[Thu Jan 2 10:14:22 2026] oom_reaper: reaped process 18231 (java), now anon-rss:0kB, file-rss:0kB, shmem-rss:0kB
[Thu Jan 2 10:14:25 2026] Out of memory: Killed process 9132 (python3) total-vm:2048320kB, anon-rss:1192200kB, file-rss:9120kB, shmem-rss:0kB
Значення: Відбулись OOM‑вбивання cgroup. Це насправді краще, ніж глобальні swap‑штормі — якщо ваш сервіс може коректно перезапуститися.
Рішення: Підтвердіть політику перезапуску, відкоригуйте ліміти і встановіть очікування: контрольований OOM кращий за неконтрольований колапс хоста.
Завдання 12: Перевірити swappiness хоста і положення overcommit
cr0x@server:~$ sysctl vm.swappiness vm.overcommit_memory vm.overcommit_ratio
vm.swappiness = 60
vm.overcommit_memory = 0
vm.overcommit_ratio = 50
Значення: Swappiness 60 — приблизно за замовчуванням і може бути занадто прагматичним на змішаних контейнерних хостах. Overcommit — евристичний (0).
Рішення: Якщо хост працює latency‑чутливі сервіси, розгляньте зниження swappiness і жорсткіший overcommit, але лише після введення per‑cgroup лімітів.
Завдання 13: Перевірити статус zswap/zram (іноді допомагає, завжди ховає проблему)
cr0x@server:~$ grep -H . /sys/module/zswap/parameters/enabled /sys/block/zram0/disksize 2>/dev/null
/sys/module/zswap/parameters/enabled:Y
Значення: zswap увімкнений; сторінки swap можуть бути стиснені в RAM. Це знижує IO, але підвищує CPU і може маскувати тиск до критичної точки.
Рішення: Залишайте його, якщо це дає стабільність. Не використовуйте як ліцензію запускати необмежені навантаження пам’яті.
Завдання 14: Перевірити припущення Docker daemon і ядра щодо обліку пам’яті
cr0x@server:~$ docker info | sed -n '1,40p'
Client:
Version: 26.1.0
Context: default
Debug Mode: false
Server:
Containers: 38
Running: 33
Paused: 0
Stopped: 5
Server Version: 26.1.0
Storage Driver: overlay2
Cgroup Driver: systemd
Cgroup Version: 2
Kernel Version: 6.5.0
Operating System: Ubuntu 24.04 LTS
OSType: linux
Значення: systemd + cgroup v2. Добре. Ваші контролі є; їх просто треба використовувати.
Рішення: Впроваджуйте політики, що знають про cgroup v2 (memory.high, memory.swap.max), а не старі рекомендації.
Три корпоративні міні‑історії з полів пам’яті
Міні‑історія 1: Інцидент через неправильне припущення
Середня SaaS‑компанія запускала флот індексерів у Docker на кількох потужних хостах. У них був swap ввімкнений «на безпеку»,
і були встановлені ліміти пам’яті для контейнерів. Усі почувалися відповідальними. Усі спали.
Під час навантаженого тижня backlog індексації виріс і інженер підвищив concurency воркерів всередині контейнера.
Контейнер більшість часу тримався під своїм --memory капом, але патерн алокацій став стрибкоподібним:
великі тимчасові буфери, інтенсивний файловий IO і агресивне кешування. Хост почав свопити.
Неправильне припущення було тонким: «Якщо контейнери мають ліміти пам’яті, хост не свопиться в безодню».
Насправді, ліміти запобігають безмежному використанню RAM однією cgroup, але вони не гарантують справедливість на рівні хоста
і не запобігають глобальному reclaim‑болю — особливо коли swap необмежений і шлях IO спільний.
Симптоми були класичними. Латентність API подвоїлася, потім потроїлася. SSH‑логіни зависали. Моніторинг показував «пам’ять контейнера в межах ліміту»,
тож команда годинами шукала налаштування мережі і БД. Нарешті хтось запустив cat /proc/pressure/memory
і історія написала себе сама.
Виправлення не було екзотичним. Вони встановили memory.swap.max для індексерів, додали memory.high щоб їх гальмувати перед OOM,
і перемістили індексування на виділені вузли з власним IO‑бюджетом. Найбільший прогрес приніс нудний крок:
задокументувати, що «ліміти пам’яті не є захистом хоста» і зробити це вимогою при деплої.
Міні‑історія 2: Оптимізація, яка обернулася проти
Інша організація мала мульти‑тенантний логінговий пайплайн. Щоб зменшити навантаження на диск, вони увімкнули zswap і збільшили розмір swap.
Початкові результати виглядали чудово: менше сплесків записів, плавніші IO‑графіки, менше миттєвих OOM.
Потім стався дрібний інцидент: клієнт увімкнув детальні логи плюс компресію на рівні застосунку.
Обсяг логів виріс, CPU піднявся, і тиск на пам’ять зріс. З zswap ядро спочатку стискало сторінки у RAM.
Це зменшило IO swap, але збільшило CPU‑час на стиснення і reclaim.
На панелі це виглядало як «насичення CPU», а не «помилка пам’яті». Команда підправила ліміти CPU, додала ядра та збільшила хости.
Система погіршилася. Більше RAM означало більше кешів і більше churn; більше CPU означало, що zswap міг стиснути більше, відтягуючи очевидний провал.
Джиттер латентності став постійним.
Провал не був у zswap сам по собі. Провал полягав у тому, що його сприймали як оптимізацію продуктивності, а не як буфер тиску.
Справжня проблема — неконтрольована розростання пам’яті в парсер‑стадії і відсутність memory.high для backpressure. Своп був симптомом,
zswap — підсилювач, що ускладнив виявлення.
Вони виправили це, встановивши суворі ліміти по кожній стадії, додавши backpressure у пайплайн і використовуючи zswap тільки на вузлах,
призначених для батч‑обробки, де латентність не важлива. Також змінили алерт: тривале PSI memory > порог стало page‑worthy подією.
Міні‑історія 3: Нудна, але правильна практика, що врятувала ситуацію
Фінансова команда запускала суміш API для клієнтів і нічний батч‑звір у тому ж Kubernetes‑кластері.
Вони були надзвичайно консервативні: request/limit по сервісах були вказані, пороги евікшенів переглядалися щоквартально,
і у кожного pool‑а вузлів була письмова «політика swap». Це не було захопливо. Саме тому в них не було захопливих аварій.
Однієї ночі батч‑завдання почало використовувати більше пам’яті після оновлення бібліотеки від вендора. Воно росло поступово, не вибухово.
В команді з менш дисципліною це перетворилося б на повільний swap‑шторм і інцидент з невизначеним звітом.
Їхня система зробила банальну річ: завдання досягло ліміту пам’яті, було OOM‑вбите і перезапущене з обмеженою паралельністю
(попередньо визначений fallback). Батч виконався довше. API лишалися швидкими. On‑call отримав конкретний алерт:
«batch job OOMKilled; host PSI normal».
Наступного ранку вони відкотили бібліотеку, зареєстрували баг у upstream і назавжди підкорегували request пам’яті для цього job.
Ніхто не писав героїчних повідомлень у Slack. Оце й суть. Правильна практика — ліміти плюс контрольований провал — запобігла явищу «хост плавиться».
Рішення, які працюють: ліміти, OOM, swappiness і моніторинг
1) Встановлюйте ліміти пам’яті на кожен важливий контейнер
Відсутність ліміту — це теж рішення. Це рішення, що хост буде вашим лімітом. Хост — поганий ліміт, бо він виходить з ладу колективно: всі робочі навантаження страждають, потім усе колапсує.
Використовуйте per‑service ліміти, підібрані під робочий набір, а не під фантазії про пікові алокації. Для JVM це означає свідомо задавати heap
і залишати запас для off‑heap і native алокацій.
2) Використовуйте memory.high (cgroup v2) щоб гальмувати перед OOM
memory.max — це обрив. memory.high — це обмеження швидкості. Коли ви встановлюєте memory.high, ядро починає reclaim і гальмувати алокації після перевищення,
що допомагає зменшити глобальний thrash.
Для стрибкоподібних сервісів memory.high, трохи нижчий за memory.max, часто дає систему, яка повільніше поводиться під тиском, але лишається контрольованою, замість хаотичної.
3) Обмежуйте swap по навантаженню або вимикайте його для latency‑чутливих сервісів
Необмежений swap — шлях до серверу, що «живий», але непридатний. Для сервісів, що мають бути чутливими, віддавайте перевагу або нульовому використанню swap, або дуже малому допуску.
Якщо swap потрібен для батчових навантажень — ізолюйте їх. Swap не безкоштовний; це рішення обміняти latency на завершення.
Змішувати «має бути швидко» з «може бути повільно» на одному swap‑підтримуваному вузлі — соціологічний експеримент.
4) Знизьте swappiness (обережно) на змішаних контейнерних хостах
vm.swappiness контролює, наскільки агресивно ядро свопить анонімну пам’ять проти витіснення page cache.
На хостах з базами даних або low‑latency сервісами нижче значення (наприклад 10 або 1) може зменшити свопінг «гарячих» сторінок.
Не копіюйте vm.swappiness=1 усюди механічно. Якщо ваш хост покладається на swap щоб уникнути OOM і ви знизите swappiness не виправивши розміри,
ви просто отримаєте заміну swap‑шторму на OOM‑шторм.
5) Віддавайте перевагу контрольованому OOM над неконтрольованим thrash
Це те, що люди емоційно не люблять, але операційно — так: для багатьох сервісів перезапуск дешевший за пагінацію.
Якщо сервіс не може працювати в межах бюджету, вбивство його — чесна дія.
Уникайте відключення OOM‑кілера для контейнерів, якщо ви не розумієте наслідки. Вимкнення OOM‑kill — шлях перетворити один поганий процес на інцидент усього хоста.
Жарт #2: Вимкнути OOM‑кілер — як прибрати пожежну сигналізацію через те, що вона голосна — тепер ви можете з комфортом спостерігати вогонь.
6) Слідкуйте за PSI і метриками reclaim, а не лише за «використана пам’ять»
Найкорисніші алерти — ті, що говорять «ядро затримує задачі».
PSI дає це безпосередньо. Поєднуйте його з швидкостями swap‑in/out і латентністю диска.
7) Розділяйте IO‑шляхи, коли swap неминучий
Якщо swap і ваш основний робочий шлях використовують один диск, ви створюєте зворотний зв’язок: swap викликає черги IO,
черги IO уповільнюють застосунки, повільні застосунки тримають пам’ять довше, тиск пам’яті зростає, swap зростає. Вітаємо, ви побудували карусель.
На серйозних системах swap має лежати на швидкому сховищі, іноді на окремих пристроях, або використовують zram для обмеженого аварійного запасу
(але з урахуванням CPU‑запасу).
8) Зробіть бюджет пам’яті частиною розгортання, а не постмортему
Корпоративна реальність: команди не «запам’ятають додати ліміти пізніше». Вони відправлять сьогодні. Ваше завдання — перетворити це на запобіжник:
CI‑перевірки для Compose, admission‑політики для Kubernetes і рантайм‑алерти «немає ліміту».
Поширені помилки: симптом → корінь → виправлення
1) Симптом: Середнє навантаження хоста величезне; CPU здається помірним
Корінь: Потоки заблоковані в IO wait під час swap‑in або direct reclaim. Load рахує runnable + uninterruptible tasks.
Виправлення: Підтвердіть vmstat/iostat/ps (D‑стан). Негайно зменште тиск пам’яті; обмежте/вбийте винуватця; додайте memory.high.
2) Симптом: Контейнери показують «в межах ліміту», але хост інтенсивно свопить
Корінь: Ліміти є, але swap необмежений, churn page cache змушує глобальний reclaim, або кілька контейнерів разом перевищують здатність хоста.
Виправлення: Встановіть per‑cgroup swap‑ліміти (memory.swap.max) і реалістичні бюджети пам’яті. Не передплатовуйте ресурси без явної політики.
3) Симптом: Випадкові сплески латентності по незалежних сервісах
Корінь: Глобальний reclaim і черги IO створюють крос‑сервісну залежність. Один меморі‑винуватець карає усіх.
Виправлення: Ізолюйте шумні навантаження на окремі вузли/пули; застосуйте ліміти; слідкуйте за PSI.
4) Симптом: «Ми додали більше swap і стало гірше»
Корінь: Більше swap збільшує час, протягом якого хост може залишатися в деградірованому стані пагінації, підсилюючи хвостову латентність і операційну плутанину.
Виправлення: Розглядайте swap як аварійний буфер, а не як ємність. Віддавайте перевагу OOM/restart для latency‑чутливих сервісів, або ізолюйте батч‑роботи.
5) Симптом: Відбуваються OOM‑вбивання, але хост досі повільний
Корінь: Swap залишається заповненим; reclaim і refaulting тривають; черга IO ще не порожня; кеші фрагментовані.
Виправлення: Після видалення винуватця дайте системі час на відновлення; зменшіть IO‑тиск; розгляньте тимчасове скидання кешів лише як крайній захід і усвідомлюючи його вплив.
6) Симптом: «Вільної пам’яті» близько нуля; хтось панікує
Корінь: Linux використовує вільну RAM для кешу; низький free — нормальний стан, якщо available низький і PSI низький.
Виправлення: Навчіть команди дивитися на available і PSI, а не на free. Аларміть за тиском і churn, а не за естетикою.
7) Симптом: Після увімкнення zswap/zram CPU зріс і пропускна здатність впала
Корінь: Накладні витрати стиснення плюс продовжений тиск пам’яті. Ви перемістили витрати з диска на CPU.
Виправлення: Увімкнюйте лише коли є CPU‑запас; обмежте використання swap; виправте реальний бюджет пам’яті.
8) Симптом: «Swappiness=1 вирішив проблему» (поки не наступного тижня)
Корінь: Зниження swap тимчасово приховало погане розмірування пам’яті; тиск все ще є і може тепер стати різким OOM.
Виправлення: Розміріруйте робочі навантаження, встановіть ліміти, додайте memory.high/backpressure. Налаштовуйте swappiness як завершальний крок, а не перший акт.
Чеклісти / покроковий план
Негайна реакція на інцидент (15 хвилин)
- Запустіть
vmstat 1іcat /proc/pressure/memory. Підтвердіть активну пагінацію + затримки. - Запустіть
iostat -xz 1. Підтвердіть насичення диска / await. - Знайдіть винуватця:
docker stats, потім per‑process swap зsmemабо події cgroup. - Міри пом’якшення:
- Зменшити concurency / трафік.
- Зупинити контейнер‑винуватець або перезапустити його з меншим споживанням пам’яті.
- За потреби тимчасово перемістити винуватця на виділений хост.
- Підтвердіть відновлення: швидкості swap‑in/out падають,
fullPSI наближається до ~0, IO await нормалізується.
План стабілізації (той самий день)
- Встановіть ліміти пам’яті для всіх контейнерів без них.
- На cgroup v2: додайте
memory.highдля стрибкоподібних навантажень, щоб зменшити thrash. - Обмежте swap там, де це доречно (
memory.swap.max), особливо для latency‑чутливих сервісів. - Перегляньте використання
--oom-kill-disable; видаліть, якщо немає вагомої причини. - Розділіть ролі вузлів: батч проти latency‑чутливих сервісів не мають ділити один memory/IO‑фейт.
План підсилення (на наступний спринт)
- Додайте алерти на PSI memory (
someіfull) із тривалими порогами. - Додайте алерти на швидкості swap‑in/out, major faults і disk await.
- Реалізуйте policy‑as‑code: лінтери Compose або admission‑контролі Kubernetes, що вимагають memory limits.
- Документуйте бюджети пам’яті по сервісах і поведінку при рестарті (що відбувається на OOM, як швидко відновлюється).
- Протестуйте під навантаженням із тиском на пам’ять: імітуйте spike concurrency і перевірте, що хост лишається інтерактивним.
Питання та відповіді
1) Чи завжди swap поганий для Docker‑хостів?
Ні. Swap — інструмент. Він корисний як аварійний буфер і для батчових навантажень. Він небезпечний, коли стає костилем для
недостатньо розмірених сервісів і робить хост «живим, але непридатним».
2) Чому мої контейнери показують низький CPU, а система повільна?
Тому що вони заблоковані. У swap‑штормі потоки часто сидять у IO wait або direct reclaim. Ваш графік CPU не покаже «очікування на диск»,
якщо ви не дивитесь на iowait, PSI або заблоковані процеси.
3) Чи варто вимкнути swap, щоб запобігти шторми?
Якщо ви запускаєте latency‑чутливі сервіси і маєте хороші ліміти, відключення swap може підвищити передбачуваність.
Але це також збільшує ймовірність OOM. Правильний підхід зазвичай: спочатку встановіть ліміти й політики, а потім вирішіть стосовно swap.
4) У чому різниця в користувацькому впливі між OOM і swap‑трясінням?
OOM — різкий і гучний: процес помирає і перезапускається. Swap‑трясіння — довге і підступне: усе повільне, тайм‑аути каскадують,
виникають вторинні відмови. У багатьох системах контрольований OOM є меншим злом.
5) Чому додавання RAM іноді не вирішує проблему?
Більше RAM допомагає лише якщо робочий набір вміщується і ви зменшите churn. Якщо навантаження «заповнює» пам’ять (кеші, heap, компакти),
ви можете отримати той самий тиск, але на більшому полі.
6) Як встановити swap‑ліміти для Docker‑контейнерів на cgroup v2?
Прапори Docker можуть бути непослідовними в різних налаштуваннях. Надійний шлях — використовувати контролі cgroup v2 напряму через systemd‑sсopes
або runtime‑налаштування, переконавшись, що memory.swap.max встановлено для cgroup контейнера. Перевіряйте читанням файлу в /sys/fs/cgroup.
7) Чому «вільна пам’ять» низька, коли система начебто в порядку?
Linux використовує вільну RAM під кеш. Цей кеш підлягає reclaim. Дивіться «available» і метрики тиску, а не «free».
Низький free + низький PSI зазвичай нормально.
8) Які метрики слід відстежувати, щоб рано помітити swap‑шторм?
Тривалий PSI memory (/proc/pressure/memory), швидкості swap‑in/out, major page faults, disk await/%util,
та per‑cgroup memory events (high/max/oom). Аларми мають спрацьовувати на тривалі умови, а не на одно‑секундні сплески.
9) Чи може overlay2 або поведінка файлової системи сприяти swap‑штормам?
Посередньо — так. Інтенсивний churn метаданих файлової системи і write amplification може наситити IO, роблячи swap‑in/out набагато болючішим.
Це не створює тиск пам’яті безпосередньо, але прискорює перетворення тиску пам’яті в системний outage.
10) Чи кращий zram за дисковий swap для контейнерів?
zram уникає дискового IO через стиснення в RAM. Він може пом’якшити шторми, якщо є CPU‑запас, але це все ще swap — латентність збільшується,
і він може ховати проблеми з ємністю. Використовуйте як буфер, а не як дозвіл на погане розмірування.
Практичні наступні кроки
Якщо ваш Docker‑хост «плавиться», тоді як контейнери «працюють», припиніть дискусії про моральну сторону swap.
Сприймайте його як інструмент: борговий інструмент продуктивності з непередбачуваною процентною ставкою і небезпечним компаундінгом.
Зробіть ці кроки наступними, у такому порядку:
- Інструментуйте тиск: додайте алерти і дашборди на базі PSI разом із swap‑активністю і IO‑латентністю.
- Бюджет пам’яті по сервісу: встановіть реалістичні ліміти; приберіть «необмежено» як дефолт.
- Керуйте поведінкою swap: обмежуйте swap по навантаженню (особливо для latency‑чутливих) і розгляньте
memory.highяк throttle. - Віддавайте перевагу контрольованому провалу: дозвольте cgroup OOM + рестарт для сервісів, що можуть відновитися, замість глобального thrash.
- Ізолюйте проблемні навантаження: індексація, компакти і все, що любить роздуватися — не повинно ділити вузли з низьколатентними API.
Ваша мета — не «ніколи не використовувати swap». Ваша мета — «не дозволити swap вирішувати часову шкалу інциденту». Ядро робитиме те, що ви просите.
Просіть те, що можна пережити.