Диск Ubuntu 24.04 повільний: IO scheduler, queue depth і як перевірити покращення

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

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

Ubuntu 24.04 постачається з сучасним ядром Linux з багаточасовою (multi-queue) блоковою підсистемою IO і багатьма розумними налаштуваннями за замовчуванням. Проте один невірний scheduler, одна невідповідна глибина черги або один «корисний» регулятор можуть перетворити швидкий NVMe на розпилювач затримок. Виправлення рідко містить містичний компонент. Зазвичай це вимірювання, декілька обґрунтованих налаштувань і звичка доводити, що зміна дійсно допомогла.

Швидкий план діагностики

Коли хтось каже «диск повільний», не починайте з зміни налаштувань. Почніть з визначення, чи маєте ви проблему латентності, обмеження пропускної здатності, насичення або просто відмову пристрою. Ось порядок дій, що дозволяє знайти більшість проблем з мінімальним драматизмом.

1) Перш за все: визначте, який саме «повільний»

  • Спайк латентності (p99/p999 читання або fsync стрибають): часто спричинений чергуванням, невідповідним scheduler, скиданнями кешу, прошивкою пристрою/GC або бекендним сховищем.
  • Обмеження пропускної здатності (MB/s застрягли на низькому рівні): часто проблема швидкості лінку, ліній PCIe, обмежень RAID/MD, лімітів хмарного тому або неправильного IO-патерну.
  • Насичення (високе завантаження, довгі черги): часто глибина черги занадто мала/велика, забагато паралельних записувачів або шумний сусід.
  • Зависання/таймаути: можуть бути помилками пристрою, мультипатчними перемиканнями, скиданнями контролера або проблемами файлової системи/журналу.

2) Друге: перевірте помилки і скидання до налаштувань

Якщо ядро логують NVMe resets або SCSI timeouts, ваша «проблема з продуктивністю» — це інцидент доступності, що ховається під маскою продуктивності.

3) Третє: виміряйте чергування і латентність на рівні блоку

Використовуйте iostat і pidstat, щоб побачити, чи формуються черги (aqu-sz), чи страждаєте від латентності (await) або просто виконуєте багато IO. Потім зіставте це з налаштуваннями scheduler і queue depth.

4) Четверте: звірте scheduler і глибину черги з типом пристрою

NVMe, SATA SSD, HDD і хмарні блокові томи — різні «тварини». Якщо ставитися до них однаково, вони покарають вас по-різному.

5) П’яте: змінюйте одну річ за раз, запускайте той самий тест, порівнюйте процентилі

Якщо ви не можете відтворити проблему, ви не зможете довести виправлення. Це не цинізм; це спосіб уникнути «налаштування», яке призведе до нового інциденту.

Що насправді означає «повільний диск» у продукції

Обговорення продуктивності сховища сходять з рейок, бо люди плутають метрики. Розробник каже «диск повільний» і має на увазі таймаут API-запиту. Сисадмін чує «диск повільний» і дивиться MB/s. Адміністратор бази даних має на увазі латентність fsync. Всі вони праві і всі неправі, доки ви не заякоритеся на однаковому вимірі.

Чотири метрики, що важливі (і одна, яка бреше)

  • IOPS: операцій за секунду. Добре для випадкових IO і малих блоків. Непридатний, якщо ігнорувати розподіл латентності.
  • Пропускна здатність (MB/s): підходить для потокових читань/записів і великих блоків. Оманлива для баз даних і робочих навантажень з великою кількістю метаданих.
  • Латентність (середнє і процентилі): єдина метрика, яку відчувають користувачі. p99 важливіший за «середнє».
  • Чергування: скільки роботи очікує. Часто саме тут народжується «повільність».
  • CPU iowait: ошукуючий показник. iowait може бути низьким при жахливій латентності (асинхронний IO), і високим коли все гаразд (CPU просто очікує, IO зайнятий).

На Ubuntu 24.04 ви зазвичай маєте багаточасову блочну стеку (blk-mq). Це означає кілька апаратних черг, кілька шляхів подачі з програмної частини і іншу поведінку scheduler у порівнянні зі старими одночерговими днями. Це швидше і більш паралельно. Але також легше створити безлад, якщо глибина черги не відповідає пристрою або бекенду.

Один вислів, який повинен бути в кожному on-call runbook:

«Надія — це не стратегія.» — Gene Kranz

Налаштування сховища без вимірювань — це надія у лабораторному халаті.

