Debian 13: Сплески запису на диск — налаштуйте vm.dirty без ризику втрати даних (випадок №45)

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

Все добре, поки раптом перестає бути. Ваш хост на Debian 13 працює нормально годинами, потім приходить пакетне завдання, пам’ять заповнюється «брудним» кешем, і машина переходить від «відповідна» до «чому введення в SSH повільне, ніби в уповільненій зйомці». iowait підіймається, потоки kworker спалахують, а база даних починає тайм-аутитися, ніби грає драму.

Це і є writeback-шторм: синхронізоване скидання надто великої кількості брудних сторінок, яке відбувається занадто пізно й у найневірніший момент. Зазвичай рішення не в «купити швидші диски». Це змусити Linux починати writeback раніше, частішими невеликими порціями, і перевірити, що ви не обміняли продуктивність на ризик для даних.

Як виглядає writeback-шторм (і чому Debian 13 робить це очевидним)

Writeback-шторм — це не «диск повільний». Це проблема синхронізації: ядро дозволяє накопичитися великій кількості брудних сторінок у пам’яті (змінених даних кешу файлів, ще не записаних на диск), а потім має їх скинути. Якщо скинути занадто багато одночасно, виникає конкуренція всюди:

  • Фонні процеси запису раптово жорстко обмежуються, часто хвилясто.
  • kswapd або тиск на звільнення пам’яті змушує writeback одночасно.
  • jbd2 (журнал ext4) або коміти файлової системи накопичуються.
  • Потоки, чутливі до затримки (бази даних, API-воркери), блокуються на ввід/вивід або на блокуваннях файлової системи.

На Debian 13, ймовірно, у вас сучасне ядро з кращою видимістю й іноді більш агресивною поведінкою під час reclaim/writeback порівняно зі старими кластерами. Це добре: проблему помічають раніше. Це також погано: значення за замовчуванням, які «працювали» в епоху Debian 10 і обертових дисків, можуть перетворитися на NVMe-обриви латентності, бо система пише дуже швидко… поки раптом не перестає.

Одна операційна істина: writeback-шторм рідко випадковий. Він повторюваний, якщо ваш шаблон навантаження повторюваний (ETL, бекапи, компресії, сплески логів, завантаження образів контейнерів, артефакти CI). Це подарунок. Скористайтеся ним.

Жарт №1: ядро — оптиміст, воно вважає, що ваш диск зможе обробити всі ці записи пізніше. «Пізніше» — це коли ви на виклику.

Основи брудних сторінок: що ядро справді робить

Linux активно використовує вільну пам’ять як page cache. Коли додатки записують у файли (не використовуючи direct IO), вони часто записують спочатку в пам’ять. Ці сторінки стають «брудними». Пізніше фонова writeback скидає їх на диск. Це дає велику пропускну здатність і розв’язує швидкість додатка від швидкості диска — поки не досягнете обмежень.

Ключові терміни, що важливі в продакшні

  • Брудні сторінки: дані кешованих файлів, змінені в ОЗП, але ще не збережені на зберіганні.
  • Writeback: операція ядра, що скидає брудні сторінки на диск у фоні або під тиском.
  • Поріг фонового writeback: коли фонові потоки починають проактивно писати брудні дані.
  • Ліміт dirty: коли процеси, що генерують бруд, починають обмежуватися (або змушені писати назад).
  • Час витримки (expire time): скільки часу брудні дані можуть знаходитися в пам’яті перед тим, як writeback спробує їх скинути.

Регулятори знаходяться в /proc/sys/vm/, найпомітніші:

  • vm.dirty_background_ratio і vm.dirty_ratio
  • vm.dirty_background_bytes і vm.dirty_bytes
  • vm.dirty_expire_centisecs і vm.dirty_writeback_centisecs

Співвідношення проти байтів (виберіть один стиль, не змішуйте необережно)

Ратіо — це відсотки від «доступної пам’яті» (не строго від загальної ОЗП; ядро використовує внутрішню метрику, яка змінюється з тиском пам’яті). Байти — абсолютні пороги. У продакшні байти передбачуваніші між різними машинами та в умовах змінного тиску на пам’ять, особливо на хостах з контейнерами, де «вільна пам’ять» рухлива.

Якщо ви встановлюєте dirty_bytes, ратіо-ручки фактично ігноруються (те саме для background bytes). Зазвичай це те, що вам потрібно: менше несподіванок.

Що означає «ризик для даних» тут

