Вам дзвонять на пейдж: «Хост закінчився памʼяттю». Ви заходите по SSH, запускаєте free -h, і воно каже, що памʼять «used». Дуже корисно. Наче пожежний датчик, який тільки каже «ДИМ».
Трюк у тому, щоб перестати сприймати ОЗП як одну купу. Linux розбиває її на відсіки, що мають оперативне значення: анонімна памʼять (процеси), файловий кеш (швидкий ввід/вивід), slab ядерних кешів (метадані) і обмеження, накладені cgroups. За десять хвилин зазвичай можна ідентифікувати справжнього «їдця» — і вибрати правильне рішення замість класичного підходу: перезапустити сервер і молитися.
Швидкий плейбук діагностики
Це порядок дій, який не дозволяє вам гнатись за примарами. Ви найперше шукаєте найбільший відсік, потім звужуєте до винуватця, а наприкінці валідовуєте даними з власного обліку ядра.
Перше: це реальний тиск памʼяті чи лише файловий кеш?
- Перевірте
free -hі зосередьтесь наavailable, а не наused. - Перевірте активність swap (
vmstat/sar) і великі page faultʼи, якщо вони у вас є.
Друге: чи ядро під тиском памʼяті (ризик OOM) і чому?
- Подивіться
dmesgна предмет логів OOM killer і яка cgroup спрацювала. - Перевірте
/proc/meminfoдля розподілу anon vs file vs slab.
Третє: ідентифікуйте головних споживачів за правильною метрикою
- Почніть з RSS на процес, щоб знайти очевидних «жерців» (
ps). - Потім використайте PSS, коли спільна памʼять робить RSS оманливим (
smemабо/proc/*/smaps_rollup). - Якщо задіяні контейнери — перевірте cgroups першими; «top на хості» — не інструмент обліку памʼяті контейнера.
Четверте: якщо процеси не пояснюють ситуацію, підозрівайте памʼять ядра
- Зростання slab:
slabtop,/proc/slabinfo, рядокSlabу meminfo. - Стек ядра, таблиці сторінок і невідновлювана памʼять можуть тихо вивести хост з ладу.
Пʼяте: підтвердьте шлях виправлення перш ніж робити драматичні дії
- Вбивайте/перезапускайте лише винуватця, а не весь сервер.
- Встановіть розумні ліміти (systemd/Kubernetes) після того, як зрозумієте steady‑state використання.
- Виправляйте витоки з доказами: криві росту, а не відчуття.
Ментальна модель: що насправді означає «used RAM»
Linux агресивно використовує ОЗП, бо проста невикористана памʼять — втрачені можливості. Воно заповнить памʼять файловим кешем, dentries, inode‑кешем та іншими помічниками продуктивності. Це не витік; це ядро, яке виконує свою роботу.
Операційне питання не «Чому used високий?», а «Чи система позбавлена відновлюваної памʼяті?». Тому free показує оцінку available. «Available» приблизно означає: якщо зараз стартувати нове навантаження, скільки ядро може звільнити без катастрофи?
Памʼять починає реально тиснути, коли:
- swap активно використовується і особливо коли швидкості swap‑in/out немалі.
- прямий reclaim гальмує роботу: стрибки латентності, CPU у ядрі, kswapd зайнятий.
- зʼявляється OOM killer, на рівні хоста або всередині cgroup (контейнери люблять тихо вмирати).
- зростає slab або невідновлювана памʼять, залишаючи менше відновлюваного простору.
Ось розподіл, який варто тримати в голові:
- Анонімна памʼять (AnonPages): купи, стек, JIT‑памʼять, malloc. Це те, що споживають «додатки».
- Файлова памʼять (Cached): файловий кеш. Зазвичай відновлювана при потребі.
- Slab (SReclaimable + SUnreclaim): кеші/метадані ядра. Частково відновлювані.
- Committed memory: обіцянки. Не всі обіцянки стають боргом, але слід слідкувати за overcommit.
- cgroups: обмеження. Ви можете мати багато фізичної памʼяті на хості й усе одно отримати OOM усередині контейнера.
Одна парафразована ідея від легенди надійності: парафразована ідея
— John Allspaw стверджував, що інциденти в продакшені часто — це «звична робота», яка зіштовхується з припущеннями; сторінки памʼяті ідеально вписуються в цю схему.
Жарт №1: Усунення проблем з памʼяттю — як дієта: числа реальні, але ярлики обманюють вас.
10‑хвилинний метод (завдання з командами)
Нижче — практичні завдання, які можна виконати на будь‑якому Linux‑сервері (bare metal, VM, хост контейнерів). Кожне завдання містить: команду, що означає її вивід, і яке рішення приймати.
Завдання 1: дивіться реальність через free
cr0x@server:~$ free -h
total used free shared buff/cache available
Mem: 31Gi 27Gi 1.2Gi 512Mi 2.8Gi 2.9Gi
Swap: 8.0Gi 2.1Gi 5.9Gi
Що це означає: Ключове число — available. Тут воно ~2.9Gi, що не комфортно при різкому сплеску навантаження. Swap уже використовується (2.1Gi), що натякає на реальний тиск.
Рішення: Якщо available низький і swap використовується або росте — продовжуйте діагностику. Якщо available у нормі і swap спокійний, то «used RAM» — ймовірно кеш, і проблема може бути в іншому (диск чи CPU).
Завдання 2: перевірте paging і поведінку reclaim через vmstat
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
2 0 2211840 923456 65536 911872 12 40 15 20 420 880 12 6 78 4 0
1 0 2211840 905216 65536 918528 0 16 0 8 410 860 11 6 79 4 0
3 0 2211840 892928 65536 924160 0 0 0 0 450 910 14 7 76 3 0
4 0 2211840 876544 65536 931072 0 24 0 12 470 980 17 8 72 3 0
2 0 2211840 868352 65536 936960 0 8 0 4 440 900 13 6 77 4 0
Що це означає: si/so — swap‑in/out за секунду. Нульові значення протягом часу означають, що ядро активно переставляє сторінки. Також дивіться wa (I/O wait) і b (blocked processes).
Рішення: Якщо swap‑out триває — у вас тиск памʼяті. Наступний крок — визначити, який відсік росте: процеси, кеш, slab чи cgroups.
Завдання 3: прочитайте «сировину правди»: /proc/meminfo
cr0x@server:~$ egrep 'MemTotal|MemFree|MemAvailable|Buffers|Cached|SwapTotal|SwapFree|AnonPages|Mapped|Shmem|Slab|SReclaimable|SUnreclaim|KernelStack|PageTables' /proc/meminfo
MemTotal: 32949044 kB
MemFree: 842112 kB
MemAvailable: 3026112 kB
Buffers: 65536 kB
Cached: 956812 kB
SwapTotal: 8388604 kB
SwapFree: 6193152 kB
AnonPages: 25801120 kB
Mapped: 612340 kB
Shmem: 524288 kB
Slab: 1523400 kB
SReclaimable: 812000 kB
SUnreclaim: 711400 kB
KernelStack: 112000 kB
PageTables: 184000 kB
Що це означає: AnonPages величезний: процеси — головний споживач. Cached відносно малий, отже це не «лише файловий кеш». Slab немалий; зверніть увагу, скільки невідновлюваного.
Рішення: Якщо домінує AnonPages, полюйте на процеси/cgroups. Якщо домінує Cached і MemAvailable низький, можлива трешування файлового кешу через патерни I/O. Якщо домінує Slab/SUnreclaim, підозрюйте зростання памʼяті ядра (часто повʼязане з файловою системою або мережею).
Завдання 4: перевірте, чи вже спрацював OOM killer
cr0x@server:~$ dmesg -T | egrep -i 'oom|out of memory|killed process' | tail -n 20
[Tue Feb 4 10:18:22 2026] Memory cgroup out of memory: Killed process 24198 (java) total-vm:8123456kB, anon-rss:6123456kB, file-rss:12000kB, shmem-rss:0kB, UID:1001 pgtables:14200kB oom_score_adj:0
[Tue Feb 4 10:18:22 2026] oom_reaper: reaped process 24198 (java), now anon-rss:0kB, file-rss:0kB, shmem-rss:0kB
Що це означає: Це не був «host OOM». Це був memory cgroup OOM, що вбив java всередині обмеженої групи (systemd unit або контейнер). Це найпоширеніша причина, через яку команди кажуть «але на хості була вільна памʼять».
Рішення: Якщо видно cgroup OOM — припиніть дивитись на загальні списки top. Перейдіть до обліку cgroup і перевірте ліміти контейнера/юниту.
Завдання 5: швидко знайдіть процеси з найбільшим RSS
cr0x@server:~$ ps -eo pid,user,comm,rss,pmem --sort=-rss | head -n 15
PID USER COMMAND RSS %MEM
24198 app java 6321452 19.2
19872 app node 1823340 5.5
1321 root dockerd 612448 1.8
2012 mysql mysqld 588120 1.7
1711 root prometheus 312880 0.9
922 root systemd-jou 188244 0.5
2666 root nginx 92240 0.2
Що це означає: RSS — resident set size: фізична памʼять, повʼязана з процесом. Це грубий інструмент, але він швидко виявляє очевидних порушників.
Рішення: Якщо один процес значно перевищує інших і корелює з часом інциденту — у вас головний підозрюваний. Далі: перевірте PSS (бо спільна памʼять може завищувати RSS) і перевірте cgroups/ліміти.
Завдання 6: не обманюйтесь спільною памʼяттю — використовуйте PSS через smaps_rollup
cr0x@server:~$ sudo sh -c 'cat /proc/24198/smaps_rollup | egrep "Pss:|Rss:|Private_Dirty:|Private_Clean:|Shared_Dirty:|Shared_Clean:"'
Rss: 6321452 kB
Pss: 6189021 kB
Shared_Clean: 12400 kB
Shared_Dirty: 1024 kB
Private_Clean: 88000 kB
Private_Dirty: 6219028 kB
Що це означає: PSS (proportional set size) ділить спільні сторінки між процесами. Тут PSS близький до RSS, отже процес дійсно володіє цією памʼяттю (private dirty величезний).
Рішення: Великий Private_Dirty вказує на зростання купи або патерн витоку памʼяті. Якщо PSS значно менший за RSS — ви можете звинувачувати не той процес через спільні бібліотеки або спільні відображення памʼяті.
Завдання 7: якщо є smem, використайте його; це економить час
cr0x@server:~$ smem -r -k -t | head -n 12
PID User Command Swap USS PSS RSS
24198 app java 1024K 6000M 6044M 6173M
19872 app node 0K 1600M 1652M 1802M
1321 root dockerd 0K 420M 435M 598M
2012 mysql mysqld 0K 510M 522M 575M
-------------------------------------------------------------------------------
1024K 8530M 8653M 9148M
Що це означає: USS — unique set size (приватна памʼять). PSS — найкраща метрика для системного розподілу витрат памʼяті.
Рішення: Пріоритизуйте процеси з високим PSS/USS при побудові плану помʼякшення. RSS підходить для швидкого огляду; PSS — те, що ви цитуватимете в постмортемі.
Завдання 8: перевірте ліміти контейнера або systemd‑юнита (cgroups v2)
cr0x@server:~$ mount | grep cgroup2
cgroup2 on /sys/fs/cgroup type cgroup2 (rw,nosuid,nodev,noexec,relatime)
cr0x@server:~$ systemctl status myapp.service --no-pager | egrep 'Memory|Tasks'
Memory: 6.3G (limit: 6.5G)
Tasks: 92 (limit: 2048)
Що це означає: Цей юніт має обмеження. Ви можете мати 128GB вільних на хості й усе одно бути вбитими при 6.5GB. Ліміт — частина системного контракту.
Рішення: Якщо кап нижчий за піковий робочий набір — або підвищіть ліміт, або зменшіть споживання памʼяті, або прийміть вбивство як «авто‑скейлер» (не рекомендовано для stateful сервісів).
Завдання 9: перегляньте current/peak та події cgroup
cr0x@server:~$ CG=/sys/fs/cgroup/system.slice/myapp.service
cr0x@server:~$ sudo sh -c "cat $CG/memory.current; cat $CG/memory.max; cat $CG/memory.peak; cat $CG/memory.events"
6848124928
6983510016
6950022144
low 0
high 0
max 12
oom 12
oom_kill 12
Що це означає: memory.max — жорсткий кап. memory.peak показує найгірше спостережуване використання. oom_kill підтверджує, що були вбивання cgroup.
Рішення: Якщо memory.current близько до memory.max, припиніть трактувати це як проблему хоста. Це проблема ліміту або витоку всередині сервісу.
Завдання 10: знайдіть розподіл памʼяті по cgroup (anon/file/slab) на cgroups v2
cr0x@server:~$ sudo sh -c "cat $CG/memory.stat | egrep 'anon |file |slab |sock |shmem |file_mapped|file_dirty|inactive_anon|inactive_file|active_anon|active_file'"
anon 6423011328
file 211345408
shmem 0
slab 142110720
sock 9123840
file_mapped 54476800
file_dirty 122880
inactive_anon 6112147456
active_anon 310863872
inactive_file 188743680
active_file 22601728
Що це означає: Cgroup домінує anon. Це памʼять додатка, не кеш. Slab присутній, але не основна історія.
Рішення: Якщо домінує file, можливо ви кешуєте дані в межах cgroup; можна підлаштувати патерни читання або дозволити більше запасу. Якщо домінує anon, потрібна дисципліна купи, менше обʼєктів в памʼяті або вищий ліміт.
Завдання 11: розслідуйте зростання slab за допомогою slabtop
cr0x@server:~$ sudo slabtop -o | head -n 15
Active / Total Objects (% used) : 4821102 / 5012240 (96.2%)
Active / Total Slabs (% used) : 118220 / 118220 (100.0%)
Active / Total Caches (% used) : 94 / 132 (71.2%)
Active / Total Size (% used) : 1289012.40K / 1390024.00K (92.7%)
Minimum / Average / Maximum Object : 0.01K / 0.28K / 8.00K
OBJS ACTIVE USE OBJ SIZE SLABS OBJ/SLAB CACHE SIZE NAME
812320 801200 98% 0.19K 38777 21 155108K dentry
610112 605900 99% 0.62K 23832 16 238320K inode_cache
420000 418000 99% 0.10K 10769 39 43076K kmalloc-96
Що це означає: Великі кеші dentry/inode вказують на тиск файлової метадані: багато файлів, багато пошуків по шляхах або навантаження, що швидко створює і видаляє записи (build‑машини, розпаковування архівів, витягання образів контейнерів).
Рішення: Якщо slab — винуватець, не «оптимізуйте додаток». Подивіться на поведінку файлової системи, нескінченні сканування каталогів, рекурсивні бекапи або мільйони дрібних файлів. Також перевірте на помилки ядра або драйверів, якщо slab поводяться дивно/неочікувано.
Завдання 12: перевірте відкриті файли і зростання FD, коли памʼять «зникає»
cr0x@server:~$ sudo lsof -p 24198 | wc -l
18452
cr0x@server:~$ cat /proc/24198/limits | egrep 'Max open files'
Max open files 1048576 1048576 files
Що це означає: Висока кількість FD часто корелює зі споживанням памʼяті (буфери, памʼять сокетів, структури на FD, кеш у юзерспейсі). Не завжди, але це сильний сигнал «щось росте».
Рішення: Якщо FD зростають разом з памʼяттю — ймовірний витік ресурсів (зʼєднання, файли, watcher’и). Виправте витік; підвищення лімітів лише відкладає аварію.
Завдання 13: чи tmpfs або спільна памʼять їдять ОЗП?
cr0x@server:~$ df -hT | egrep 'tmpfs|shm'
tmpfs tmpfs 3.2G 2.7G 0.5G 85% /run
tmpfs tmpfs 16G 9.0G 7.0G 57% /dev/shm
Що це означає: tmpfs використовує ОЗП (і swap). Якщо /dev/shm росте, ця памʼять фактично анонімна і може тиснути систему.
Рішення: Якщо ріст tmpfs несподіваний — знайдіть процес, що пише (часто браузери, ML‑навання, системи з інтенсивним IPC) і обмежте або перемістіть сховище.
Завдання 14: перевірте KSM і THP, якщо ви вишукуєте дивні ефекти
cr0x@server:~$ cat /sys/kernel/mm/transparent_hugepage/enabled
[always] madvise never
cr0x@server:~$ cat /sys/kernel/mm/ksm/run
0
Що це означає: THP може покращити пропускну здатність або створити сплески латентності та фрагментацію памʼяті під деякими навантаженнями. KSM зазвичай вимкнений, якщо ви його явно не вмикали (історично поширене на хостах віртуалізації).
Рішення: Не чіпайте ці налаштування посеред інциденту, якщо ви не знаєте поведінки навантаження. Якщо підозрюєте проблеми THP — збирайте докази (швидкості фолтів, латентність, фрагментація) і міняйте в вікні технічного обслуговування.
Інтерпретація результатів: рішення, які ви зможете відстояти
Коли «used» високе, але «available» у нормі
Якщо MemAvailable у нормі і активність swap близька до нуля, ймовірно у вас звична Linux‑система з великим кешем. Правильна дія зазвичай: нічого не робити, але переконатись, що критичні сервіси не досягли лімітів cgroup.
Команди іноді «вирішують» це, скидаючи кеш. Це не виправлення; це навмисне уповільнення наступного читання.
Коли домінує AnonPages
Саме тут живуть витоки. Але також тут живуть легітимні робочі набори в памʼяті (датастори, JVM‑купи, кеші, які ви навмисно тримаєте в памʼяті).
- Якщо
Private_Dirtyпроцесу зростає стабільно протягом годин/днів — підозрюйте витік або незрегульований кеш. - Якщо воно зростає з трафіком і падає при зменшенні трафіку (і GC працює), це може бути нормальна еластичність.
- Якщо зростання почалося після деплою і не повертається — вважайте це регресією, поки не доведено протилежне.
Коли домінує slab
Памʼять ядра часто — «невидимий податок». Inode, dentry, мережеві буфери, таблиці conntrack і файлові метадані можуть накопичуватися. Slab не є поганим; поганий — безконтрольний slab.
Типові причини:
- Мільйони дрібних файлів і постійні обходи директорій.
- Вибух шарів контейнерів і розпакування образів.
- Системи з великою мережею і великими таблицями conntrack.
- Баги ядра/драйверів (рідше, але помітно, коли нічого в user‑space не пояснює втрату).
Коли на хості є RAM, але сервіс отримує OOM‑kill
Це проблема cgroups. Межа сервісу обмежена. Або ваш ліміт невірний, або робочий набір виріс, або сервіс почав робити щось нове (наприклад кешувати більше даних).
Операційно — ставтесь до цього як до контрактної проблеми: узгодьте ліміти з реальністю і додайте алертинг для memory.current при наближенні до memory.max.
Жарт №2: OOM killer — єдиний колега, що завжди діє рішуче — на жаль, він ніколи не приходить на постмортем.
Три міні‑історії з корпоративного життя
Міні‑історія 1: інцидент через хибне припущення
Середня компанія тримала пул API‑серверів за балансувальником. На графіках хости виглядали на 40–50% «used» памʼяті, тож усі почувалися в безпеці. Потім після трафікового сплеску API почав випадково повертати 502. Не повний збій — гірше. Частково, що викликало суперечки.
Он‑кол подивився хост‑метрики: багато RAM, CPU ок, диск ок. Перезапустили кілька подів (Kubernetes), стало краще, потім знову погіршилося. Інцидент назвали «мережевою нестабільністю», бо це те, що називають проблемами, які не видно.
Нарешті хтось зазирнув у dmesg на вузлі і побачив повторювані повідомлення Memory cgroup out of memory. Кожне вбивство відбувалося всередині cgroup пода. На хості була памʼять, але поди були обмежені і вмирали під навантаженням. Їхнє припущення — «вільна памʼять на хості = памʼять сервісу» — виявилось помилковим.
Виправлення не було героїчним: поставили ліміти пода на основі спостережуваного PSS під піком, додали запас, і налаштували алерти на memory.events. Після цього той самий трафік давав більшу латентність (прийнятно) замість випадкових вбивств (неприйнятно). Головний урок: «вільна RAM» на ноді не має значення, коли ви живете в cgroups.
Міні‑історія 2: оптимізація, що вдарила назад
Команда платформи даних вирішила зменшити читання з диска на батч‑хості. Хтось підкрутив налаштування, щоб тримати більше даних у памʼяті на рівні додатку: більші кеші, більші буфери, більше паралелізму. Бенчмарки прискорились. Всі були задоволені. Розгорнули це в продакшн.
Через два тижні флот почав свопитися при нормальному навантаженні. Латентність і тривалість джобів впали в невідповідний бік. Команда звинуватила масив зберігання. Потім гіпервізор. Потім «Linux дивно поводиться». Класичний бізнес‑тур.
Коли вони нарешті виміряли AnonPages і PSS по процесах, виявилось, що новий кеш фактично був безконтрольним при певних міксах джобів. Це не був «витік» у строгому сенсі; це був кеш без дисципліни витіснення. Додаток жадібно захоплював памʼять, витісняючи файловий кеш і змушуючи ядро свопити інші сторінки.
Негативний ефект був тонким: оптимізація зменшила читання на малих тестах, але в реальній конкуренції збільшила загальний тиск памʼяті і створила I/O‑помноження через свопінг. Виправлення — поставити жорсткі межі кешів додатку і навантажувати тестами, що імітують продукцію. Перемоги в продуктивності реальні; гординя в памʼяті дорога.
Міні‑історія 3: нудна, але правильна практика, що врятувала день
Сервіс оплати працював під systemd‑юнитами (без контейнерів). У них була непримітна, але корисна звичка: після кожного релізу вони записували базовий профіль памʼяті. Не складний APM — просто скрипт, що зберігав /proc/meminfo, топ‑список RSS з ps і зведення smaps_rollup з привʼязкою до метаданих збірки.
Одного дня вузол почав показувати повільне зростання використання памʼяті. Спочатку жодних алертів, просто повільний дрейф. Он‑кол порівняв поточний PSS‑профіль з тижневим базисом. Дельта була очевидна: приватна dirty памʼять головного процесу виросла на значну величину і трималась на зростаючому тренді.
Вони відкотилися до попередньої версії до того, як OOM killer втрутився. Ніякого інциденту для клієнтів. Пізніше, в спокійний час, вони відтворили витік у staging: нова фічер‑гілка алоціювала обʼєкти, які потім лишалися в глобальній мапі довше, ніж потрібно. Баг виправили швидко, бо команда мала базову лінію і могла сказати «це нове», без суперечок.
Практика не була гламурною. Вона просто переводила розмови про памʼять з релігії в арифметику. У продакшені нудна правильність перемагає хитромудрі здогадки щодня.
Типові помилки (симптом → корінь → виправлення)
1) Симптом: сповіщення «RAM 95% used» постійно спрацьовує, але продуктивність нормальна
Корінь: Алерт базується на used, а не на available. Linux використовує RAM для кешу за задумом.
Виправлення: Алертуйте за MemAvailable (або похідним «available percent») і активністю swap. Оновіть runbook он‑кола, щоб ігнорувати лише «used».
2) Симптом: сервіс отримує OOM‑kill, але хост має багато вільної памʼяті
Корінь: Вдарили по ліміту cgroup (systemd/Kubernetes). Метрики на рівні хоста вводять в оману.
Виправлення: Перевірте memory.max, memory.current і memory.events. Встановіть ліміти на основі спостережуваного PSS/peak. Додайте запас для сплесків і фрагментації.
3) Симптом: swap використовується, але в top немає якихось величезних процесів
Корінь: Спільна памʼять спотворює уявлення RSS; або памʼять споживає slab ядра; або кілька середніх процесів в сумі створюють тиск.
Виправлення: Використовуйте PSS (smem або smaps_rollup), потім перевірте slab (slabtop) і /proc/meminfo.
4) Симптом: памʼять постійно зростає після деплою і врешті OOM
Корінь: Витік або неконтрольований кеш у user‑space; іноді зміна у трафіку відкриває шлях росту.
Виправлення: Підтвердіть ріст за Private_Dirty в часі. Накладіть тимчасовий жорсткий кап (налаштування) як помʼякшення, потім отскануйте купу відповідними інструментами для JVM, Go, Python. Не «просто додайте swap».
5) Симптом: slab росте, домінують dentry/inode_cache
Корінь: Час життя метаданих файлової системи: великі каталоги, рекурсивні сканування, build‑артефакти, буря логів, розпакування образів контейнерів.
Виправлення: Зменште кількість і churn файлів; виправте скрипти, що роблять повторні рекурсивні find; налаштуйте ротацію логів; уникайте вибуху дрібних файлів на спільних хостах. Якщо це build‑хост — ізолюйте робочі навантаження.
6) Симптом: випадкові стрибки латентності, kswapd вимагає CPU, але графіки RAM виглядають «окей»
Корінь: Витрати на reclaim і compacting; потенційні побічні ефекти THP; фрагментація памʼяті.
Виправлення: Корелюйте з статистикою paging і reclaim. Розгляньте режим THP madvise для певних навантажень, але лише після тестування. Частіше виправлення — «менше тиску памʼяті», а не «інші графіки».
7) Симптом: «кеш величезний, drop_caches допомагає»
Корінь: Скидання кешу маскує основну проблему — витік процесу, неконтрольну метадані або недооцінений cgroup; ядро все одно заповнить кеш згодом.
Виправлення: Не робіть drop_caches рутинною операцією. Виміряйте, що викликає ріст кешу; виправте навантаження або встановіть реалістичні бюджети памʼяті.
Чеклісти / покроковий план
10‑хвилинний чекліст on‑call (робіть у цьому порядку)
- Запустіть
free -h. Якщоavailableнизький — продовжуйте; якщо ні — все одно перевірте swap і cgroups. - Запустіть
vmstat 1 5. Якщоsi/soпостійно ненульові — працюйте як з реальним тиском памʼяті. - Прогляньте meminfo grep. Визначте: anon‑heavy vs cache‑heavy vs slab‑heavy.
- Перевірте
dmesgна предмет OOM. Якщо це cgroup OOM — зупиніться і перемкніться на cgroups. - Складіть список топ RSS процесів через
ps; знайдіть кандидатів. - Підтвердіть кандидатів через
smaps_rollup(PSS/private dirty). - Якщо є контейнери/systemd: прочитайте
memory.current,memory.max,memory.eventsіmemory.stat. - Якщо процеси не сходяться — розслідуйте slab через
slabtop. - Перевірте використання tmpfs (
df -hTдля tmpfs/shm). - Виберіть дію: перезапуск винуватця, підвищення ліміту, виправлення витоку, зменшення метаданого churn, або масштабування. Уникайте перезавантаження, якщо це не необхідно для зупинки кровотечі.
Чекліст рішень: що змінювати залежно від знахідок
- Один процес володіє памʼяттю (високий PSS/private dirty): помʼякшити перезапуском/відкатом; впровадити обмеження; відлагоджувати витік.
- Багато процесів у сумі створюють тиск: зменшити паралелізм, масштабувати горизонтально або перемістити важкі задачі з спільних хостів.
- Кап cgroup занадто низький: підвищити кап з запасом; правильно визначити requests/limits; алертити при наближенні до капа.
- Slab — головний споживач: виправити файловий/мережевий churn; зменшити кількість файлів; дослідити підсистеми ядра.
- Активність swap — проблема: зменшити використання памʼяті; розглянути зміну swappiness лише після переконливих доказів, що це допоможе.
Післяінцидентний чекліст (щоб не повторюватися)
- Заберіть знімок профілю памʼяті: meminfo, топ PSS, cgroup‑статистика, зведення slabtop.
- Додайте алерти, які відображають реальність: MemAvailable, I/O swap, cgroup OOM‑kill та зростання slab.
- Встановіть бюджети: бюджети памʼяті на сервіс і протестуйте їх при реалістичній конкуренції.
- Документуйте «відомо добрий» базовий профіль для кожного релізу, щоб регресії були очевидні.
Факти й історичний контекст (те, що пояснює сучасну дивність)
- Поле
MemAvailable— відносно молоде в термінах ядра; його додали, бо «вільна памʼять» погано прогнозує відновлювану памʼять. - Linux навмисно використовує вільну RAM для файлового кешу, щоб уникнути повільних операцій з диском; низький «free» часто означає, що ядро робить свою роботу.
- Рішення OOM‑killer є евристичним: він призначає «оцінки шкідливості» процесам. Це не моральний вирок — це сортування під час кризи.
- cgroups зробили нормою «один хост — багато світів памʼяті». Контейнер може OOM‑итись, поки вузол у порядку, бо вузол не є всесвітом контейнера.
- RSS може переносити надмірний рахунок спільних сторінок. Тому існує PSS: для справедливого розподілу спільних сторінок між процесами.
- tmpfs — це RAM‑підтримуване сховище (та також підтягує swap). Зберігання «тимчасових файлів» у tmpfs може перетворитися на «постійний тиск памʼяті».
- Slab кеші — це функція продуктивності: ядра кешують обʼєкти типу dentry/inode, бо постійне їх виділення/звільнення дорого обходиться.
- Transparent Huge Pages набули популярності для приросту пропускної здатності, але також внесли операційні компроміси: витрати на compacting, фрагментацію і чутливість до латентності.
FAQ
1) Чому free показує майже нічого «free» на здоровій системі?
Тому що Linux використовує RAM як кеш. Дивіться на available, а не на free. «Free» — це здебільшого невикористані сторінки; «available» — оцінка відновлюваних сторінок плюс невикористані.
2) Чи варто скидати кеш, щоб «звільнити памʼять»?
Майже ніколи в продакшені. Скидання кешу — це як висипати інструменти на підлогу, щоб верстак виглядав чистим. Це може тимчасово полегшити тиск, але зазвичай шкодить продуктивності і приховує корінну причину.
3) У чому різниця між RSS, VIRT, USS і PSS?
VIRT — віртуальний адресний простір; може бути величезним і неінформативним. RSS — resident pages у RAM, але він переобліковує спільні сторінки. USS — приватна памʼять (унікальна). PSS — найкраща метрика для системного розподілу, бо пропорційно ділить спільні сторінки.
4) OOM‑killer вбив мій найбільший процес. Чи означає це, що саме він був причиною?
Не обовʼязково. Це був найзручніший для вбивства процес за евристикою в той момент. Причина може бути агрегованим тиском, лімітом cgroup або зростанням невідновлюваної памʼяті ядра.
5) Як зрозуміти, чи це cgroup OOM?
Подивіться в dmesg на «Memory cgroup out of memory» і перевірте /sys/fs/cgroup/.../memory.events. Якщо там інкрементує oom_kill — це cgroup‑проблема.
6) Чому swap показує ненульову величину навіть при наявності вільної RAM?
Ядро може перемістити «холодні» анонімні сторінки в swap, щоб тримати більше файлового кешу «гарячим». Ненульовий swap сам по собі не завжди поганий. Важливі активні швидкості swap‑in/out і їхній вплив на латентність.
7) Що зазвичай викликає вибухове зростання slab?
Час життя файлових метаданих (dentry/inode), мережеві таблиці, і іноді баги ядра. slabtop покаже, який кеш росте; це вкаже на підсистему.
8) Мій контейнер показує низьке використання памʼяті всередині, але хост каже, що воно величезне. Хто правий?
Вони можуть бути обидва «праві», бо повідомляють про різні області і метрики. Для дебагу контейнера довіряйте файлам cgroup: memory.current і memory.stat. Для дебагу хоста використовуйте meminfo плюс PSS по процесах.
9) Як швидко виявити витік памʼяті без профайлерів?
Шукайте монотонне зростання Private_Dirty (через /proc/PID/smaps_rollup) і збільшення PSS з часом, корельоване з аптаймом або трафіком. Як помʼякшення — перезапуск/відкат і постановка капу, якщо можливо.
10) Чи коли-небудь правильно додати більше RAM?
Так — коли робочий набір дійсно більший за машину і вартість оптимізації перевищує вартість RAM. Але підтвердіть це PSS і сигналами тиску спочатку; не платіть за апарат, щоб покривати витік.
Висновок: наступні кроки, які реально зменшують сторінки
Коли ви намагаєтесь відповісти «що їсть ОЗП», не дивіться лише на одне число. Використайте послідовність, яка змушує говорити правду: сигнали тиску, розподіл по відсіках, потім атрибуція правильною метрикою (PSS), потім область (cgroups vs хост), і нарешті — памʼять ядра, якщо user‑space не пояснює втрату.
Практичні наступні кроки:
- Оновіть алерти, щоб пріоритетними були
MemAvailable, активність swap і cgroup OOM‑kills — а не сирий «used». - Додайте легкий скрипт знімка памʼяті до інструментів інциденту (meminfo + топ PSS + cgroup‑статистика + зведення slab).
- Правильно визначайте ліміти памʼяті з використанням
memory.peakі PSS‑базових профілів, додаючи запас. - Для повторних порушників: реалізуйте ліміти для внутрішніх кешів додатку і розглядайте будь‑яке монотонне зростання private dirty як регресію, поки не доведено протилежне.
Якщо нічого не робите більше: наступного разу, коли прийде пейдж, перш ніж формувати думку — прочитайте /proc/meminfo. З ядром сперечатися важче, коли воно дає вам розбивку по рядках.