Цікаві факти та історія (коротко, конкретно, корисно)

  1. Раніше Linux обирав між CFQ, deadline і noop; з blk-mq багато пристроїв тепер за замовчуванням використовують none (bypass scheduler) або mq-deadline.
  2. NVMe спроектовано для глибоких черг (багато команд у польоті), бо флеш і PCIe процвітають у паралелізмі.
  3. HDD не люблять випадковий IO не тому, що вони «повільні», а тому, що вони механічні: кожен випадковий seek — це маленька фізична поїздка.
  4. Deadline scheduling став популярним для баз даних, бо він запобігає голодуванню і тримає латентність читань у межах під час навантаження записами.
  5. Кеш запису і поведінка flush змінили правила гри: сучасні SSD можуть швидко підтверджувати записи, але примусовий flush (fsync/fua) все ще може коштувати реального часу.
  6. Тюнінг глибини черги став масовим з SAN: занадто неглибока черга марнує масив, занадто глибока — плавить хвостову латентність чергуванням.
  7. Хмарні блокові томи часто накладають ліміти продуктивності (IOPS/MB/s), незалежно від можливостей вашого інстансу, через що «повільний диск» насправді може бути «повільний гаманець».
  8. Multi-queue впровадили, щоб масштабуватися з ядрами CPU; глобальна блокування черги була вузьким місцем на швидких SSD.
  9. «noop» не означало «без планування», скоріше — «мінімальне злиття»; це мало сенс для апаратури, яка вже ефективно перепорядковує запити.

IO scheduler в Ubuntu 24.04: що він робить і коли має значення

IO scheduler — це регулювальник трафіку між файловою системою і блоковим пристроєм. Його задача вирішувати, що відправляти наступним і в якому порядку, а також як агресивно зливати і відправляти запити.

На сучасному Linux, особливо з NVMe, пристрій і прошивка вже багато роблять для перепорядкування. Ось чому «без scheduler» (none) може бути найкращим вибором: ядро не заважає. Але «може бути» не означає «завжди є».

Scheduler’и, які ви часто побачите

  • none: фактично обходить scheduling для blk-mq пристроїв; покладається на апарат для керування порядком. Часто найкраще для NVMe і висококласних масивів.
  • mq-deadline: мультичергова версія deadline; прагне обмежити латентність і запобігти голодуванню. Часто хороший вибір за замовчуванням для SATA SSD і змішаних навантажень.
  • kyber: націлений на низьку латентність, контролюючи відправлення залежно від цільових латентностей. Може допомогти на певних пристроях; також може заплутати, якщо ви не вимірюєте правильно.
  • bfq: орієнтований на справедливість, часто для десктопів; може бути корисним для інтерактивної латентності на обертових дисках, але це не мій перший вибір для серверів, хіба що є причина.

Коли scheduler має значення

Якщо ваше навантаження переважно послідовні IO (великі потокові читання/записи), вибір scheduler часто мало що змінить. Якщо навантаження змішане — випадкові читання/записи під конкуренцією (бази даних, VM-хости, Ceph OSD), поведінка scheduler і чергування може домінувати у хвостовій латентності.

Якщо ви на апаратному RAID-контролері або SAN, що вже виконує складне планування, додавання важкого шару scheduler може бути як доручити двом менеджерам координувати один і той самий спринт. Отримаєте більше зустрічей, а не більше функціоналу.

Глибина черги: прихований важіль за пропускною здатністю і хвостовою затримкою

Глибина черги — це скільки IO-запитів ви дозволяєте бути одночасно в польоті. Більша глибина підвищує паралелізм і може покращити пропускну здатність та IOPS. Занадто велика глибина збільшує затримки чергування й погіршує латентність, особливо на хвостах.

Три черги, які варто перестати плутати

  • Паралелізм додатка: скільки потоків, асинхронних задач або процесів виконує IO.
  • Глибина черги на рівні ядра: здатність блочного шару тримати і відправляти запити (див. nr_requests та ліміти на пристрій).
  • Глибина черги пристрою або бекенда: розмір черги NVMe, глибина черги HBA, ліміти LUN у SAN, обмеження хмарного тому.

«Правильна» глибина черги залежить від навантаження і пристрою. Бази даних часто хочуть обмеженої латентності більше, ніж пікової пропускної здатності. Резервні копії прагнуть пропускної здатності і можуть терпіти затримки. VM-хости хочуть обидва — тому вони викликають суперечки.

Жарт №1: Тюнінг глибини черги схожий на еспресо — замало й нічого не відбувається, занадто багато і ніхто не спить, включно з вашим масивом зберігання.

Що відбувається, коли глибина черги неправильна

  • Занадто мала: ви побачите низьку завантаженість і посередню пропускну здатність; пристрій міг би робити більше, але не отримує достатньо паралельної роботи.
  • Занадто велика: ви побачите високу завантаженість, велике aqu-sz, зростаючий await і некрасиву p99 латентність. Ви не «зайняті»; ви заторені.

Хмарні томи і SAN: глибина черги — це політика, не фізика