Налаштування vm.dirty* не змінює коректність файлової системи або гарантії журналювання. Воно змінює скільки даних дозволено бути брудними в ОЗП і як довго вони там можуть перебувати. Ризики — операційні:

  • Більші dirty-ліміти можуть покращити пропускну здатність, але збільшити обсяг даних «під ризиком» під час відключення живлення (дані ще не скинуті), і можуть створити більші, болючіші writeback-штормі.
  • Менші dirty-ліміти зменшують розмір штормів і зменшують експозицію брудних даних, але можуть раніше обмежувати писачів і знизити пікову пропускну здатність.

Більшість команд не потребують героїчної пропускної здатності. Потрібна передбачувана затримка. Налаштовуйте відповідно.

Цитата, що стосується надійності: Charity Majors сказала: «Ви не можете покращити те, що не вимірюєте». Це суть із боротьби зі writeback-штормами: спочатку вимірюйте, потім налаштовуйте.

Факти та історія: як ми дійшли до цього

  • Факт 1: Ранні ядра Linux використовували значно простішу поведінку кешу; сучасний writeback еволюціонував ще в епоху 2.6 з per-bdi механізмами для кращого керування тиском на IO.
  • Факт 2: Значення dirty за замовчуванням історично передбачали «розумний диск» і людські масштаби ОЗП. Ці значення погано масштабувалися, коли стали нормою 128–512 ГБ пам’яті.
  • Факт 3: Throttling бруду — це інструмент для контролю латентності, одягнений у костюм для пропускної здатності: він існує, щоб запобігти «нескінченному» засміченню пам’яті і вічному блокуванню на IO.
  • Факт 4: З SSD та NVMe проблема часто переміщується з пропускної здатності до хвостової латентності — стрибків 99.9-го процентиля, викликаних чергуванням і раптовими сплесками.
  • Факт 5: Журнальні файлові системи (ext4, XFS) все ще можуть створювати бурхливі IO-шаблони, особливо під навантаженням, що інтенсивно працює з метаданими.
  • Факт 6: Віртуалізовані середовища можуть підсилювати шторм: writeback на рівні хоста плюс writeback у гості дають «подвійне кешування», і кожен шар думає, що допомагає.
  • Факт 7: Уявлення ядра про «доступну пам’ять» динамічне; пороги на основі ратіо можуть фактично зростати й падати під час reclaim, змінюючи поведінку writeback посеред інциденту.
  • Факт 8: Встановлення dirty_writeback_centisecs в 0 не означає «без writeback», це вимикає періодичний таймер; writeback усе ще відбувається через інші тригери.
  • Факт 9: Багато інцидентів «диск повільний» насправді є інцидентами «черга насичена» — ваш диск може бути швидким, але ви годуєте його патологічними сплесками.

Швидкий план діагностики (перевірити 1/2/3)

Це версія для он-колу. Ви не тут, щоб філософствувати. Ви тут, щоб знайти вузьке місце за п’ять хвилин і вирішити, чи допоможе налаштування vm.dirty.

Перше: підтвердіть, що це writeback, а не випадковий IO

  • Перевірте рівні брудних сторінок і активність writeback.
  • Перевірте, чи задачі заблоковані в стані D.
  • Перевірте, чи під час «зависання» зростає IO wait і глибина черги диска.

Друге: визначте пристрій і джерело тиску

  • Який блочний пристрій насичений? (NVMe? RAID? мережевий блок?)
  • Чи робота — це файлові записи, коміти журналу або swap/reclaim, що змушують writeback?
  • Чи це всередині VM або хоста з контейнерами з подвійним кешуванням?

Третє: виберіть мінімальне безпечне пом’якшення

  • Зменшіть dirty-ліміти (використовуйте байти), щоб почати writeback раніше і уникнути великих хвиль скидання.
  • Опційно скоротіть час витримки, щоб старі брудні дані не накопичувалися.
  • Перевірте за метриками затримки й черги; відкотіть, якщо пропускна здатність падає занадто сильно.

Практичні завдання: 14 перевірок з командами, виводами і рішеннями

Це реальні завдання, які ви можете виконати на Debian 13 під час інциденту або у спокійному вікні. Кожне включає: команду, приклад виводу, що це означає, і яке рішення прийняти.

Завдання 1: Перегляньте поточні налаштування dirty

cr0x@server:~$ sysctl vm.dirty_ratio vm.dirty_background_ratio vm.dirty_bytes vm.dirty_background_bytes vm.dirty_expire_centisecs vm.dirty_writeback_centisecs
vm.dirty_ratio = 20
vm.dirty_background_ratio = 10
vm.dirty_bytes = 0
vm.dirty_background_bytes = 0
vm.dirty_expire_centisecs = 3000
vm.dirty_writeback_centisecs = 500

Значення: Цей хост використовує пороги на основі ратіо (bytes = 0). Фоновий writeback починається при 10% бруду, а писачі обмежуються при 20%.

