Все «помірно добре», поки раптом ні. p99 API спрацьовує на обмежувачі, база даних зависає, панелі виглядають як сейсмограф, і хтось вимовляє фразу, яку ви почуєте у кожній компанії завжди: «Додаток не змінювався.»
Сплески латентності диска — класична гра у пошук винуватців. Це протиотрута: робочий процес для Debian/Ubuntu, який дає докази, а не відчуття — щоб ви могли довести, що це сховище (або довести, що ні), а потім виправити правильну річ.
Швидкий план діагностики
Якщо у вас є 10 хвилин — робіть це в порядку. Хитрість у тому, щоб відокремити латентність від пропускної здатності, і блоковий пристрій від файлової системи від додатку. Завантажений диск може бути нормальним; диск з випадковими паузами 2–20 секунд зіпсує вам день.
1) Підтвердіть симптом: чи ми застигли на вв/вив?
- Перевірте систему в цілому: запустіть
vmstat 1і дивіться на високийwa(iowait) під час сплеску. - Перевірте по-пристрою: запустіть
iostat -x 1і дивіться, чи зростаєawaitта%utilпід час сплеску. - Перевірте чергування: якщо
avgqu-szзростає, ви складаєте запити швидше, ніж пристрій їх завершує.
2) Визначте «жертву»: який процес заблокований?
- Зніміть список заблокованих задач:
ps -eo pid,stat,wchan:25,comm | awk '$2 ~ /D/'. - Скорелюйте з додатком: робочі потоки БД у стані
D— ОС каже «я чекаю на сховище».
3) Визначте: локальний диск, віртуальний диск чи віддалене сховище?
- Спроєктуйте mount → блоковий пристрій:
findmnt -no SOURCE,TARGET /your/mount. - Потім:
lsblk -o NAME,TYPE,SIZE,FSTYPE,MOUNTPOINTS,ROTA,MODEL, щоб побачити, чи у вас NVMe, SATA SSD, HDD, dm-crypt, LVM, MD RAID, multipath або хмарний віртуальний диск.
4) Якщо це сплеск: трасуйте, а не усереднюйте
- Використовуйте
biosnoop(bcc) абоbpftraceдля відлову латентних викидів. - Якщо не можете — застосуйте
blktrace/blkparseі шукайте великі проміжки між диспетчеризацією та завершенням.
5) Підтвердіть контрольованим відтворенням
- Запустіть безпечний профіль
fioпроти тестового файлу на тій же файловій системі і подивіться, чи хвостові p99/p999 збігаються з симптомами продакшену.
Що насправді означає «сплеск латентності диска»
Латентність диска — це час між «ядро відправляє запит блокового вводу/виводу» і «ядро отримує підтвердження завершення». Якщо цей час сплескує, усе вище по ланцюжку стає неправдивим: потік додатку виглядає «повільним», блокування — «конкурентними», черги — «таємниче» ростуть, а люди починають переписувати код замість того, щоб виправити вузьке місце.
Є три загальні форми сплесків:
- Чергові сплески: латентність росте, бо ви насичуєте пристрій або бекенд. Симптоми: високий
%util, високеavgqu-sz, зростаючийawaitпри стабільних IOPS. - Пауза-сплески: латентність стрибає з кількох мс до секунд при невеликій зміні пропускної здатності. Симптоми: періодичні багатосекундні зупинки; іноді
%utilнавіть не здається перевантаженим. Причини: глюки прошивки, збір сміття, обмеження віддаленого бекенду або коміти журналу. - Сплески-ампліфікації: невеликі записі перетворюються на багато записів (журнальні файлові системи, copy-on-write, RAID-парність, шифрування). Симптоми: додаток робить «розумний» ввід/вивід, а сховище виконує значно більше роботи, латентність зростає під навантаженням.
Цитата, яку варто мати на стікері:
«Надія — це не стратегія.» — General Gordon R. Sullivan
Також: ваш додаток може бути невинним і все одно стати тригером. Зміна навантаження без деплою (новий орендар, інша форма запиту, побудова індексу, фонове компактування) може штовхнути сховище за край. Ваше завдання — довести, що цей край існує.
Жарт №1: Латентність диска — як зустріч, яка «займе тільки п’ять хвилин». Вона не займе.
Цікаві факти та контекст (бо історія повторюється)
- «iowait» — це не «диск повільний». Це час CPU, проведений в простої, поки в системі є незавершені вв/вив запити. CPU-важкий додаток може мати низький iowait і одночасно жахливу латентність сховища.
- Linux elevator колись був головною фічею. Ранні планувальники як anticipatory і CFQ проектувалися для обертових дисків і інтерактивності; SSD та NVMe змістили баланс у бік mq-deadline/none.
- NCQ і глибокі черги змінили режими відмов. SATA NCQ дозволив пристроям переупорядковувати запити; водночас це зробило «одна погана команда зависає чергу» помітнішою, коли прошивка помиляється.
- SSD можуть паузити під час «прибирання». Garbage collection і wear leveling можуть викликати періодичні сплески латентності, особливо коли диск близький до заповнення або без додаткового overprovisioning.
- Журналювання обміняло втрату даних на передбачуваність латентності. ext3/ext4 з журналом зробили аварії менш драматичними, але поведінка комітів може створювати періодичні піки запису та синхронні затримки.
- Бар’єри запису стали дефолтними не випадково. Бар’єри (flush/FUA семантика) перешкоджають перемішуванню, що могло б пошкодити метадані після втрати живлення; вони також можуть виявити повільний шлях очищення кеша.
- Віртуальні диски — це політичні кордони. У хмарі ваш «диск» — це зріз спільного бекенду; обмеження і кредити на «burst» можуть призвести до несподіваних сплесків латентності.
- RAID ховає проблеми пропускної здатності краще, ніж проблеми латентності. Додавши шпинделі, ви отримаєте більше MB/s, але малі випадкові записи на паритетних RAID все одно платять ціну.
Практичні завдання: команди, що означає вивід і що робити далі
Цей розділ навмисно практичний. Ви хочете відтворювані артефакти: логи, часові позначки та оповідь, що переживе наступну нараду.
Завдання 1: Встановіть істину (ядро, пристрій, віртуалізація)
cr0x@server:~$ uname -a
Linux db-01 6.5.0-28-generic #29~22.04.1-Ubuntu SMP PREEMPT_DYNAMIC x86_64 GNU/Linux
cr0x@server:~$ systemd-detect-virt
kvm
cr0x@server:~$ lsb_release -ds
Ubuntu 22.04.4 LTS
Значення: Версія ядра і віртуалізація суттєво впливають на поведінку сховища (multi-queue, дефолти планувальника, virtio). Якщо це VM, треба думати про «галасливих сусідів» і обмеження бекенду.
Рішення: Якщо віртуалізовано, збирайте докази, що витримають розмову «це ваша гостьова ОС»: латентність по-пристрою, глибина черг, сигнали троттлінгу і кореляція по часу.
Завдання 2: Скоштуйте відповідність монтувань до блокових пристроїв (не гадати)
cr0x@server:~$ findmnt -no SOURCE,TARGET,FSTYPE,OPTIONS /var/lib/postgresql
/dev/mapper/vg0-pgdata /var/lib/postgresql ext4 rw,relatime,discard
cr0x@server:~$ lsblk -o NAME,TYPE,SIZE,FSTYPE,MOUNTPOINTS,ROTA,MODEL
NAME TYPE SIZE FSTYPE MOUNTPOINTS ROTA MODEL
vda disk 500G 1 QEMU HARDDISK
└─vda2 part 499.5G LVM2_member 1
├─vg0-root lvm 50G ext4 / 1
└─vg0-pgdata lvm 449G ext4 /var/lib/postgresql 1
Значення: Додаток розташований на LVM поверх vda. Прапорець ROTA показує «1» (HDD) навіть у віртуальному середовищі; трактуйте як «не NVMe-швидко». Також зверніть увагу на discard — це може мати значення.
Рішення: Ваш вузький профіль може бути під LVM (virtio, хостове сховище, мережевий блок). Слідкуйте за стеком: device-mapper додає черги і складність.
Завдання 3: Спостерігайте системний тиск, а не лише усереднення
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 0 612304 64216 931224 0 0 120 410 410 880 12 4 82 2 0
1 0 0 610992 64216 931260 0 0 180 512 398 860 10 4 84 2 0
1 5 0 610120 64216 930800 0 0 140 2100 520 1200 6 3 60 31 0
0 6 0 609880 64216 930744 0 0 90 1800 540 1300 5 3 58 34 0
1 0 0 610400 64216 931100 0 0 110 600 420 900 9 4 84 3 0
Значення: Під час сплеску зростає b (заблоковані процеси) і wa підстрибує до 31–34%. Це реальний підпис простою.
Рішення: Якщо заблоковані процеси ростуть під час сплесків латентності — переходьте до метрик по-пристрою. Якщо wa низький, але p99 латентність висока, додаток може бути CPU-зв’язаний або чекати блокувань, а не вв/вив.
Завдання 4: Отримайте латентність по-пристрою і сигнали чергування
cr0x@server:~$ iostat -x 1 5
Linux 6.5.0-28-generic (db-01) 12/30/2025 _x86_64_ (8 CPU)
Device r/s w/s rkB/s wkB/s rrqm/s wrqm/s %rrqm %wrqm r_await w_await aqu-sz rareq-sz wareq-sz svctm %util
vda 8.00 95.00 512.0 8240.0 0.00 12.00 0.00 11.21 3.10 18.40 1.90 64.0 86.7 1.20 12.40
vda 7.00 110.00 448.0 9100.0 0.00 10.00 0.00 8.33 3.40 120.50 14.20 64.0 82.7 1.50 17.50
vda 6.00 105.00 384.0 9000.0 0.00 11.00 0.00 9.48 3.80 240.10 28.30 64.0 85.7 1.60 18.00
vda 9.00 98.00 576.0 8600.0 0.00 14.00 0.00 12.50 3.20 22.00 2.30 64.0 87.8 1.30 13.50
vda 8.00 92.00 512.0 8100.0 0.00 12.00 0.00 11.54 3.10 19.30 2.00 64.0 88.0 1.20 12.60
Значення: Записи — проблема (w_await підстрибує до 120–240ms), тоді як %util не дуже високий. Це класичний випадок «бекенд уповільнився» або «шлях flush/commit» швидше, ніж «пристрій насичений». aqu-sz зростає під час сплесків: чергування відбувається.
Рішення: Коли await підстрибує, але завантаження не зашкалює, підозрюйте паузи: flush-ї, тонке виділення, тротлінг віддаленого бекенду або конкуренцію на хості. Перейдіть до трасування і дослідження flush/черг.
Завдання 5: Підтвердіть, які процеси у невідмінному сні (стан D)
cr0x@server:~$ ps -eo pid,stat,wchan:25,comm | awk '$2 ~ /D/'
18423 D io_schedule postgres
18431 D io_schedule postgres
21102 D ext4_writepages postgres
Значення: Робочі процеси Postgres заблоковані в шляхах очікування ядра, пов’язаних з вв/вив. Це не просто «повільний SQL»; це «пізнє завершення операцій сховища».
Рішення: Якщо потоки додатку у стані D під час вікон сплесків, пріоритет — доказам на блоці і причині на боці сховища. Якщо вони runnable (R), але повільні — зосередьтеся на CPU, блокуваннях, GC або мережі.
Завдання 6: Читайте PSI для доведення системної I/O конкуренції
cr0x@server:~$ cat /proc/pressure/io
some avg10=0.28 avg60=0.22 avg300=0.15 total=184329210
full avg10=0.07 avg60=0.05 avg300=0.03 total=40210299
Значення: PSI показує, як часто задачі затримуються в очікуванні вв/вив. full означає часи, коли в системі не було runnable задач, бо всі чекали на вв/вив. Це сильний сигнал «сховище гейтить машину».
Рішення: Якщо PSI full росте під час сплесків — трактуйте це як інфраструктурну проблему, а не додаток. Якщо PSI тихий — латентність може бути всередині додатку (блокування) або в кеші файлової системи (page faults), а не у блоці вв/вив.
Завдання 7: Перевірте планувальник блочного пристрою та налаштування черги
cr0x@server:~$ cat /sys/block/vda/queue/scheduler
[mq-deadline] none
cr0x@server:~$ cat /sys/block/vda/queue/nr_requests
256
cr0x@server:~$ cat /sys/block/vda/queue/read_ahead_kb
128
Значення: Вибір планувальника важливий для латентності. mq-deadline часто хороший дефолт для справедливості на блокових пристроях. Глибина черги (nr_requests) впливає на поведінку сплесків і хвостову латентність.
Рішення: Не «тонюйте» всліпу. Якщо ви бачите довгі хвости латентності, можливо потрібно зменшити чергування, щоб тримати латентність у межах, особливо на спільних бекендах. Плануйте контрольовані тести.
Завдання 8: Перевірте опції монтування файлової системи, що можуть примушувати синхронність
cr0x@server:~$ findmnt -no TARGET,FSTYPE,OPTIONS /var/lib/postgresql
/var/lib/postgresql ext4 rw,relatime,discard
cr0x@server:~$ tune2fs -l /dev/mapper/vg0-pgdata | egrep 'Filesystem features|Journal features'
Filesystem features: has_journal ext_attr resize_inode dir_index filetype extent 64bit flex_bg sparse_super large_file huge_file dir_nlink extra_isize metadata_csum
Journal features: journal_incompat_revoke journal_64bit journal_checksum_v3
Значення: discard може викликати сплески латентності в залежності від бекенду. Сучасна рекомендація часто — періодичний fstrim, а не inline discard для чутливих до латентності робочих навантажень.
Рішення: Якщо сплески збігаються зі видаленнями/vacuum/компактацією — спробуйте вимкнути discard і використовувати запланований fstrim. Підтвердіть через change control і вимірювання.
Завдання 9: Спостерігайте flush-и і поведінку writeback (throttling)
cr0x@server:~$ sysctl vm.dirty_background_ratio vm.dirty_ratio vm.dirty_expire_centisecs vm.dirty_writeback_centisecs
vm.dirty_background_ratio = 10
vm.dirty_ratio = 20
vm.dirty_expire_centisecs = 3000
vm.dirty_writeback_centisecs = 500
cr0x@server:~$ grep -E 'Dirty:|Writeback:' /proc/meminfo
Dirty: 182340 kB
Writeback: 2048 kB
Значення: Пороги «dirty pages» визначають, коли ядро примушує writeback. Коли ви досягаєте dirty_ratio, записуючі процеси можуть жорстко тротлитися, що виглядає як випадкові сплески латентності.
Рішення: Якщо сплески корелюють з хвилями «dirty» пам’яті і writeback, тонко налаштуйте dirty-параметри або зменшіть write amplification (батчинг у додатку, налаштування БД). Будьте обережні; ці ручки можуть зробити гірше.
Завдання 10: Захопіть латентні викиди сховища з BPF (biosnoop з bcc)
cr0x@server:~$ sudo biosnoop -Q -d vda
TIME(s) COMM PID DISK T SECTOR BYTES LAT(ms)
12.4321 postgres 18423 vda W 9132456 16384 14.92
12.4398 postgres 18431 vda W 9132488 16384 18.77
12.9802 postgres 18423 vda W 9132520 16384 942.51
13.0054 postgres 18431 vda W 9132552 16384 1103.44
Значення: Це «грошовий кадр»: реальні I/O запити з виміряною латентністю, приписані процесу. Ці 900–1100ms записи пояснюють p99 тайм-аути додатку.
Рішення: Якщо BPF показує викиди на блоці, можна припинити суперечки про додаток. Тепер з’ясуйте чому: flush-и, троттлінг, паузи бекенду, чергування або помилки пристрою.
Завдання 11: Трасуйте блоковий шар blktrace для глибшого таймінгу
cr0x@server:~$ sudo blktrace -d /dev/vda -o - | blkparse -i -
8,0 1 1 0.000000000 18423 Q WS 9132520 + 32 [postgres]
8,0 1 2 0.000310215 18423 G WS 9132520 + 32 [postgres]
8,0 1 3 0.000482906 18423 I WS 9132520 + 32 [postgres]
8,0 1 4 0.000650102 18423 D WS 9132520 + 32 [postgres]
8,0 1 5 0.942912433 18423 C WS 9132520 + 32 [0]
Значення: Показує життєвий цикл запиту: queued (Q), dispatched (D), completed (C). Тут завершення відбувається приблизно через 0.942s після dispatch. Це час пристрою/бекенду, а не вашого SQL-парсера.
Рішення: Якщо більшість часу між D і C — фокус на пристрої/бекенді. Якщо затримка між Q і D — чергування в ОС (планувальник/глибина черги), часто через насичення або стек device-mapper.
Завдання 12: Перевірте повідомлення ядра про проблеми з пристроєм (нудні логи важливі)
cr0x@server:~$ sudo dmesg -T | egrep -i 'blk|I/O error|timeout|reset|nvme|scsi|ext4|xfs' | tail -n 8
[Mon Dec 30 10:12:41 2025] blk_update_request: I/O error, dev vda, sector 9132520 op 0x1:(WRITE) flags 0x0 phys_seg 2 prio class 0
[Mon Dec 30 10:12:41 2025] Buffer I/O error on dev dm-1, logical block 1141568, lost async page write
[Mon Dec 30 10:12:41 2025] EXT4-fs warning (device dm-1): ext4_end_bio:345: I/O error 10 writing to inode 262411 starting block 1141568)
Значення: Якщо є I/O помилки або ресети — латентність уже не головна історія. У вас ризик коректності. Сплески можуть бути пов’язані з повторними спробами, ремапами чи тайм-аутами бекенду.
Рішення: Ескалація негайно: команда storage/провайдер хмари/власник гіпервізора. Плануйте відмовостійкість і перевірки цілісності даних, а не мікрооптимізації.
Завдання 13: Перевірте поведінку TRIM (discard проти запланованого trim)
cr0x@server:~$ systemctl status fstrim.timer
● fstrim.timer - Discard unused blocks once a week
Loaded: loaded (/lib/systemd/system/fstrim.timer; enabled; vendor preset: enabled)
Active: active (waiting) since Mon 2025-12-30 09:00:01 UTC; 1h 12min ago
Trigger: Mon 2026-01-06 00:00:00 UTC; 6 days left
cr0x@server:~$ sudo fstrim -v /var/lib/postgresql
/var/lib/postgresql: 94.3 GiB (101251604480 bytes) trimmed
Значення: Якщо ви можете запланувати trim, часто можна позбутися inline discard. Це може зменшити сплески латентності під час масових видалень.
Рішення: Віддавайте перевагу fstrim.timer у багатьох бекендах. Якщо ви на тонкошарованому SAN або в певних хмарних дисках — перевірте з провайдером/командою storage.
Завдання 14: Відтворіть з fio і дивіться на хвостову латентність, а не лише IOPS
cr0x@server:~$ fio --name=latcheck --filename=/var/lib/postgresql/fio.test --size=2G --direct=1 --ioengine=libaio --rw=randwrite --bs=16k --iodepth=16 --numjobs=1 --time_based --runtime=30 --group_reporting --output-format=normal
latcheck: (g=0): rw=randwrite, bs=(R) 16.0KiB-16.0KiB, (W) 16.0KiB-16.0KiB, ioengine=libaio, iodepth=16
fio-3.33
latcheck: Running 1 job
write: IOPS=1420, BW=22.2MiB/s (23.3MB/s)(666MiB/30001msec)
slat (usec): min=6, max=421, avg=14.52, stdev=9.30
clat (msec): min=0, max=1840, avg=10.62, stdev=41.10
lat (msec): min=0, max=1840, avg=10.65, stdev=41.11
clat percentiles (msec):
| 50.00th=[ 1], 90.00th=[ 3], 99.00th=[ 120], 99.90th=[ 820], 99.99th=[1700]
Значення: Медіана в порядку; хвіст — жахливий. Саме так відчуває продакшен: здебільшого нормально, іноді катастрофічно. Хвостові перцентилі підтверджують сплескову поведінку навіть у контрольованому тесті.
Рішення: Якщо fio відтворює хвостові сплески — це не ваш додаток. Тепер можна тестувати виправлення (планувальник, discard, глибина черги, dirty-настройки) і вимірювати покращення.
Побудуйте аргумент: сховище проти додатку (як довести, не розвʼязавши війну)
«Доведіть, що це сховище» означає побудувати ланцюжок доказів від видимої користувачем латентності до часу завершення I/O на рівні ядра. Вам потрібні кореляція, атрибуція і правдоподібний механізм.
Сходи доказів (використовуйте як у суді)
- Користувацький симптом: p95/p99 сплески латентності, таймаути, хвилі повторних запитів, зростання черг.
- Симптом хоста: заблоковані задачі, PSI I/O тиск, підвищений iowait в інцидентні вікна.
- Симптом пристрою:
iostat -xпоказує сплескиawaitі зростання черги; іноді без насичення. - Доказ по кожному I/O: BPF-інструменти або blktrace показують окремі I/O, які займають 200ms–секунди, приписані процесу і пристрою.
- Механізм: щось пояснює чому: flush-шторм, contention метаданих тонкого пулу, троттлінг хмари, discard, RAID-пенальті, GC прошивки, мультипат нечіткість тощо.
Чого не робити
- Не використовуйте «CPU iowait високий» як єдиний аргумент. Це натяк, а не доказ.
- Не сліпо вірте
svctm. На сучасних ядрах і мульти-стекових пристроях воно часто вводить в оману. - Не усереднюйте хвостову латентність. Сплески живуть у p99/p999, а не в середньому.
Як проблеми додатку маскуються під сховище (і як їх розділити)
Іноді додаток винен, а сховище — свідок. Ось поширені підробники:
- Конкуренція за блокування: потоки додатку, які чекають на mutex, виглядають як «усе повільно», але ядро показує runnable-задачі, а не стан D — вв/вив чек.
- Паузи GC або компактування: додаток зупиняється, але диск стабільний; CPU може сплескати або паузи періодичні.
- Залежність від мережі: віддалені виклики додають латентність; диск залишається в нормі; заблоковані процеси не в стані вв/вив.
- Промахи файлового кешу: major page fault можуть виглядати як вв/вив, але ви побачите шаблон у
vmstatі perf-лічильниках; це все ще пов’язано зі сховищем, але на іншому рівні.
Жарт №2: Команда додатку скаже, що це сховище; команда сховища скаже, що це додаток. Вітаємо — ви тепер надаєте дипломатичні послуги як сервіс.
Три корпоративні міні-історії (як це ламається в реальному житті)
Міні-історія 1: Інцидент через хибне припущення
У компанії була служба оформлення замовлень на Ubuntu VM, підкріплена керованим блочним томом. Новий партнерський інтегратор запустили в понеділок. Ніяких деплоїв, ніяких змін у схемі, нічого явного. До обіду p99 латентності підскочили до секунд, і чат on-call робив те, що роблять чати on-call: продукував теорії швидше, ніж дані.
Домінуюче припущення: «Якщо б проблема була в сховищі, ми б бачили 100% використання диска.» Графіки показували %util у районі десятків. Хтось оголосив сховище невинним і звинуватив API партнера. Інженери почали додавати кешування, коригувати таймаути і логіку повторів — чим підвищили навантаження на базу.
Пізніше SRE запустив biosnoop і зловив періодичні 800–1500ms записів на томі. Рівень запитів не був високим; бекенд просто інколи був повільним. Відсутня концепція була в тому, що сплески латентності можуть траплятися без локальної насиченості, коли ви на спільному або тротлінгованому бекенді.
Виправлення не було героїчним: навантаження змістилося у бік дрібних записів. Вони підняли клас тому на той з кращою базовою латентністю і прибрали опцію монтування, яка викликала синхронні дискарди під масивними видаленнями. API партнера був в порядку. Первісне припущення — ні.
Міні-історія 2: Оптимізація, що відкотилася назад
Команда хотіла пришвидшити нічні батчі. Хтось помітив налаштування dirty-сторінок ядра і вирішив «дати Linux більше буфера», піднявши vm.dirty_ratio і vm.dirty_background_ratio. Батч став швидшим у першу годину. Slack святкував. Change request написали постфактум. Ви вже розумієте, куди це йшло.
У продакшені денний трафік теж пише. З підвищеними dirty-порогами ядро накопичило більше «брудних» даних, потім викинуло їх більшими writeback-сплесками. Середній стан сховища не був насиченим, але сплеск створив багатосекундні паузи, коли система досягла dirty-ліміту і тротлінгувала писачів.
База даних не просто сповільнилася; вона почала таймаутити клієнтські запити, що викликало повтори і посилило тиск на запис. Команда додатку бачила таймаути і звинувачувала плани запитів. Інфра-команда бачила прийнятний середній IOPS і знизувала плечі. Хвостова латентність була єдиним важливим показником, і вона горіла.
Відкат відновив стабільність одразу. Тривале виправлення — дисципліноване тестування навантаження з відстеженням p99/p999 і окреме вікно для батчів з обмеженням швидкості. «Оптимізація» була реальною для пропускної здатності і катастрофічною для латентності. Обидва можуть бути правдою.
Міні-історія 3: Сумна, але правильна практика, що врятувала день
В іншому місці команда платформи зберігання мала нудну, але робочу звичку: кожен хост експортував невеликий набір SLO-метрик сховища — await пристрою, PSI I/O full і гістограму блокової латентності з eBPF-програми, зібрану під час піків. Вони також вели інвентар опцій монтування і стеків device mapper.
Одного четверга кілька сервісів почали синхронізовано показувати p99 сплески у різних додатках. Оскільки телеметрія була послідовною, on-call не почав з «що змінилося в додатку». Вони почали з: «Що спільного в цих хостах?» Гістограма показала довгі хвости записів на томах, прикріплених до одного кластеру гіпервізорів.
Вони зняли знімки процесів у стані D і підтвердили, що кілька процесів блоковані в io_schedule по різних сервісах. Це змінило розмову з «баг у додатку» на «спільний бекенд сховища». Власник гіпервізора знайшов подію обслуговування, яка перевела storage pool у деградований стан. Жодна окрема VM не була «на межі». Бекенд був.
Результат був антиграндіозним: навантаження перемістили з постраждалих хостів, pool відновили. Рятівним фактором не було геніальності. Це була нудна вимірність плюс звичка корелювати час, пристрій і процес. В опсах нудне — це перевага.
Виправлення, які дійсно рухають стрілку (в порядку частоти успіху)
Після того, як ви довели, що латентність у шляху сховища, виправлення повинні бути спрямовані на спостережуваний механізм. Не робіть «розстрілу по всіх шарах» з sysctl і надії. Хвостова латентність карає за суєту.
1) Виправте клас/ліміти бекенду (хмари та віртуалізовані середовища)
Якщо ви на хмарному блочному томі або спільному SAN, ви можете стикатися з троттлінгом, вичерпанням burst-кредитів або конфліктом галасливих сусідів. Шаблон доказів: await підстрибує без локальної насиченості, хвилі у fio tail, іноді повторювана періодичність.
- Дія: перейдіть на клас тому з кращою базовою латентністю / provisioned IOPS; зменшіть варіацію, заплативши за це.
- Дія: розподіліть гарячі дані по кількох томах (striping на рівні додатка/БД або LVM), якщо підходить.
- Уникайте: «просто додати повтори». Повтори перетворюють дрібні інциденти в відмови.
2) Приберіть inline discard для чутливих до латентності файлових систем (використовуйте fstrim)
Inline discard може перетворювати видалення у синхронні операції trim. На деяких бекендах це нормально; на інших — мінне поле латентності.
- Дія: приберіть
discardз fstab для даного тому, перемонтуйте і покладайтеся наfstrim.timer. - Валідація: запустіть fio і спостерігайте покращення хвостової латентності.
3) Обмежте чергування, щоб зменшити хвостову латентність
Глибокі черги підвищують пропускну здатність, поки не знищать p99. Для спільних бекендів величезні глибини черг можуть перетворити коротку паузу в довгу, складаючи роботу за нею.
- Дія: тестуйте планувальники
mq-deadlinevsnoneдля NVMe/virtio. Обирайте той, що зменшує хвіст латентності для вашого навантаження, а не той, що виграє на мікротестах. - Дія: розгляньте зменшення глибини черги (provider-specific у VM; у Linux — черга пристрою та iodepth додатка мають значення).
4) Усуньте write amplification
Якщо додаток робить дрібні випадкові записи — кожен шар може примножити це в більше I/O, ніж ви думаєте.
- Дія: для баз даних узгодьте чекпоінти/flush з характеристиками сховища; уникайте налаштувань, що створюють великі періодичні flush-штормы.
- Дія: перевірте, чи ви випадково не на паритетному RAID для навантаження з дрібними записами; розгляньте дзеркала/striped mirrors для чутливих до латентності записів.
- Дія: уникайте наслаювання LVM + dm-crypt + MD RAID, якщо це не необхідно; кожен шар додає черги і режим відмови.
5) Налаштування dirty-сторінок (тільки з вимірюванням)
Тюнінг writeback ядра може зменшити бурстовість, але легко зробити гірше. Безпечна позиція: дрібні зміни, тест під навантаженням, моніторинг p99 write latency і SLO додатку.
- Дія: якщо ви бачите періодичні паузи при досягненні dirty-ліміту — трохи знизьте
vm.dirty_ratio, щоб змусити плавніший writeback. - Дія: розгляньте
vm.dirty_background_bytesіvm.dirty_bytesзамість відсотків на хостах з варіабельним об’ємом пам’яті.
6) Виправте шляхи помилок і проблеми прошивки
Якщо dmesg показує ресети/таймаути/помилки — розглядайте це як інцидент надійності. Сплески латентності часто — повторні спроби і ресети контролера в маскуванні.
- Дія: оновіть прошивку диска / драйвери сховища гіпервізора, де застосовано.
- Дія: замініть несправні пристрої; не намагайтеся налаштуваннями «вилікувати» фізичні проблеми.
Типові помилки: симптом → корінна причина → виправлення
1) p99 сплески додатку, але %util диска низький
Симптом: Користувачі бачать таймаути; iostat показує низький %util, але await сплески.
Корінна причина: пауза бекенду або троттлінг (хмарний том, SAN contention, шлях кеш-флешу) замість тривалого насичення.
Виправлення: зафіксуйте латентність по кожному I/O з BPF/blktrace; перейдіть на кращий клас тому або зменшіть тригери flush/trim; налаштуйте чергування для хвостової латентності.
2) Високий iowait приводить до «звинувачення диска», але await у нормі
Симптом: vmstat показує високий wa, але iostat -x показує низький await.
Корінна причина: система чекає на щось інше (збої NFS сервера, мережеві файлові системи, swap I/O або додаток викликає заблоковані читання через page faults на іншому пристрої).
Виправлення: зіставте mount-и з пристроями; перевірте статистику NFS, якщо застосовано; ідентифікуйте заблоковані процеси та їх wait channel; трасуйте правильний пристрій.
3) Періодичні сплески кожні кілька секунд/хвилин
Симптом: каденс латентності схожий на метроном.
Корінна причина: коміти журналу, чекпоінти, таймери writeback, періодичний trim або обслуговування бекенду сховища.
Виправлення: скорелюйте з логами файлової системи/БД; приберіть inline discard; відрегулюйте поведінку чекпоінтів/комітів; вимірюйте через fio і BPF.
4) Сплески при видаленнях/компактації
Симптом: vacuum/компактація/видалення співпадає з I/O заїданням.
Корінна причина: синхронний discard, тиск метаданих тонкопризначених пулів або GC SSD через хвилі інвалідації.
Виправлення: використовуйте запланований trim; забезпечте достатньо вільного місця/overprovisioning; розгляньте SSD кращого класу або конфігурацію бекенду.
5) «Ми ввімкнули шифрування і стало повільно»
Симптом: підвищена латентність і менша пропускна здатність після активації dm-crypt.
Корінна причина: накладні витрати CPU, менші ефективні розміри запитів, втрата offload-ів пристрою або взаємодії черг у стеку device-mapper.
Виправлення: підтвердіть через perf і завантаження CPU; переконайтеся, що AES-NI доступний; оптимізуйте розміри I/O та iodepth; мінімізуйте стек, якщо можливо.
6) RAID «працює», поки не з’являться дрібні випадкові записи
Симптом: читання в порядку; записи мають жахливу хвостову латентність під навантаженням.
Корінна причина: паритетний RAID-пенальті і цикли read-modify-write; поведінка кешу запису.
Виправлення: використовуйте дзеркала для чутливих до латентності записів; переконайтеся, що кеш запису надійний (BBU/PLP) і flush-и адекватні; збільшіть вирівнювання stripe, коли застосовно.
Контрольні списки / покроковий план
Покроково: від алерту до кореня причини контроловано
- Захопіть часові позначки. Зафіксуйте початок/кінець вікон сплесків. Кореляція вмирає без часу.
- Підтвердіть вплив на хост. Запишіть
vmstat 1і вивід PSI I/O під час сплеску. - Зберіть статистику по-пристрою. Запустіть
iostat -x 1як мінімум 60 секунд, охоплюючи сплеск. - Ідентифікуйте заблоковані процеси. Зніміть знімок D-state задач і їх wait channel.
- Спростіть шлях даних. Mount → filesystem → dm-crypt/LVM/MD → фізичний/віртуальний диск.
- Затрасуйте аутлайери. Використайте
biosnoopабоblktraceдля захоплення кількох I/O з максимальною латентністю. - Перевірте логи на ризики коректності. Проскануйте dmesg на предмет I/O помилок/таймаутів/ресетів.
- Відтворіть безпечно. Запустіть fio на тестовому файлі, щоб підтвердити хвостові сплески поза додатком.
- Сформуйте гіпотезу. «Сплески спричинені X, бо доказ Y показує латентність між D і C і корелюється з Z.»
- Застосуйте одну зміну. Одна. Не п’ять. Заміряйте знову тими ж інструментами.
- Закріпіть моніторинг. Тримайте PSI I/O, iostat await і пробник хвостової латентності як стандартні сигнали.
Оперативний чекліст: що прикріпити до інцидентного тікета
- Вивід iostat охоплюючий сплеск (raw text).
- Снімок PSI I/O і вивід
vmstat. - Список процесів у стані D з wait channel.
- Один артефакт трасування (рядки з biosnoop або витяг blktrace) що показує латентні I/O.
- Витяг з dmesg для будь-яких попереджень/помилок, пов’язаних зі сховищем.
- Топологія сховища: вивід lsblk, що показує стеки (dm-crypt/LVM/MD/multipath).
- Нотатка про навантаження: що робив додаток (checkpoint, vacuum, compaction, batch job).
Поширені запитання
1) Чи достатньо високого iowait, щоб довести, що сховище є вузьким місцем?
Ні. Це підказка. Доведіть це через латентність по-пристрою (iostat -x) і трасування по кожному I/O (BPF або blktrace). iowait може бути низьким під час коротких сплесків.
2) Чому латентність підстрибує, коли %util далеко не 100%?
Бо використання — це усереднення і часто локальне для гостя. Пауза бекенду, троттлінг, flush-и кешу або віддалене змагання можуть створити високий час завершення без локальної насиченості.
3) Який найшвидший спосіб приписати повільний I/O до процесу?
Використовуйте biosnoop (bcc) або подібні eBPF-інструменти. Вони записують латентність I/O і показують, який процес її ініціював. Це швидко закриває дискусії.
4) Чи варто переключити планувальник на «none» для SSD/NVMe?
Інколи так, але вимірюйте. «none» може зменшити накладні витрати, але також допустити несправедливість і гіршу хвостову латентність на спільних бекендах. Тестуйте з fio і навантаженням, схожим на продакшен.
5) Чи справді inline discard такий поганий?
Може бути. На деяких пристроях це дешево; на інших — викликає дорогі операції в найгірший час. Якщо бачите сплески під час видалень — спробуйте запланований trim.
6) Мій тест fio показує жахливий p99, але додаток «пощастило» більшу частину дня. Що далі?
У вас проблема хвостової латентності, яка проявиться при невірній конкуренції або фонового активності. Вирішіть її зараз, перш ніж отримаєте інцидент, що трапляється тільки по вівторках.
7) Чи може вибір файлової системи (ext4 vs xfs) виправити сплески латентності?
Інколи так, але це рідко перший важіль. Більшість сплесків походить від варіацій бекенду, write amplification, чергування або поведінки flush. Зміни файлової системи — руйнівні; спробуйте простіші виправлення першими.
8) Як відрізнити чергування в ОС від повільного завершення пристрою?
Використовуйте blktrace для таймінгу життєвого циклу. Якщо затримка між Q і D — чергування перед диспетчеризацією. Якщо між D і C — повільний пристрій/бекенд.
9) Чому сплески погіршуються, коли ми додаємо повтори?
Повтори додають навантаження саме тоді, коли система найслабша. Вони збільшують конкуренцію, заглиблюють черги і подовжують сплеск. Віддавайте перевагу backoff, jitter і circuit breakers — і виправляйте корінну причину.
Висновок: наступні кроки, що не марнують тиждень
Коли зʼявляються сплески латентності диска, небезпека — не лише в продуктивності. Це помилкова діагностика. Люди переписують код, «оптимізують» не той шлях і вивозять зміни, які роблять інцидент більшим і важчим для розуміння.
Зробіть наступне:
- Інструментуйте: тримайте PSI I/O та метрики типу
iostat -xу стандартних дашбордах. - Під час наступного сплеску захопіть один артефакт трасування (BPF або blktrace), що показує аутлайер I/O і його латентність.
- Приберіть очевидні множники латентності (inline discard, патологічні черги) і протестуйте заново з fio, використовуючи перцентилі.
- Якщо ви на спільному/віртуалізованому сховищі і можете відтворити хвостові сплески — припиніть торгуватися з фізикою: підніміть клас бекенду або перерахуйте розташування даних.
Коли ви зможете вказати на конкретний I/O, що зайняв 1.1 секунди, і назвати процес, який його ініціював, розмова зміниться. Ось ціль. Докази перемагають думки, і вони також роблять вас менш популярним на нарадах — що, чесно кажучи, іноді і є перевагою.