У випадку томів на кшталт EBS ви можете мати локально швидкий NVMe instance store і все одно бути обмеженими політикою мережевого блочного пристрою. Саме тому зміна scheduler на гостевій ОС іноді допомагає менше, ніж зміна класу тому, provisioned IOPS або типу інстансу.

Верифікація: як бенчмаркати чесно

Якщо ви хочете підтвердити покращення, вам потрібен тест, що відповідає вашому продакшн IO-патерну, і метод, який контролює кешування, прогрів і паралелізм.

Правила бенчмаркингу сховища, що збережуть вам роботу

  • Виберіть IO-патерн: випадковий чи послідовний, читання чи запис, розмір блоку, синхронний чи асинхронний, частота fsync.
  • Використовуйте процентилі: середнє — це казка на ніч. p95/p99 — це ваш інцидент.
  • Контролюйте кеш: page cache може робити читання магічними. Direct IO може зробити файлові системи гіршими за реальністю. Вибирайте свідомо.
  • Розділяйте тести пристрою і файлової системи: тестуйте сирий блочний пристрій, коли підозрюєте scheduler/queue depth; тестуйте файлову систему, коли підозрюєте журналювання або опції mount.
  • Запускайте досить довго: SSD GC і теплове тротлінг можуть проявитися через хвилини, а не секунди.
  • Одна зміна за раз: це не кулінарне шоу.

Ваш найкращий друг тут — fio. Не тому, що він модний, а тому, що він явний: ви можете описати робоче навантаження і отримати детальні статистики латентності. Поєднуйте його з iostat і логами ядра — і ви зможете сказати, чи дійсно ваше «покращення» реальне.

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

Це реальні завдання, які можна виконати на Ubuntu 24.04. Кожне включає на що дивитися і яке рішення прийняти. Запускайте їх від root, де потрібно. Якщо ви на продакшні, спочатку робіть низькоімпактні перевірки.

Завдання 1: визначити фактичні блочні пристрої і топологію

cr0x@server:~$ lsblk -e7 -o NAME,TYPE,SIZE,ROTA,TRAN,MODEL,SERIAL,MOUNTPOINTS
NAME        TYPE   SIZE ROTA TRAN MODEL             SERIAL            MOUNTPOINTS
nvme0n1     disk  1.8T    0 nvme Samsung SSD 980PRO S64DNE0R123456A
├─nvme0n1p1 part   512M   0 nvme                                  /boot/efi
└─nvme0n1p2 part   1.8T   0 nvme                                  /

Що це означає: ROTA=0 натякає на SSD/NVMe; TRAN каже, чи це nvme, sata, sas тощо. Якщо ви на multipath або MD RAID, побачите інші шари.

Рішення: Оберіть правильний пристрій для інспекції (nvme0n1 тут). Не налаштовуйте розділ і не забувайте про підлягаючий диск.

Завдання 2: перевірити логи ядра на помилки, скидання, таймаути

cr0x@server:~$ sudo dmesg -T | egrep -i "nvme|scsi|blk_update_request|reset|timeout|I/O error" | tail -n 30
[Mon Dec 29 10:11:14 2025] nvme nvme0: I/O 123 QID 4 timeout, aborting
[Mon Dec 29 10:11:14 2025] nvme nvme0: Abort status: 0x371
[Mon Dec 29 10:11:15 2025] nvme nvme0: controller reset scheduled

Що це означає: Таймаути і скидання — не «можливості для тюнінгу». Це інциденти надійності. Продуктивність стає хаотичною, коли пристрій нестабільний.

Рішення: Якщо бачите це — призупиніть тюнінг. Перевірте прошивку, кабелі/бекплейн, PCIe AER помилки, стан контролера і зверніться до постачальника. Якщо ви в хмарі — відкрийте звернення в підтримку і розгляньте переміщення тому.

Завдання 3: перевірити поточний scheduler для пристрою

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

Що це означає: Scheduler в дужках — активний (none тут). Доступні варіанти наведені після.

Рішення: Для NVMe none часто коректний. Якщо ви бачите хвостову латентність під змішаним навантаженням, протестуйте mq-deadline. Не припускайте.

Завдання 4: перевірити базову глибину черги і ліміти запитів

cr0x@server:~$ for f in nr_requests read_ahead_kb rotational rq_affinity nomerges; do echo -n "$f="; cat /sys/block/nvme0n1/queue/$f; done
nr_requests=256
read_ahead_kb=128
rotational=0
rq_affinity=1
nomerges=0

Що це означає: nr_requests обмежує кількість запитів у блочному шарі. Це не єдина черга, але важлива для уникнення заторів. read_ahead_kb впливає на поведінку послідовного читання.

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

Завдання 5: перевірити кількість апаратних черг і їх розміри (NVMe)