Рішення: На хостах з великою пам’яттю 20% може бути величезним. Якщо є штормі, плануйте перейти на пороги в байтах.

Завдання 2: Підтвердіть масштаб пам’яті машини (раціо — відносні)

cr0x@server:~$ free -h
               total        used        free      shared  buff/cache   available
Mem:           256Gi       92Gi       11Gi       2.0Gi      154Gi      150Gi
Swap:          8.0Gi      0.0Gi      8.0Gi

Значення: При 256 GiB ОЗП ліміт 20% може дорівнювати десяткам гігабайт брудних даних. Це великий скидання.

Рішення: Віддавайте перевагу байтам. Наприклад, обмежте dirty одиничними GiB, якщо немає вагомої причини інакше.

Завдання 3: Слідкуйте за брудними та writeback сторінками в реальному часі

cr0x@server:~$ awk '/Dirty:|Writeback:|MemAvailable:|Cached:|Buffers:/{print}' /proc/meminfo
MemAvailable:   157392112 kB
Cached:         132884944 kB
Buffers:          126764 kB
Dirty:           6248120 kB
Writeback:        184320 kB

Значення: ~6.2 GiB бруду і активний writeback. Під час шторму Dirty може швидко рости, а потім Writeback сплескує при початку скидання.

Рішення: Якщо Dirty зростає до багатьох ГБ, а Writeback відстає, ваш фоновий поріг завелик або зберігання не встигає.

Завдання 4: Перевірте заблоковані задачі та IO wait під час «зависання»

cr0x@server:~$ top -b -n1 | head -n 20
top - 11:08:21 up 14 days,  3:52,  1 user,  load average: 18.42, 16.77, 10.91
Tasks: 612 total,   4 running,  58 sleeping,   0 stopped,   9 zombie
%Cpu(s):  5.3 us,  2.1 sy,  0.0 ni, 33.7 id, 58.6 wa,  0.0 hi,  0.3 si,  0.0 st
MiB Mem : 262144.0 total,  11540.0 free,  94512.0 used, 156092.0 buff/cache
MiB Swap:   8192.0 total,   8192.0 free,      0.0 used. 157000.0 avail Mem

Значення: 58.6% iowait — класична ознака насичення. Load average зростає, бо задачі чекають, а не через завантаження CPU.

Рішення: Продовжуйте діагностику шляху IO. Налаштування dirty допомагає, коли очікування корелює з масовим writeback.

Завдання 5: Визначте найзавантаженіший блочний пристрій і тиск у черзі

cr0x@server:~$ iostat -x 1 3
Linux 6.12.0 (server) 	12/30/2025 	_x86_64_	(64 CPU)

avg-cpu:  %user   %nice %system %iowait  %steal   %idle
           4.92    0.00    2.18   56.10    0.00   36.80

Device            r/s     w/s   rKB/s   wKB/s  avgrq-sz avgqu-sz   await  r_await  w_await  svctm  %util
nvme0n1          8.0  1900.0    256.0  78000.0    82.4     32.5   17.3     2.1    17.4    0.5   99.2

Значення: %util поруч із 100% і велике avgqu-sz означають, що пристрій насичений. Латентність запису (w_await) підвищена.

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

Завдання 6: Перевірте Pressure Stall Information (PSI) для IO

cr0x@server:~$ cat /proc/pressure/io
some avg10=12.43 avg60=8.11 avg300=3.21 total=91823354
full avg10=7.02 avg60=4.88 avg300=1.94 total=51299210

Значення: full показує час, коли задачі повністю застоюються на IO. Якщо воно росте під час шторму — це не тонкість.

Рішення: Високе full підкріплює аргумент за згладжування writeback; ви бачите системні загальмування.

Завдання 7: Спостерігайте лічильники writeback і throttling

cr0x@server:~$ egrep 'dirty|writeback|balance_dirty' /proc/vmstat
nr_dirty 1605402
nr_writeback 48812
nr_writeback_temp 0
nr_dirtied 981234567
nr_written 979998123
balance_dirty_pages 734520
dirty_background_threshold 786432
dirty_threshold 1572864

Значення: balance_dirty_pages інкрементується, коли задачі обмежуються, щоб тримати брудні сторінки під контролем. Пороги показані в сторінках.

Рішення: Якщо ви бачите величезне nr_dirty і раптові стрибки balance_dirty_pages, ви в зоні burst-throttle. Налаштуйте так, щоб починати раніше й уникати обривів.

Завдання 8: Визначте, які процеси пишуть

cr0x@server:~$ pidstat -d 1 5
Linux 6.12.0 (server) 	12/30/2025 	_x86_64_	(64 CPU)

11:10:01      UID       PID   kB_rd/s   kB_wr/s kB_ccwr/s iodelay  Command
11:10:02        0      2143      0.00  32100.00  31500.00      12  rsyslogd
11:10:02      105     18721      0.00  98000.00  96500.00      88  postgres
11:10:02        0     291002     0.00  14000.00  13900.00       6  backup-agent

Значення: kB_ccwr/s показує «скасовані байти запису» — дані, які були брудні, але потім обрізані або перезаписані до writeback. Високі значення можуть сигналізувати про сильне перезаписування.

Рішення: Якщо один процес домінує у записах, також можна виправити поведінку навантаження (блокувати, змінити частоту fsync, ротацію логів) замість лише налаштування ядра.

Завдання 9: Перевірте файлову систему та опції монтування

cr0x@server:~$ findmnt -no SOURCE,TARGET,FSTYPE,OPTIONS /var/lib/postgresql
/dev/nvme0n1p3 /var/lib/postgresql ext4 rw,relatime,errors=remount-ro,data=ordered

Значення: ext4 data=ordered — типовий і розумний режим; він журналює метадані та гарантує, що блоки даних записуються перед тим, як метадані на них вказують.

Рішення: Не змінюйте режими журналювання під час інциденту. Якщо у вас екзотичні опції монтування, документуйте їх і перегляньте пізніше.

Завдання 10: Перевірте, чи випадково використовуєте кеш запису без захисту від втрати живлення

cr0x@server:~$ lsblk -d -o NAME,MODEL,ROTA,TRAN,TYPE,SIZE
NAME   MODEL                 ROTA TRAN TYPE  SIZE
nvme0n1 Samsung SSD 980 PRO     0 nvme disk  1.8T
cr0x@server:~$ sudo nvme id-ctrl /dev/nvme0n1 | egrep 'vwc|oncs'
vwc     : 0x01
oncs    : 0x001f

Значення: vwc вказує на наявність леткого кешу запису. Це не обов’язково погано, але підвищує важливість бар’єрів/flush-поведінки та захисту від втрати живлення.

Рішення: Якщо платформа не має захисту від втрати живлення, тримайте dirty-ліміти консервативними. Ви не «sysctl»-ом переведете фізику на свій бік.

Завдання 11: Підтвердіть планувальник IO (і уникайте cargo cult)

cr0x@server:~$ cat /sys/block/nvme0n1/queue/scheduler
[none] mq-deadline kyber bfq

Значення: NVMe часто використовує none за замовчуванням; це може бути правильно. Планувальники можуть впливати на латентність під навантаженням.

Рішення: Не вважайте вибір планувальника першим засобом боротьби з writeback-штормами. Спочатку налаштуйте dirty-порогі; змінюйте планувальники тільки з вимірюваннями.

Завдання 12: Виміряйте навантаження fsync/commit (приклад ext4)

cr0x@server:~$ ps -eLo pid,comm,wchan:30 | egrep 'jbd2|fsync|fdatasync' | head
  612 jbd2/nvme0n1p3      jbd2_journal_commit_transaction
18721 postgres             do_fsync
18804 postgres             do_fsync

Значення: Якщо багато потоків чекають у шляхах fsync, коли брудних сторінок багато, ваша робота змушує точки довговічності під час інтенсивного writeback.

Рішення: Розгляньте скорочення dirty-лімітів (зменшення background/dirty bytes) і перевірте поведінку fsync на рівні додатка та параметри чекпойнтів.

Завдання 13: Застосуйте тимчасовий, безпечніший профіль writeback (на основі байтів)

cr0x@server:~$ sudo sysctl -w vm.dirty_background_bytes=$((512*1024*1024)) vm.dirty_bytes=$((2*1024*1024*1024))
vm.dirty_background_bytes = 536870912
vm.dirty_bytes = 2147483648

Значення: Фоновий writeback починається приблизно при 512 MiB бруду, а throttling починається приблизно при 2 GiB бруду.

Рішення: Це розумний початковий варіант для багатьох серверів. Якщо шторм зменшився і затримка покращилась, зробіть його постійним і відшліфуйте.

Завдання 14: Перевірте, що шторм зменшився (рівні Dirty + IO await)

cr0x@server:~$ watch -n1 'awk "/Dirty:|Writeback:/{print}" /proc/meminfo; iostat -x 1 1 | tail -n +7'
Every 1.0s: awk "/Dirty:|Writeback:/{print}" /proc/meminfo; iostat -x 1 1 | tail -n +7

Dirty:            612480 kB
Writeback:         98304 kB
nvme0n1          6.0  820.0   192.0  31000.0    78.3      6.2    3.8     1.9     3.8    0.4   71.0

Значення: Dirty тримається в межах ~600 MiB, writeback активний, але не вибуховий, await і черга нижчі, а %util має запас.

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