cr0x@server:~$ sudo nvme id-ctrl /dev/nvme0 | egrep -i "mdts|sqes|cqes|oacs|oncs|nn|cntlid"
nn    : 0x1
mdts  : 0x9
cntlid: 0x0001
oacs  : 0x0017
oncs  : 0x001f
sqes  : 0x66
cqes  : 0x44

Що це означає: Можливості NVMe визначають, які розміри IO підтримуються і які функції доступні. Це не пряме число «глибини черги», але каже, чи пристрій поводиться як сучасний NVMe.

Рішення: Якщо nvme інструменти повідомляють дивні речі, підтвердіть, що ви не за контролером, який подає NVMe в дивному режимі. Для хмари переконайтеся, що ви дійсно на NVMe, а не на virtio-blk з іншим поводженням.

Завдання 6: спостерігати живу латентність і чергування за допомогою iostat

cr0x@server:~$ iostat -x -d 1 10 nvme0n1
Linux 6.8.0-xx-generic (server) 	12/29/2025 	_x86_64_	(32 CPU)

Device            r/s     rkB/s   rrqm/s  %rrqm r_await rareq-sz     w/s     wkB/s   wrqm/s  %wrqm w_await wareq-sz  aqu-sz  %util
nvme0n1         120.0   3840.0     0.0    0.0    1.20    32.0     95.0   3040.0     0.0    0.0    8.50    32.0    1.05  32.0

Що це означає: r_await/w_await — середні латентності читань/записів (ms). aqu-sz — середній розмір черги. %util — час зайнятості (не завжди надійний на швидких пристроях, але дає підказку).

Рішення: Високий await з високим aqu-sz натякає на чергування: ви накопичуєте запити. Високий await з низьким чергуванням означає, що пристрій/бекенд просто повільний на одиницю IO (або зайнятий flush).

Завдання 7: зіставити «хто робить IO» зі списком процесів

cr0x@server:~$ sudo pidstat -d 1 5
Linux 6.8.0-xx-generic (server) 	12/29/2025 	_x86_64_	(32 CPU)

10:21:01 AM   UID       PID   kB_rd/s   kB_wr/s kB_ccwr/s iodelay  Command
10:21:02 AM  1001     21455      0.00  51200.00      0.00       8  postgres
10:21:02 AM     0      1892      0.00   2048.00      0.00       1  systemd-journald

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

Рішення: Якщо «повільний диск» співпадає з одним процесом, що домінує у записах — виправте навантаження спочатку (лімітуйте, заплануйте, перемістіть), перш ніж чіпати scheduler.

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

cr0x@server:~$ findmnt -no SOURCE,FSTYPE,OPTIONS /
/dev/nvme0n1p2 ext4 rw,relatime,errors=remount-ro

Що це означає: Вибір файлової системи і опцій впливають на метадані IO, поведінку writeback і барʼєри.

Рішення: Не робіть cargo-cult налаштувань mount. Якщо хтось вимкнув барʼєри або встановив data=writeback «для продуктивності», розглядайте це як інцидент цілісності даних, що чекає на зникнення електроживлення.

Завдання 9: перевірити udev правила і tuned профілі, які можуть перезаписувати налаштування

cr0x@server:~$ systemctl is-enabled tuned 2>/dev/null || true
disabled
cr0x@server:~$ grep -R "queue/scheduler" -n /etc/udev/rules.d /lib/udev/rules.d 2>/dev/null | head
/lib/udev/rules.d/60-persistent-storage.rules:...

Що це означає: Ви можете «встановити scheduler» вручну, а udev або профіль змінить це при завантаженні.

Рішення: Якщо налаштування постійно змінюються — припиніть ганятися за привидами. Зробіть конфігурацію персистентною через udev-правила або kernel cmdline там, де потрібно, і документуйте це.

Завдання 10: тимчасово змінити scheduler (для контрольованого тесту)

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

Що це означає: Scheduler змінено на льоту. Це оборотно і має бути протестовано під тим самим навантаженням.

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

Завдання 11: протестувати сиру латентність і пропускну здатність пристрою за допомогою fio (direct IO)

cr0x@server:~$ sudo fio --name=randread --filename=/dev/nvme0n1 --direct=1 --ioengine=io_uring --iodepth=32 --rw=randread --bs=4k --numjobs=1 --time_based=1 --runtime=60 --group_reporting
randread: (g=0): rw=randread, bs=(R) 4096B-4096B, (W) 4096B-4096B, ioengine=io_uring, iodepth=32
fio-3.36
Starting 1 process
randread: IOPS=210k, BW=820MiB/s (860MB/s)(49.1GiB/60s)
  lat (usec): min=42, max=8500, avg=145.2, stdev=60.1
  clat percentiles (usec):
   |  1.00th=[   70],  5.00th=[   85], 10.00th=[   95], 50.00th=[  140],
   | 90.00th=[  210], 95.00th=[  260], 99.00th=[  420], 99.90th=[ 1100]