Безпечна стратегія налаштування: зменшити шторм без ризику компанії

Налаштування writeback дивно легко зробити неправильно. Люди читають блог, ставлять vm.dirty_ratio=80, і потім дивуються, чому при збої живлення черга перетворилася на сцену злочину.

Ось стратегія, яка працює в реальному продакшні:

1) Віддавайте перевагу порогам у байтах на сучасних серверах

Ратіо масштабується з пам’яттю. Це приємно звучить, доки ваш «20% dirty» не стане 30–50 GiB на великому хості. Якщо ваше сховище може стабільно і швидко сбросити це, ви б не читали цю статтю.

Рекомендація: Встановіть:

  • vm.dirty_background_bytes на 256–1024 MiB
  • vm.dirty_bytes на 1–8 GiB залежно від сховища та навантаження

Починайте консервативно. Збільшуйте лише за виміряного болю в пропускній здатності.

2) Тримайте фоновий поріг значно нижче за dirty-ліміт

Якщо фоновий і dirty-пороги занадто близько, ви не отримаєте «плавний writeback», а отримаєте «writeback починається пізно й відразу ж обмежує». Це виглядає як зависання.

Правило великого пальця: фон на 1/4–1/2 від dirty-ліміту. Наприклад: 512 MiB фон, 2 GiB dirty.

3) Скоротіть час витримки, якщо ваше навантаження створює довгоживучий бруд

vm.dirty_expire_centisecs контролює, як довго брудні дані можуть сидіти, перш ніж їх вважати «достатньо старими» для скидання. Значення за замовчуванням часто означають «до десятків секунд». Це може бути нормально, але також дозволяє повільне накопичення, яке перетворюється на шторм при тиску.

Рекомендація: Якщо ви бачите «тиху акумуляцію, потім раптове скидання», спробуйте помірно зменшити expiry (наприклад з 30с до 10–15с). Не ставте 1с і не дивуйтеся зміні пропускної здатності.

4) Не відключайте періодичний writeback, поки не розумієте наслідки

vm.dirty_writeback_centisecs контролює періодичні пробудження фонового writeback. Встановлення 0 змінює динаміку і може перенести скидання на реактивніші тригери (reclaim, sync, fsync-інтенсивні шляхи). Це не «ефективніше». Це «хаотичніше».

5) Пам’ятайте, що ви оптимізуєте: хвостову латентність

Ви не намагаєтеся виграти в бенчмарку. Ви намагаєтеся тримати p99 латентності розумною, при цьому все ще пишучи достатньо даних. Нижчі dirty-ліміти згладжують writeback і зменшують максимальний розмір сплеску. Оце й суть.

Жарт №2: Якщо ви налаштовуєте dirty-раціо на підставі «відчуття швидкості», ви погано винайшли тестування продуктивності заново.

6) Зробіть це постійним, перегляданим і з можливістю відкотити

Тимчасові sysctl-і рятують інциденти. Постійні — запобігають повторенням. Але тільки якщо вони розгортаються як будь-яка інша зміна в продакшні: перегляд колегами, документація і поступове розгортання.

cr0x@server:~$ sudo tee /etc/sysctl.d/99-dirty-writeback.conf >/dev/null <<'EOF'
# Reduce writeback storms by starting writeback earlier and capping dirty cache.
# Bytes-based thresholds are predictable across RAM sizes.
vm.dirty_background_bytes = 536870912
vm.dirty_bytes = 2147483648
vm.dirty_expire_centisecs = 1500
vm.dirty_writeback_centisecs = 500
EOF
cr0x@server:~$ sudo sysctl --system
* Applying /etc/sysctl.d/99-dirty-writeback.conf ...
vm.dirty_background_bytes = 536870912
vm.dirty_bytes = 2147483648
vm.dirty_expire_centisecs = 1500
vm.dirty_writeback_centisecs = 500

Значення: Ви застосували контрольований профіль: раніший фон, менший макс. бруд у кеші, трохи швидший expiry.

Рішення: Розгорніть на одному канарному хості, потім на частині флоту. Слідкуйте за p99 латентністю і чергуванням IO.

Запропоновані профілі (VM, БД, файлові сервери, NVMe)

Це точки старту, а не догма. Правильні значення залежать від пропускної здатності запису сховища, толерантності до IO-латентності і того, наскільки хвилеподібні ваші писачі.

Профіль A: Універсальна VM або app-сервер (чутливий до латентності)

  • dirty_background_bytes = 256–512 MiB
  • dirty_bytes = 1–2 GiB
  • dirty_expire_centisecs = 1500–3000 (15–30s)

Використовуйте, коли важливіша відгуковість, ніж потокове записування.

Профіль B: Хост бази даних (пункти довговічності + сталі записи)

  • dirty_background_bytes = 512 MiB–1 GiB
  • dirty_bytes = 2–4 GiB
  • dirty_expire_centisecs = 1000–2000 (10–20s)

Бази даних часто самі роблять flush/чекпойнти і піклуються про fsync-латентність. Менший розмір шторму зазвичай вигідний.

Профіль C: Файловий сервер / ціль бекапу (орієнтований на пропускну здатність, але без штормів)

  • dirty_background_bytes = 1–2 GiB
  • dirty_bytes = 4–8 GiB
  • dirty_expire_centisecs = 3000 (30s)

Для послідовного інжесту записів, коли користувачі терпимі до трохи вищої затримки, але не до повного зависання хоста.

Профіль D: NVMe RAID або дуже швидкий локальний SSD (уникайте «занадто великого оптимізму»)

Швидкі пристрої можуть швидко скинути дані, що спокушає підняти dirty-ліміти. Пастка в тому, що чергування все одно створює сплески, і фоновий writeback може відстати, коли шаблони журналювання/метаданих стають дивними.

  • Почніть з Профілю A або B.
  • Піднімайте тільки після вимірювання стійкої поведінки і p99 латентності.

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

Міні-історія 1: Інцидент через хибне припущення (історія «RAM — безкоштовна, правда?»)

Середня SaaS-компанія мігрувала пакетну аналітичну послугу з 64 GB вузлів на нові 256 GB вузли. Сховище лишилося приблизно того ж класу: гарні SSD за контролером, що роками справлялися. Припущення були простими: більше RAM = більше кешу = менше доступів до диска = швидші джоби.

Першого понеділка після релізу денне інжестування спрацювало швидше — поки не перестало. В середині прогону затримка API стрибнула. SSH затерп. Load average виглядав як міський горизонт. Команда спочатку звинуватила шумного сусіда у віртуалізаційному шарі, бо CPU було мало завантажено, а load високе. Класична помилка інтерпретації.

Коли вони нарешті подивилися /proc/meminfo і /proc/vmstat, стало очевидно: брудний кеш піднявся до десятків гігабайт, потім ядро обмежило писачів і почало агресивно скидати. Сховище писало швидко, але не могло одночасно «скинути 40 GB і обслуговувати випадкові читання та fsync». Робоче навантаження не змінилося; змінились лише значення за замовчуванням.

Вони виправили ситуацію порогами на байтах і нижчим фоновим тригером. Джоб все ще працював швидко, але система більше не зависала. Хибне припущення було не «кеш корисний», а «значення за замовчуванням масштабуються з апаратурою». Ні — не масштабуються.

Міні-історія 2: Оптимізація, що дала зворотний ефект (експеримент «дозвольте більше буферизувати»)

Фінтех-команда мала високопродуктивний конвеєр логів, що писав великі файли додаючи в кінець. Вони хотіли максимізувати пропускну здатність, бо нічна звірка залежала від доступності логів. Хтось запропонував підняти vm.dirty_ratio і vm.dirty_background_ratio, «щоб Linux буферизував більше і писав більшими партіями». На папері це могло покращити послідовну ефективність запису.

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

Головна проблема: піднявши раціо, вони значно збільшили максимальний слід бруду. Під час ротації і знімків writeback мав виштовхувати гору бруду одночасно. Фонові задачі—особливо ті, що виконують fsync—почали застрявати за цим скиданням. Конвеєр не просто уповільнився; він спричинив каскадні затримки в залежних системах.

Виправлення не полягало в «відновленні продуктивності» шляхом надмірного підняття — а у виборі менших dirty-лімітів і раннього фонового writeback, а також у налаштуванні конвеєра, щоб писати більш рівномірно. Їхня «оптимізація» була зміною тільки для пропускної здатності в системі із залежностями, чутливими до латентності. Ось як це дає зворотний ефект.

Міні-історія 3: Скучно, але правильно — канарка + метрики + відкат

Медіакомпанія мала флот Debian-хостів для завантажень і вихідних результатів транскодингу. Їх попередньо палили «однохлинасті sysctl-фікси», що спричиняли тонкі регресії тижнями. Тому вони ставилися до налаштування ядра як до розгортання додатка: канарка, спостереження, розширення й план відкату.

Коли writeback-шторм почав проявлятися під час пікових вікон завантажень, вони не вкидали зміни на весь флот. Вони вибрали один репрезентативний хост, застосували пороги в байтах і спостерігали дві речі: IO PSI full і затримку запитів на краю додатка. Також дивилися на помилки, бо нічого так не говорить «ой» як тайм-аути, що маскуються як успіх.