Що це означає: Це обходить page cache і влучає в пристрій. Ви отримуєте процентилі, що реально відображають хвостову латентність.

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

Завдання 12: протестувати продуктивність файлової системи (включає метадані і журналювання)

cr0x@server:~$ mkdir -p /mnt/fio-test
cr0x@server:~$ sudo fio --name=fsyncwrite --directory=/mnt/fio-test --ioengine=io_uring --direct=0 --rw=write --bs=16k --numjobs=4 --iodepth=8 --fsync=1 --size=2G --group_reporting
fsyncwrite: (g=0): rw=write, bs=(R) 16384B-16384B, (W) 16384B-16384B, ioengine=io_uring, iodepth=8
fio-3.36
fsyncwrite: IOPS=9800, BW=153MiB/s (160MB/s)(8192MiB/53s)
  clat percentiles (msec):
   |  1.00th=[  0.6],  5.00th=[  0.9], 10.00th=[  1.0], 50.00th=[  2.1],
   | 90.00th=[  6.2], 95.00th=[  9.5], 99.00th=[ 21.0], 99.90th=[ 45.0]

Що це означає: Це ближче до «поведінки бази даних»: безліч точок синхронізації. Хвостова латентність тут вбиває транзакції.

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

Завдання 13: перевірити multipath/DM пристрої (якщо застосовно)

cr0x@server:~$ lsblk -o NAME,TYPE,SIZE,PKNAME,MOUNTPOINTS | head
NAME          TYPE  SIZE PKNAME MOUNTPOINTS
dm-0          lvm   900G        /
└─mpatha      mpath 900G sdb
  ├─sdb       disk  900G
  └─sdc       disk  900G

Що це означає: Device-mapper шари можуть ховати обмеження глибини черги. Тюнінг неправильного шару нічого не дасть.

Рішення: Визначте leaf-пристрої і підтвердіть їхні ліміти черги і scheduler. Для SAN перевірте глибину черги HBA і налаштування multipath. Якщо ви помічаєте перемикання шляху в графіках латентності — у вас є робота.

Завдання 14: спостерігати час IO і злиття на пристрої з sar

cr0x@server:~$ sar -d 1 5
Linux 6.8.0-xx-generic (server) 	12/29/2025 	_x86_64_	(32 CPU)

10:24:01 AM       DEV       tps     rkB/s     wkB/s   areq-sz    aqu-sz     await     %util
10:24:02 AM     nvme0n1   220.00   4096.00   6144.00     46.55      2.10      9.55     40.00

Що це означає: Інший погляд на чергування і завантаженість, корисний для історичного захоплення.

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

Завдання 15: перевірити поведінку discard/TRIM (SSD) і уникати міфів «завжди включено»

cr0x@server:~$ lsblk -D -o NAME,DISC-GRAN,DISC-MAX,DISC-ZERO
NAME      DISC-GRAN DISC-MAX DISC-ZERO
nvme0n1        512B       2G         0

Що це означає: Пристрій підтримує discard. Чи монтуєте з discard або запускаєте періодичний fstrim — має значення.

Рішення: Віддавайте перевагу періодичному fstrim (через systemd timer) замість постійного discard для більшості серверних навантажень. Безперервний discard може додати накладні витрати і непередбачуваність.

Завдання 16: підтвердити, що ваше «покращення» стабільне (повторюваність)

cr0x@server:~$ sudo fio --name=randrw --filename=/dev/nvme0n1 --direct=1 --ioengine=io_uring --iodepth=16 --rw=randrw --rwmixread=70 --bs=4k --numjobs=2 --time_based=1 --runtime=180 --group_reporting
randrw: (g=0): rw=randrw, bs=(R) 4096B-4096B, (W) 4096B-4096B, ioengine=io_uring, iodepth=16
fio-3.36
randrw: IOPS=120k, BW=469MiB/s (492MB/s)(84.1GiB/180s)
  clat percentiles (usec):
   | 95.00th=[  600], 99.00th=[ 1200], 99.90th=[ 4200]

Що це означає: Довший змішаний прогін ловить GC, тротлінг і ефекти чергування.

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

Три міні-історії з корпоративного життя (анонімізовані, правдоподібні, технічно коректні)

1) Інцидент, спричинений хибним припущенням: «NVMe завжди швидкий»

Команда мігрувала latency-чутливий сервіс на нові сервери з NVMe дисками. У тікеті було «ми оновили сховище, диск не може бути проблемою». Одночасно відбувся новий деплой, тож наратив був майже написаний: проблема в коді додатка.

Першою підказкою було те, що середня латентність виглядала нормально, але сервіс періодично стикався з таймаутами. На хості iostat показував сплески, коли aqu-sz різко зростав і w_await підіймався до подвійних значень. Це не було постійно; приходило хвилями. Пристрій був «швидким», поки раптом переставав ним бути.

Припущення «NVMe = низька латентність» приховало справжню історію: навантаження було записо-важким з частими sync-ами, а модель диска мала невеликий SLC-кеш. Під стійкими записами диск переходив у повільніший steady state і починав GC. Логи ядра були чистими. Ніяких помилок. Просто диск поводився як споживчий, коли його використовують як журнал бази даних.

Вони протестували інший scheduler (mq-deadline) і трохи знизили конкуренцію. Хвостова латентність покращилася, але справжнє виправлення було нудним: перехід на SSD класу endurance і винесення WAL/journal на пристрій з передбачуваною стійкою швидкістю запису.

Урок не був «купуйте дорогі диски». Він був у тому, щоб не трактувати торгові назви як гарантію продуктивності. NVMe — це протокол. Ваше навантаження все одно має відповідати фізиці і прошивці.

2) Оптимізація, що обернулася проти: «Давайте піднімемо queue depth до максимуму»

Кластер віртуалізації страждав від низької пропускної здатності на спільному SAN LUN. Хтось прочитав, що «глибокі черги збільшують IOPS», що вірно так само, як «більше машин збільшує потік трафіку»: інколи, до певної межі.

Вони підняли параметри черги на декількох шарах: глибина HBA, налаштування multipath і nr_requests в блочному шарі. Графіки кластера виглядали чудово один день. Потім helpdesk отримав скарги «VM зависла». Не просто повільна. Зависла.

Розслідування показало, що середня латентність залишалася прийнятною, але p99 пішла в прірву під навантаженням. SAN поглинав збільшений паралелізм шляхом внутрішнього чергування. Коли приходив сплеск (резервне копіювання плюс патчинг плюс хтось запускав звіт), внутрішні черги зростали. Це чергування приводило до багатосекундних очікувань IO для невдалих VM. Глибокі черги зробили SAN виглядати завантаженим, а не чуйним.

Вони відкотили налаштування. Менша глибина черги знизила пікову пропускну здатність, але покращила хвостову латентність і усунула «зависання VM». Правильний підхід — встановити глибину черги такою, щоб масив міг її обробляти з обмеженою латентністю, і впровадити ізоляцію навантажень (окремі LUN/ QoS/ планування резервних копій).

Жарт №2: Оптимізація queue depth — єдиний тюнінг, який може одночасно слугувати імпровізованим генератором аварій.

3) Нудна, але правильна практика, що врятувала ситуацію: базові бенчмарки і дисципліна змін

Сервіс з інтенсивним використанням сховища мав ритуал: кожного циклу оновлення ядра вони запускали ту саму невелику суїту fio на стендовому хості. Це було нудно. Ніхто за це не просувався. Але це створило базову лінію з часом: типові IOPS, очікувана p99 латентність і як це змінюється залежно від вибору scheduler.

Одного тижня звичайне оновлення Ubuntu прилетіло. Команда додатка повідомила «диск повільний», але графіки були неоднозначні: CPU не був високим, мережа не була завантажена, і дашборд сховища виглядав нормально. Зазвичай це саме та сцена, де люди сперечаються і інцидент затягується.

Оскільки у них були базові виміри, вони відразу перезапустили той самий профіль fio і побачили, що p99 латентність приблизно подвоїлася для 4k випадкових записів при тій же конкуренції. Це звузило пошук. Вони виявили, що оновлення змінило udev-правило, що застосовувалося до певних класів пристроїв, переключивши scheduler з їхнього обраного на дефолтний.

Вони виправили це персистентним udev-правилом і перевірили виправлення тим самим бенчмарком. Ніяких подвигів, ніяких припущень, ніякого «так тепер відчувається швидше». Postmortem був короткий і нудний — саме те, що потрібно.

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

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

1) Симптом: високий iowait, але диск виглядає простою

Корінь проблеми: IO не йде на локальний диск, який ви дивитесь (мережева файловa система, інший LUN, device-mapper шар), або IO блокується на flush, поки пристрій повідомляє низьке завантаження.

Виправлення: Використовуйте pidstat -d, щоб знайти процес-винуватця, і findmnt, щоб ідентифікувати бекенд-пристрій. Якщо це NFS/Ceph/RBD, локальні зміни scheduler не допоможуть; фокусуйтеся на мережевому/бекендному latency.

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

Корінь проблеми: Глибина черги занадто велика, або конкуренція навантаження занадто висока, що призводить до затримок чергування. Часто трапляється на спільних масивах і хмарних томах.

Виправлення: Зменшіть конкуренцію (потоки додатка, iodepth, кількість job-ів) і/або оберіть scheduler, що обмежує латентність (часто mq-deadline). Виміряйте процентилі до/після.