Канарка показала поліпшення: менше IO-застоїв, менша хвостова латентність і відсутність краху пропускної здатності. Вони розгорнули на 10% флоту, потім на 50%. Один кластер зі старими SATA SSD відчув невелике падіння пропускної здібності, тому вони трохи підняли dirty_bytes лише для цього класу обладнання. Жодної драми, жодних переговорів в кризовому залі, без героїзму.

Скучна практика виявилася переможною: контрольоване розгортання плюс змістовні метрики. Коли ви налаштовуєте writeback, найкращий інструмент не sysctl — це стриманість.

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

1) Симптом: періодичні «зависання» всього хоста з високим iowait

Корінь: Брудний кеш накопичується до dirty-ліміту, потім writeback сплеском насичує сховище і обмежує все.

Виправлення: Використовуйте пороги в байтах; зменшіть dirty_bytes і dirty_background_bytes, щоб writeback починався раніше. Перевіряйте /proc/meminfo Dirty/Writeback і iostat -x глибину черги.

2) Симптом: пропускна здатність в порядку, але p99 латентність стрибає під змішаним навантаженням

Корінь: Ефекти чергування від сплесків writeback; журналювання і fsync-конфлікти з масовим скиданням.

Виправлення: Зменшіть dirty-ліміти (менші сплески). Перевірте процеси з частими fsync і налаштуйте частоту флашів на рівні додатка, якщо можливо.

3) Симптом: налаштування раціо «працює» на одному класі хостів, але не на іншому

Корінь: Ратіо-пороги масштабуються з пам’яттю і з уявленням ядра про «доступну пам’ять», яке змінюється за навантаженням і роллю хоста.

Виправлення: Стандартизувати на dirty_bytes/dirty_background_bytes по класах обладнання.

4) Симптом: після зниження dirty-лімітів масові писачі суттєво сповільнюються

Корінь: Ви обмежили занадто рано відносно стійкої пропускної здатності диска; фоновий writeback не встигає, і писачі постійно чекають.

Виправлення: Трохи підвищте dirty_background_bytes (щоб починати writeback раніше, але залишити трохи ресурсу) і/або помірно підвищте dirty_bytes. Підтвердіть пропускну здатність і латентність диска; якщо пристрій занадто повільний, налаштування не створять додаткової смуги.

5) Симптом: «sync» або знімки викликають багатохвилинні паузи

Корінь: Великий backlog бруду зустрічає форсований тригер скидання (sync, snapshot, commit файлової системи), що породжує хвилю записів і активність журналу.

Виправлення: Утримуйте backlog малим за допомогою порогів у байтах. Плануйте знімки поза піками. Переконайтеся, що ваше інструментування знімків не змушує зайвого глобального sync.

6) Симптом: штормі відбуваються головним чином всередині VM, а не на bare metal

Корінь: Подвійне кешування і взаємодія writeback між гостем і гіпервізором. Кожен шар буферизує, потім скидає хвилею.

Виправлення: Використовуйте консервативні dirty-bytes всередині гостей. Якщо можливо, узгодьте налаштування зберігання гіпервізора і уникайте крайнощів буферизації на обох рівнях. Вимірюйте як у гості, так і на хості.

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

Покроково: від інциденту до стабільної конфігурації

  1. Підтвердіть, що це writeback. Перевірте Dirty/Writeback в /proc/meminfo, IO PSI та чергу iostat.
  2. Визначте гарячий пристрій. Знайдіть насичений блочний пристрій і підтвердіть відповідність файловій системі.
  3. Захопіть короткий базовий слайс. Збережіть поточні sysctl і 2–3 хвилинну зйомку IO-статистики під час події.
  4. Застосуйте тимчасові пороги у байтах. Почніть з 512 MiB фонового та 2 GiB dirty. Уникати зміни опцій монтування під час інциденту.
  5. Спостерігайте форму. Dirty має коливатись нижче порога; глибина черги має падати; латентність має згладжуватись.
  6. Перевірте стан додатків. p99 затримка, кількість помилок та затримка комітів БД, якщо застосовно.
  7. Збережіть зміну. Використовуйте /etc/sysctl.d/ з коментарями, що пояснюють чому.
  8. Канарне розгортання. Один хост → невелика частка → весь флот, з дашбордами, що включають IO PSI і disk await.
  9. Тонке налаштування по класам. Старі диски та мережеві блочні пристрої можуть потребувати інших обмежень, ніж локальні NVMe.
  10. Напишіть нотатку в ранбуку. «Якщо бачиш A/B/C, перевір ці значення і ці графіки.» Майбутнє ви — зацікавлена сторона.