3) Симптом: випадкові читання IOPS значно нижчі за очікуване на SSD/NVMe

Корінь проблеми: Мілка глибина черги, неправильний IO engine, або тест випадково використовує буферизований IO і домінує page cache.

Виправлення: Використовуйте fio з --direct=1, задайте розумний iodepth (наприклад, 16–64) і підтвердіть scheduler. Також перевірте PCIe link і NUMA-локальність, якщо результати дивно низькі.

4) Симптом: записи стають повільними через кілька хвилин тестування

Корінь проблеми: Вичерпання SLC-кешу SSD, garbage collection, теплове тротлінг або тиск журналу файлової системи.

Виправлення: Подовжіть бенчмарки до 3–10 хвилин і порівняйте ранню і пізню поведінку. Перевірте SMART/NVMe health і температуру. Якщо важлива стійка швидкість запису — використовуйте enterprise SSD і уникайте споживчих дисків для журнал-важких навантажень.

5) Симптом: зміна scheduler, здається, нічого не дає

Корінь проблеми: Ви змінюєте scheduler на неправильному шарі (розділ замість диска, dm-crypt замість базового диска), або пристрій контролюється апаратним RAID/SAN, що диктує порядок.

Виправлення: Використовуйте lsblk, щоб знайти leaf-пристрій і підтвердити scheduler там. У випадках RAID/SAN фокусуйтеся на політиках контролера, вирівнюванні stripe size, здоровʼї масиву і глибині черги HBA.

6) Симптом: час від часу багатосекундні затримки при нормальній поведінці інакше

Корінь проблеми: Скидання/таймаути пристрою, флапи multipath, або проблеми з прошивкою. Іноді також commit-и ext4 під патологічною конкуренцією.

Виправлення: Перевірте dmesg на повідомлення про скидання/таймаути. Стабілізуйте апарат/бекенд спочатку. Потім налаштовуйте.

7) Симптом: послідовні читання повільні на HDD масивах

Корінь проблеми: Надто малий read-ahead, фрагментація або конкуренція випадкового IO. Також можливо: політика кешу контролера або відновлення/деградація RAID.

Виправлення: Підтвердіть стан масиву, перевірте read-ahead і ізолюйте послідовні навантаження від випадкових записувачів. Не «виправляйте» це глибшими чергами бездумно.

Чек-листи / покроковий план

Чек-лист A: реагування на інцидент «диск повільний» (15–30 хвилин)

  1. Підтвердіть шлях пристрою: використовуйте findmnt і lsblk, щоб впевнитися, що ви налаштовуєте правильну річ.
  2. Перевірте помилки: проскануйте dmesg на таймаути, скидання або помилки IO.
  3. Виміряйте живу латентність/чергування: iostat -x 1 на відповідному пристрої під час проблеми.
  4. Визначте процеси, що роблять IO: pidstat -d 1. Рішення може бути в контролі навантаження.
  5. Зʼясуйте, чи це біль з читанням чи записом: порівняйте r_await і w_await, і врахуйте fsync-важкі навантаження.
  6. Зробіть один базовий бенчмарк (якщо безпечно): короткий fio прогін, що відповідає підозрюваному патерну.
  7. Тільки потім тестуйте зміни scheduler/черги: один параметр за раз, вимірюйте, фіксуйте результати.

Чек-лист B: контрольований план тюнінгу (зручний для change management)

  1. Оберіть представницьке навантаження: визначте розмір блоку, суміш читань/записів, конкуренцію і тривалість.
  2. Зніміть базову лінію: збережіть вивід fio, знімки iostat і версію ядра.
  3. Визначте критерії успіху: наприклад, p99 write latency нижче X ms при Y IOPS; не лише «виглядає швидше».
  4. Протестуйте кандидати scheduler: none проти mq-deadline (і kyber, якщо розумієте, для чого).
  5. Протестуйте діапазон глибини черги: варіюйте fio --iodepth і кількість job-ів; не стрибайте одразу в крайнощі.
  6. Валідуйте під конкуренцією: запустіть друге навантаження одночасно, якщо в продакшні зазвичай є змішані IO.
  7. Зробіть зміни персистентними: після підтвердження реалізуйте через udev правило або відповідну конфігурацію і документуйте.
  8. Моніторте після релізу: спостерігайте p95/p99, а не лише середні значення, принаймні один бізнес-цикл.

Як зробити зміни scheduler персистентними (без сюрпризів)

Тимчасові echo зміни пропадають після перезавантаження. Для персистентності часто використовують udev-правила. Точні критерії відповідності залежать від неймінгу вашого заліза. Тестуйте на стенді перед продакшном.