Чекліст безпеки (список «не створюйте нові інциденти»)

  • Не піднімайте dirty-ліміти під час інциденту, якщо ви не впевнені, що обмеження тільки за пропускною здатністю, а не за латентністю.
  • Не змішуйте ратіо і байти «бо обидва здаються важливими». Виберіть байти для передбачуваності.
  • Не відключайте таймери writeback як перший крок.
  • Не змінюйте режими журналювання або налаштування бар’єрів, щоб виправити шторм. Це не налаштування — це гра на виживання.
  • Тримайте dirty-обмеження консервативними на системах без захисту від втрати живлення.
  • Тестуйте на тому самому патерні навантаження, що викликає шторм (вікно пакетної обробки, вікно бекапу, вікно компресій).

FAQ

1) Чи є writeback-шторм помилкою Debian 13?

Зазвичай ні. Це невідповідність між значеннями за замовчуванням, розміром ОЗП і хвилеподібністю вашого навантаження. Debian 13 просто робить проблему помітнішою, бо сучасні стекі виражають хвостову латентність чіткіше.

2) Чи використовувати vm.dirty_ratio чи vm.dirty_bytes?

На серверах, де важлива передбачуваність (а це більшість серверів), використовуйте байти. Раціо можуть підходити для невеликих однорідних машин, але вони погано масштабуються з ростом ОЗП.

3) Чи підвищення безпечності даних відбувається при зниженні dirty-лімітів?

Це зменшує обсяг неподаних на диск даних у пам’яті, тож експозиція при втраті живлення зменшується. Але це не заміна правильним практикам довговічності (журналювання, коректні семантики flush, UPS/PLP у зберіганні).

4) Чи можу я встановити дуже низькі dirty-ліміти, щоб усунути шторм?

Можете, але ви можете просто перетворити шторм у постійне обмеження. Мета — менший, безперервний writeback — не змусити кожного писача поводитися як синхронний IO весь час.

5) А що з базами даних, що використовують O_DIRECT або direct IO?

Direct IO обходить page cache для файлів даних, що зменшує тиск брудних сторінок від цього навантаження. Але бази даних все одно записують логи, метадані та інші файли через кеш, і решта системи теж використовує page cache. Налаштування dirty може і далі бути важливим.

6) Чи варто налаштовувати vm.swappiness замість цього?

Swappiness впливає на поведінку reclaim і використання swap; воно може вплинути, коли writeback тригериться під тиском пам’яті, але це не основний інструмент для writeback-штормів. Виправте пороги dirty спочатку, потім дивіться на reclaim, якщо ще бачите трешинг.

7) Чому штормі відбуваються «випадково»?

Вони часто тригеряться періодичними подіями: ротація логів, бекапи, компресії, знімки або тиск пам’яті від нового навантаження. Корелюйте час з cron/systemd таймерами і розкладами додатків.

8) Чи краща зміна IO scheduler за налаштування dirty?

Іноді планувальники допомагають хвостовій латентності під навантаженням, але вони не усувають корінь проблеми «занадто багато брудних даних, які скидаються занадто пізно». Тюнінг scheduler без налаштування writeback — полірування неправильної частини машини.

9) Як зрозуміти, що я не просто замаскував проблему?

Якщо пристрій все ще завантажений на 100% і черга залишається глибокою, ви не вирішили вузьке місце; ви лише змінили, коли воно болить. Хороше рішення зменшує час простою (IO PSI), зменшує глибину черги і покращує затримку додатка без різкого зростання помилок.

Наступні кроки, які можна зробити сьогодні

Якщо ви бачите writeback-шторм на Debian 13, не починайте з упереджених дій. Почніть з доказів: Dirty/Writeback рівнів, IO PSI і глибини черги диска. Потім зробіть одну дисципліновану зміну: перейдіть від ратіо-based dirty до caps у байтах, що відповідають реальності вашого сховища.

Зробіть це далі:

  1. Запустіть швидкі діагностичні перевірки і зафіксуйте базову метрику під час шторму.
  2. Застосуйте тимчасовий профіль: dirty_background_bytes=512MiB, dirty_bytes=2GiB, і опційно dirty_expire_centisecs=1500.
  3. Підтвердіть, що шторм зменшився: Dirty тримається в межах, глибина черги диска падає, p99 покращується.
  4. Збережіть конфіг у /etc/sysctl.d/ з коментарями, проведіть канарку, потім розгорніть.

Вам не потрібно усунути writeback. Потрібно не давати йому з’являтися одночасно, як неоплачений рахунок із відсотками.

← Попередня
Proxmox ZFS «пул не в здоровому стані»: що робити, поки не стало гірше
Наступна →
Права доступу веб‑кореня в Debian/Ubuntu: припиніть 403 без 777 (Випадок №69)

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