cr0x@server:~$ sudo tee /etc/udev/rules.d/60-ioscheduler.rules >/dev/null <<'EOF'
ACTION=="add|change", KERNEL=="nvme0n1", ATTR{queue/scheduler}="mq-deadline"
EOF
cr0x@server:~$ sudo udevadm control --reload-rules
cr0x@server:~$ sudo udevadm trigger --name-match=nvme0n1

Рішення: Якщо ви не можете впевнено співпасти пристрої (бо імена змінюються), співпадайте по атрибутах, як-от ID_MODEL або WWN. Мета — стабільна конфігурація, а не рулетка при завантаженні.

Поширені питання

1) Чи використовувати none або mq-deadline на NVMe?

Почніть з none для чистих NVMe локальних дисків, потім протестуйте mq-deadline, якщо вас хвилює хвостова латентність під змішаним навантаженням. Оберіть те, що покращує p99 для вашого реального навантаження.

2) Яку глибину черги мені встановити?

Одного числа не існує. Для NVMe глибші черги можуть покращити пропускну здатність. Для спільних масивів/хмарних томів занадто велика глибина часто підвищує p99 латентність. Налаштовуйте по мірі вимірювань: запускайте fio з iodepth 1, 4, 16, 32, 64 і малюйте графік процентилів латентності.

3) Чому %util показує 100%, але пропускна здатність низька?

На швидких пристроях або коли чергування велике, %util може відображати «busy waiting» і затримки в чергах, а не продуктивну пропускну здатність. Дивіться await і aqu-sz для інтерпретації.

4) Чи допоможе зміна scheduler на апаратному RAID?

Іноді трохи, часто незначно. Апаратні RAID-контролери і SAN роблять власне планування і кешування. У цих випадках фокусуйтеся на політиці кешу контролера, вирівнюванні stripe size, стані масиву і глибині черги HBA.

5) Мій додаток повільний, але сирий fio швидкий. Що далі?

Вузьке місце вище пристрою: журналювання файлової системи, поведінка fsync, малі синхронні записи, конкуренція метаданих або IO-патерн додатка. Запустіть файлові тести fio і перевірте опції mount і поведінку writeback.

6) Чи підходить bfq на серверах?

Рідко, але не ніколи. Якщо потрібна справедливість між конкурентними джерелами IO (мульти-tenant системи) і ви можете дозволити собі накладні витрати, він може допомогти. Для більшості серверних навантажень починайте з none або mq-deadline і відхиляйтеся тільки з доказами.

7) Чи монтувати ext4 з discard для продуктивності SSD?

Зазвичай ні. Віддавайте перевагу періодичному trim (fstrim.timer) для передбачуваних накладних витрат. Постійний discard може додати варіанс латентності.

8) Чи можна «підправити» повільний диск, збільшивши read-ahead?

Тільки якщо навантаження дійсно послідовне читання і вузьке місце — readahead thrash. Для випадкових навантажень (бази даних) підвищення read-ahead часто марнує кеш і погіршує ситуацію. Вимірюйте page cache hit rate і патерн навантаження перед змінами.

9) Чому продуктивність змінюється після перезавантаження?

Ви втратили непостійні налаштування, імʼя пристрою змінилося або udev/tuned застосував інші політики. Зробіть зміни персистентними і перевірте після перезавантаження тим самим бенчмарком.

10) Чи завжди io_uring найкращий io-движок для fio?

Це хороший дефолт на сучасних ядрах, але не універсальний. Для деяких середовищ або драйверів libaio більше відповідає legacy додаткам. Використовуйте engine, що відповідає вашому продакшн IO-стеку.

Висновок: наступні кроки, що переживуть change control

Ubuntu 24.04 не саботує ваші диски таємно. Більшість проблем «повільного диска» зводяться до трьох речей: ви вимірюєте неправильний шар, ви чергуєте занадто багато (або замало), або пристрій/бекенд нездоровий і тюнінг — відволікання.

Зробіть це далі, у порядку:

  1. Збирайте докази: уривки iostat -x, pidstat -d і dmesg під час спаду продуктивності.
  2. Задайте базу з fio: один тест на сирий пристрій і один тест файлової системи, що нагадує продакшн.
  3. Протестуйте вибір scheduler: none проти mq-deadline — прагматичний старт. Перевіряйте процентилі.
  4. Тюньте конкуренцію/глибину черги цілеспрямовано: спочатку регулюйте паралелізм навантаження; чіпайте ядрові параметри лише з поясненням, навіщо.
  5. Зробіть персистентним і задокументуйте: якщо виправлення не відтворюється після перезавантаження — це не виправлення, це демо.

Мета не перемогти в бенчмарку. Мета — зробити систему передбачуваною у найгірший день, який ви обовʼязково матимете.

← Попередня
Підробки та відновлені: як уникнути примарного GPU
Наступна →
Клони ZFS: миттєві копії з прихованими залежностями (важливо знати)

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