Налаштування ZFS для NVMe-пулів: затримка, IRQ та реальні обмеження

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

Якщо ви перенесли ZFS на «швидкі NVMe» і очікували, що затримки зникнуть, то, ймовірно, зіткнулися з двома лиходіями сучасного зберігання даних: хвостовою затримкою та невидимою конкуренцією. Ваші бенчмарки виглядають героїчно, але в продакшені система все ще зависає на 200–800 мс у найневдалий момент. Користувачам байдуже, що медіана 80 µs, коли їхня сторінка оформлення замовлення потрапляє у 99.9-й процентиль.

NVMe-пули прибирають одну вузьку місцину й оголюють решту. Тепер обмеження — це планування CPU, розподіл переривань, черги, поведінка транзакційних груп ZFS та різниця між «швидким пристроєм» і «швидкою системою». Налаштуймо під реальність: стабільні затримки під змішаним навантаженням, а не лише красиві графіки fio.

Реальні обмеження: NVMe робить решту очевидною

На механічних дисках або навіть SATA SSD латентність зберігання домінувала й прикривала багато гріхів. На NVMe зберігання вже не є «повільною частиною» у більшості випадків. Це й хороша, й погана новина.

Хороша новина: ви можете отримати неймовірні IOPS і велику пропускну здатність з мінімальним тюнінгом, особливо для читань і асинхронних записів. Погана новина: вузьке горлечко зміщується. Якщо ви бачите стрибки затримки на NVMe‑пулі ZFS, ваші перші підозрювані:

  • Планування CPU і обробка переривань: один ядро виконує всю роботу, тоді як інші простоюють.
  • Черги в блоковому шарі або драйвері NVMe: глибокі черги приховують затори, поки раптом не перестають.
  • Ритм транзакційних груп ZFS (TXG): сплески записування назад кожні кілька секунд, плюс семантика sync.
  • Тиск пам’яті та поведінка ARC: хвилі звільнення пам’яті, kswapd і заблоковані потоки.
  • Припущення «швидкого шляху»: наприклад, навантаження з великою кількістю метаданих, дрібні випадкові записи та додатки з великою кількістю синхронних записів.

Ви налаштовуєте розподілену систему на одній машині: потоки додатків, потоки ядра, переривання, стадії конвеєра ZFS та контролер із власною прошивкою. NVMe не знижує складність; воно просто перестає її маскувати.

Цікаві факти та історія (що справді має значення)

  • ZFS не народився в еру NVMe. Початковий дизайн передбачав обертові диски та квапливе злиття записів протягом секунд; поведінка TXG досі відображає це.
  • NVMe спроектовано для паралелізму. Існують множинні черги подачі/завершення саме для зменшення блокувань і штормів переривань у порівнянні з AHCI/SATA.
  • Коалесценція переривань існувала ще до NVMe. Мережеві карти давно жертвували латентністю заради пропускної здатності, пакетуючи переривання; NVMe‑пристрої і драйвери роблять подібне.
  • 4K сектори не завжди були нормою. Advanced Format і реалії стирання SSD змусили говорити про вирівнювання; ashift у ZFS — це, фактично, «я не довіряю пристрою».
  • TRIM/Discard колись лякав. Ранні SSD могли сильно застопоритися на discard; раніше «не вмикайте TRIM» була розумною порадою. Сучасний NVMe зазвичай поводиться краще, але не завжди акуратно під навантаженням.
  • Контрольні суми — це не податок, який можна ігнорувати. На швидкому носії CRC і стиснення можуть стати значними витратами CPU, особливо з маленькими IO і високою конкуренцією.
  • ZIL — не кеш записів. Журнал намірів існує, щоб забезпечити POSIX‑семантику sync; нерозуміння цього призводить до дивних налаштувань і розчарованих фінансових відділів.
  • Числа IOPS стали зброєю маркетингу. «Мільйони IOPS» змусили забути про хвостову латентність і змішані навантаження; операційні команди мали це виправляти.

Ментальна модель: звідки береться затримка в ZFS на NVMe

Коли затримка погіршується, вам потрібна карта. Ось ця карта.

ZFS write pipeline, simplified

  1. Application write() потрапляє в page cache / логіку ARC і ZFS DMU.
  2. Якщо потрібен sync, ZFS має зафіксувати намір у ZIL (в пулі або SLOG) перед підтвердженням.
  3. Фаза відкриття TXG: записи накопичуються в пам’яті.
  4. Фаза синхронізації TXG: дані записуються в основний пул, metaslabs виділяють простір, обчислюються контрольні суми, застосовується стиснення тощо.
  5. Flush/FUA/barriers: підлеглий NVMe має дати гарантії надійності для шляхів sync.

Read pipeline, simplified

  1. ARC hit: дешево, переважно поведінка CPU/кешу.
  2. ARC miss: ZFS ініціює IO; пристрій повертає дані; перевіряються контрольні суми; опційно декомпресія.

NVMe path, simplified

  1. Блоковий IO ставиться в чергу в ядрі.
  2. Драйвер NVMe подає в чергу(и) пристрою.
  3. Пристрій завершує; переривання (або polling) повідомляє CPU; обробляється завершення.
  4. Пробудження і контекстні переключення доставляють дані потокам, що чекають.

Стрибки затримки виникають, коли будь‑яка стадія стає послідовною. На NVMe послідовність зазвичай на стороні CPU: одна черга, одне ядро, одна блокування, одна хибна налаштування. Або це власний ритм ZFS: синхронізації TXG, конкуренція metaslab або flush‑и синхронних записів.

Одна цитата, щоб залишатись чесним. Сподівання — не стратегія. — General Gordon R. Sullivan

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

Це «відкрити ноутбук о 2:00 ночі» план. Ви намагаєтесь відповісти на одне питання: куди йде час?

Перший: підтвердьте, що симптом — це латентність зберігання (не додаток чи мережа)

  • Перевірте, чи потоки додатка блокуються на IO (стан D) або на CPU.
  • Перевірте наскрізну латентність у момент інциденту (p99/p999) і зв’яжіть з IO wait та навантаженням переривань.

Другий: вирішіть, чи це шлях читання, асинхронних записів чи синхронних записів

  • Навантеження з великою кількістю sync (бази даних, NFS зі sync, образи VM) поводяться інакше ніж лог‑орієнтовані асинхронні записувачі.
  • Шукайте активність ZIL і поведінку flush, якщо стрибки латентності періодичні або прив’язані до fsync.

Третій: визначте, чи вузьке горлечко — CPU/IRQs, черги чи TXG

  • Якщо одне CPU завантажене перериваннями/softirq: виправте IRQ affinity і відображення черг.
  • Якщо черги IO глибокі і час очікування зростає: це чергування; зменшіть конкурентність, змініть параметри планувальника або вирішіть апаратні/прошивкові обмеження контролера.
  • Якщо затримки збігаються з синхронізаціями TXG: проблема не «закінчилися NVMe», а «закінчилася пропускна здатність конвеєра ZFS» (часто metaslab/алокація або тиск пам’яті).

Жарт #1: Хвостова затримка — як хатній кіт: тихий увесь день, а о 3 ранку несеться по вашій панелі приладів без видимої причини.

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

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

Завдання 1: Підтвердити топологію пулу і ashift (вирівнювання)

cr0x@server:~$ sudo zpool status -v
  pool: tank
 state: ONLINE
  scan: scrub repaired 0B in 00:18:21 with 0 errors on Mon Dec 23 02:10:01 2025
config:

        NAME                        STATE     READ WRITE CKSUM
        tank                        ONLINE       0     0     0
          mirror-0                  ONLINE       0     0     0
            nvme0n1p2               ONLINE       0     0     0
            nvme1n1p2               ONLINE       0     0     0

errors: No known data errors
cr0x@server:~$ sudo zdb -C tank | egrep -i 'ashift|vdev_tree' -n | head
55:        ashift: 12

Що це означає: ashift: 12 означає 4K сектори. Для багатьох NVMe‑дисків 4K є коректним; деякі віддають перевагу 8K/16K вирівнюванню, але ZFS не змінить ashift після створення.

Рішення: Якщо ashift рівний 9 (512B) на SSD/NVMe, плануйте перебудову. Жоден хитрий sysctl не врятує вас від штрафів read-modify-write.

Завдання 2: Перевірити властивості датасету, що часто впливають на затримку

cr0x@server:~$ sudo zfs get -o name,property,value -s local,default recordsize,compression,atime,sync,logbias,primarycache,secondarycache xattr,dnodesize tank
NAME  PROPERTY        VALUE
tank  recordsize      128K
tank  compression     lz4
tank  atime           off
tank  sync            standard
tank  logbias         latency
tank  primarycache    all
tank  secondarycache  all
tank  xattr           sa
tank  dnodesize       legacy

Що це означає: Це істина щодо поведінки. sync=standard зазвичай правильний вибір; logbias=latency означає «оптимізувати для затримки».

Рішення: Змінюйте властивості відповідно до навантаження, а не з забобонів. Якщо ви хостите бази даних, розгляньте менший recordsize (16K/32K) і залишайте compression=lz4 ввімкнутим, якщо CPU явно не є вузьким горлечком.

Завдання 3: Виміряти латентність пулу і чергування з перспективи ZFS

cr0x@server:~$ sudo zpool iostat -v tank 1 5
              capacity     operations     bandwidth
pool        alloc   free   read  write   read  write
----------  -----  -----  -----  -----  -----  -----
tank        1.20T  2.30T   8.1K  12.4K   410M   980M
  mirror    1.20T  2.30T   8.1K  12.4K   410M   980M
    nvme0n1     -      -   4.0K   6.2K   205M   490M
    nvme1n1     -      -   4.1K   6.2K   205M   490M

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

Рішення: Якщо один NVMe постійно робить менше роботи в дзеркалі, підозрівайте прошивку, обмеження PCIe лінії або теплове обмеження.

Завдання 4: Перевірити стан NVMe і індикатори дроселювання

cr0x@server:~$ sudo nvme smart-log /dev/nvme0n1 | egrep -i 'temperature|warning|critical|media|percentage|thm'
temperature                             : 71 C
critical_warning                        : 0x00
percentage_used                         : 4%
media_errors                            : 0
warning_temp_time                       : 12
critical_comp_time                      : 0

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

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

Завдання 5: Підтвердити швидкість і ширину лінку PCIe (тихий обмежувач пропускної здатності)

cr0x@server:~$ sudo lspci -s 0000:5e:00.0 -vv | egrep -i 'LnkCap|LnkSta'
LnkCap: Port #0, Speed 16GT/s, Width x4
LnkSta: Speed 16GT/s, Width x4

Що це означає: Для багатьох NVMe очікується Gen4 x4 лінк. Якщо бачите x2 або Gen3 несподівано, ви знайшли момент «NVMe не швидкий».

Рішення: Виправляйте BIOS lane bifurcation, вибір слота, riser‑и або проводку M.2 backplane. Це апаратна проблема, а не тюнінг.

Завдання 6: Проінспектувати латентність блокового шару (await) і поведінку глибини черги

cr0x@server:~$ iostat -x -d 1 5 nvme0n1 nvme1n1
Device            r/s     w/s   rKB/s   wKB/s  rrqm/s  wrqm/s  r_await  w_await  aqu-sz  %util
nvme0n1         4100   6200  210000  505000     0.0     0.0     0.35     1.90    6.20   78.0
nvme1n1         4200   6200  210000  505000     0.0     0.0     0.36     1.85    6.05   77.5

Що це означає: await — середня латентність запиту. aqu-sz показує середній розмір черги. Високе aqu-sz зі зростанням await означає чергування (затори), а не сиро‑пристрій‑латентність.

Рішення: Якщо aqu-sz велике і %util близько 100% з ростом await, ви насичуєте пристрій або чергу. Зменшіть конкурентність, розподіліть навантаження або додайте vdev‑и.

Завдання 7: Перевірити розподіл IRQ драйвера NVMe (одне ядро робить усю роботу)

cr0x@server:~$ grep -i nvme /proc/interrupts | head -n 12
  98:  10293812          0          0          0   PCI-MSI 524288-edge      nvme0q0
  99:   1938120     1980221     2018830     1999012   PCI-MSI 524289-edge      nvme0q1
100:   1912202     1923301     1899987     1901120   PCI-MSI 524290-edge      nvme0q2
101:   1908821     1910092     1903310     1899922   PCI-MSI 524291-edge      nvme0q3

Що це означає: Якщо одна лінія IRQ різко зростає на CPU0, тоді як інші лишаються плоскими, ви маєте вузьке горлечко на обробці переривань. Черга 0 (часто адміністративна + IO) може бути спеціальною; не панікуйте через nvme0q0 саму по собі, але слідкуйте за дисбалансом.

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

Завдання 8: Підтвердити NUMA‑локальність і чи NVMe «не далеко» від CPU, що працює

cr0x@server:~$ sudo cat /sys/class/nvme/nvme0/device/numa_node
1
cr0x@server:~$ numactl -H | sed -n '1,25p'
available: 2 nodes (0-1)
node 0 cpus: 0 1 2 3 4 5 6 7 8 9 10 11
node 1 cpus: 12 13 14 15 16 17 18 19 20 21 22 23
node distances:
node   0   1
  0:  10  21
  1:  21  10

Що це означає: Якщо NVMe знаходиться на NUMA‑вузлі 1, але більшість переривань і потоків ZFS працюють на вузлі 0, ви платите за міжвузлову латентність і штрафи пам’яті/пропускної здатності.

Рішення: Узгодьте IRQ affinity і, у критичних випадках, розміщення процесів (потоки бази даних) з NUMA‑вузлом NVMe.

Завдання 9: Перевірити стани потоків ZFS і шукати тиск TXG sync

cr0x@server:~$ ps -eLo pid,tid,cls,rtprio,pri,psr,stat,wchan:20,comm | egrep 'txg|z_wr_iss|z_wr_int|arc_reclaim|dbu|zio' | head
  1423  1450 TS      -  19  12 S    txg_sync_thread  txg_sync
  1423  1451 TS      -  19  13 S    txg_quiesce      txg_quiesce
  1423  1462 TS      -  19  14 S    zio_wait        z_wr_int
  1423  1463 TS      -  19  15 S    zio_wait        z_wr_int
  1423  1488 TS      -  19  16 S    arc_reclaim_thr arc_reclaim

Що це означає: Наявність потоків — нормально. Багато потоків, заблокованих на zio_wait під час сплесків, свідчить про тиск на бекенд; активний arc_reclaim вказує на тиск пам’яті.

Рішення: Якщо reclaim корелює з затримками, не «тюнюйте ZFS» — почніть вирішувати проблему з пам’яттю: обмеження ARC, RSS додатків і поведінку виснаження ядра.

Завдання 10: Спостерігати тиск пам’яті і хвилі звільнення

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
 4  0      0  52132  91244 8032100   0    0     2   180 8200 9900 22  9 66  3  0
 7  1      0  11200  88010 7941200   0    0     0  2500 14000 22000 28 14 50  8  0

Що це означає: Зростання b (blocked), зменшення free і збільшення контекстних переключень під час інцидентів — класичний запах «система треше». На NVMe пристрій достатньо швидкий, щоб зробити петлі звільнення пам’яті «цікавими».

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

Завдання 11: Підтвердити sync‑навантаження і чи домінують flush‑и

cr0x@server:~$ sudo zpool iostat -r -w tank 1 3
              capacity     operations     bandwidth
pool        alloc   free   read  write   read  write
----------  -----  -----  -----  -----  -----  -----
tank        1.20T  2.30T   2.2K  18.1K   120M  1.10G

                              read                            write
pool        ops   bytes   latency  disk latency    ops   bytes   latency  disk latency
----------  ---  -----  ---------  ------------   ---  -----  ---------  ------------
tank       2.2K   120M    420us       160us      18.1K  1.10G    3.8ms      2.9ms

Що це означає: Латентність на рівні ZFS проти дискової латентності допомагає відокремити витрати конвеєра ZFS від часу пристрою. Якщо латентність ZFS значно вища за дискову, ви платите за CPU/блокування/черги всередині стеку.

Рішення: Якщо дискова латентність низька, а ZFS‑латентність висока — дивіться на CPU, IRQ і конкурентність ZFS; не купуйте більше NVMe.

Завдання 12: Виміряти час softirq по CPU (завершення зберігання відображаються тут)

cr0x@server:~$ mpstat -P ALL 1 3 | egrep -A1 'CPU|Average|  0| 12'
CPU    %usr   %nice    %sys %iowait    %irq   %soft  %steal  %guest  %gnice   %idle
  0     3.12    0.00    6.01    0.21    0.90   28.44    0.00    0.00    0.00   61.31
 12    15.90    0.00   10.82    0.12    0.10    3.20    0.00    0.00    0.00   69.86

Що це означає: CPU0 витрачає 28% на softirq — часто «занадто багато роботи з завершення IO на одному ядрі». Це часта причина, коли NVMe «повинен бути швидким», але не є.

Рішення: Виправте IRQ affinity, увімкніть множинні черги і вирівняйте по NUMA. Якщо потрібно, розгляньте NVMe polling (обережно) для чутливих до латентності навантажень.

Завдання 13: Переглянути конфігурацію черги і вибір планувальника

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

Що це означає: Для NVMe none є поширеним і часто правильним вибором. Планувальники можуть допомогти у справедливості; але також можуть додавати накладні витрати. nr_requests впливає на глибину черги на блоковому шарі, що може вплинути на хвостову латентність.

Рішення: Якщо ваш ворог — хвостова латентність, уникайте величезних черг, що приховують затори. Розгляньте mq-deadline для передбачуванішої латентності, якщо none дає погані p99 під змішаним IO.

Завдання 14: Переглянути налаштування ZFS, які слід розглядати як «останню милю», а не магію

cr0x@server:~$ sudo sysctl kstat.zfs.misc.arcstats.size kstat.zfs.misc.arcstats.c kstat.zfs.misc.arcstats.mfu_size kstat.zfs.misc.arcstats.mru_size
kstat.zfs.misc.arcstats.size = 68412985344
kstat.zfs.misc.arcstats.c = 73014444032
kstat.zfs.misc.arcstats.mfu_size = 51200901120
kstat.zfs.misc.arcstats.mru_size = 15600930816

Що це означає: Розмір і ціль ARC мають значення. Якщо ARC конкурує з додатком за пам’ять, виникає churn reclaim. Якщо ARC надто малий, ви трете читання і метадані.

Рішення: Встановлюйте ARC max явно на машині зі змішаною роллю. На виділених сховищах дайте ARC дихати, якщо немає вагомих причин інакше.

Завдання 15: Підтвердити поведінку TRIM/autotrim і чи збігається це зі стрибками

cr0x@server:~$ sudo zpool get autotrim tank
NAME  PROPERTY  VALUE     SOURCE
tank  autotrim  on        local
cr0x@server:~$ sudo zpool trim -v tank
trim operation for tank has completed
trim rate: 0B/s
total trimmed: 0B

Що це означає: Autotrim загалом корисний для SSD/NVMe, але деякі пристрої погано обробляють фонове виділення, особливо під важкими записами.

Рішення: Якщо ви можете зіставити стрибки з trim, встановіть autotrim=off і плануйте trim у тихий час. Якщо продуктивність погіршується за тижні без trim, внутрішній GC пристрою потребує допомоги — тому не вимикайте це назавжди.

Налаштування датасетів і пулу, які варто робити (і що — ритуал)

Стиснення: залишайте lz4 увімкненим, потім вимірюйте CPU

На NVMe‑пулі стиснення часто допомагає більше, ніж шкодить. Воно зменшує записувані/читані байти, що знижує навантаження на бекенд і може покращити латентність. Податок — це CPU. На сучасних CPU lz4 зазвичай вигідне рішення.

Коли це шкодить? Дані з високою ентропією (вже стиснені), дрібні блоки з великою кількістю sync‑записів або коли ви CPU‑зв’язаний через переривання і перевірки контрольних сум.

recordsize: припиніть використовувати 128K як моральний імператив

recordsize визначає форму IO. Бази даних, образи VM і лог‑структуровані додатки зазвичай віддають перевагу 8K–32K. Потоки для стрімінгу (резервні копії, медіа, аналітика) люблять 128K або більше.

На NVMe великий recordsize все ще може бути допустимим, але дрібні випадкові оновлення великих записів викликають write amplification. Пристрій достатньо швидкий, щоб ви цього не помітили, поки p99 не покаже зуби.

atime і xattr: нудні перемикачі, реальні виграші

Вимкнення atime зменшує метадані‑записи. Зберігання xattr як SA (xattr=sa) зменшує IO для робочих навантажень, що інтенсивно використовують ACL і метадані. Це не гламурно; але часто правильно.

dnodesize: для метаданих важливі кращі значення за замовчуванням

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

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

sync=disabled: не робіть цього, якщо не готові втратити дані і зберегти роботу

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

Жарт #2: sync=disabled — це схоже на те, щоб вийняти пожежний датчик, бо він голосно пищить.

IRQs, черги та CPU: нелюксовий вузький горлечко

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

Як виглядає «добре»

  • Активні множинні NVMe IO черги (nvme0q1..qN), а не тільки черга 0.
  • Переривання розподілені по CPU на правильному NUMA вузлі.
  • Час softirq немалий, але не концентрується на одному ядрі.
  • Потоки додатків і потоки ZFS не борються за ті самі кілька CPU.

Що ви справді можете змінити безпечно

IRQ affinity: прив’язуйте NVMe IRQ‑и до набору CPU локального для NUMA‑вузла пристрою. Не прив’язуйте все до CPU0, бо якийсь блог із 2017 року казав «CPU0 обробляє переривання». Той блог писали, коли люди носили інше взуття.

irqbalance: може допомогти, може нашкодити. Для серверів загального призначення — ок. Для критичного до латентності зберігання зазвичай бажана свідома прив’язка.

Планувальник: none підходить для чистої пропускної здатності. Якщо у вас змішане IO і має значення p99, тестуйте mq-deadline. Не припускайте — вимірюйте.

Синхронні записи, міфи про SLOG і що змінює NVMe

NVMe‑пули породжують спокусу: «Якщо пул вже NVMe, чи потрібен мені SLOG?» Чесна відповідь: зазвичай ні. Корисна відповідь: залежить від патерну ваших sync‑записів і від того, що означає «довговічність» у вашому середовищі.

Уточнимо, що робить ZIL/SLOG

ZIL (ZFS Intent Log) існує, щоб записати достатньо намірів, щоб синхронні записи могли підтверджуватись без очікування повного коміту TXG. Після збою відтворення робить останні зафіксовані sync‑операції довговічними.

SLOG — це окремий пристрій для збереження записів ZIL. Він допомагає, коли основний пул повільний на sync‑записах або flush‑ах. На NVMe‑пулі основний пул може вже добре справлятися. Додавання SLOG все ще може допомогти, якщо:

  • основний NVMe пул має погану латентність flush під навантаженням,
  • у вас важкі sync‑навантаження і ви хочете ізоляції,
  • вам потрібна поведінка лог‑пристрою з захистом від втрати живлення.

Справжнє обмеження: поведінка flush і гарантії надійності

Продуктивність sync часто залежить від семантики скидання кешу, а не сирої швидкості носія. Диск може робити 700k IOPS і водночас мати жахливу латентність fsync, якщо прошивка має консервативну поведінку flush під тривалим записом.

Поведінка TXG, затримки та чому «швидке» медіа не рятує

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

  • у вас сплески випадкових записів, що спричиняють тиск на алокатор,
  • нагромаджуються оновлення метаданих (снапшоти, дрібні файли, atime),
  • система має обмежену пам’ять і не може плавно буферизувати.

Розпізнати симптоми, пов’язані з TXG

  • Стрибки латентності з’являються з ритмом (часто кожні кілька секунд).
  • Зростання використання CPU у потоках ядра, а не в користувацькому просторі.
  • Латентність пристрою залишається помірною, але латентність на рівні ZFS зростає.

Існує тюнінг TXG, але це не те, з чого починати. Починають із перевірки, чи система не задихається на перериваннях, звільненні пам’яті чи патологічних IO‑формах.

Спеціальні vdev‑и та метадані в NVMe‑пулах

Спеціальні vdev‑и часто продають як «помістіть метадані на SSD». У NVMe‑пулі ви вже це зробили. То навіщо про це говорити?

Тому що «NVMe‑пул» не означає «всі NVMe однакові», і тому що метадані й дрібні блоки мають інші профілі продуктивності, ніж великі послідовні записи. Спеціальний vdev все ще може допомогти, якщо:

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

Але спеціальні vdev‑и — це зобов’язання: якщо ви втратите його без надлишковості, ви втратите пул. Дзеркальте або не робіть цього.

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

Інцидент через неправильне припущення: «NVMe mirror = більше ніяких затримок»

Середній SaaS провайдер переніс свою основну кластерну базу Postgres на NVMe‑дзеркальний пул ZFS. План міграції був ретельний: scrub перед cutover, снапшоти, відкат. Графіки продуктивності в стейджингу виглядали чудово. Всі спали спокійно.

У продакшені кожні кілька хвилин база зависала лише на стільки, щоб зіпсувати таймаути додатка. Не повний збій — гірше. Повільні помилки й повторні спроби. On‑call дивився на пропускну здатність дисків, яка була далеко від насичення, і на NVMe SMART, що виглядав чистим.

Неправильне припущення полягало в тому, що «швидкий диск означає, що IO не може бути проблемою». Насправді проблема була в одному CPU‑якірі, який був завантажений softirq під час пікового трафіку. Завершення NVMe потрапляли на вузький набір CPU через комбінацію дефолтної IRQ‑affinity і розміщення NUMA. Під нормальною нагрузкою це проходило, а під пікою це ядро стало вузьким горлечком, черги зросли, і потоки бази даних накопичувалися за ним.

Вони вирішили це, вирівнявши NVMe IRQ‑и до CPU локальних для NUMA‑вузла NVMe, і перемістили процес Postgres подалі від найгарячіших CPU переривань. Пул не змінювався. Диски не змінювалися. Затримка змінилася.

Оптимізація, яка відкинула назад: «Піднімемо глибину черги і вимкнемо sync»

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

Числа покращилися. Пропускна здатність зросла. Всі плескали. Потім настав понеділок. Приїхали реальні навантаження: змішані читання, записи, метадані і сплески sync із файлових систем гостьових ОС.

Хвостова латентність пішла з крутого схилу. Глибокі черги приховували затори доти, доки було надто пізно, а коли система відставала, вона відставала великими, огидними шматками. Вони також тестували sync=disabled на VM датасетах «тільки заради продуктивності». Це справді покращило fsync у гостя — аж поки крах хоста не перетворив «тільки заради продуктивності» у вечірку з ремонту файлових систем для багатьох VM.

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

Нудна, але правильна практика, що врятувала день: pinning, спостережливість і ран-бук

Одна команда підприємства вела три нудні речі: явно задавали ARC max, документували irq affinity і щотижня проводили «дриль латентності», де фіксували 10 хвилин iostat/mpstat/zpool iostat під піковим часом.

Одного тижня латентність почала повільно зростати. Не настільки, щоб спричинити відмови, але достатньо, щоб SLO‑тривога розбудила когось. Дані дрилю показали новий патерн: w_await одного NVMe подвоювався під тривалим writeback, тоді як його дзеркальний партнер лишався стабільним. Пул лишався онлайн; нічого «не ламалося».

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

Без героїки. Без магічних налаштувань. Просто нудна дисципліна: базові показники, pinning і заміна обладнання до інциденту.

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

  • Симптом: Чудова середня латентність, жахливий p99 під навантаженням.
    Корінь: Чергування + концентрація переривань; глибокі черги ховають затори.
    Виправлення: Перевірте iostat -x на зростання aqu-sz; перерасподіліть NVMe IRQ‑и; розгляньте mq-deadline; зменшіть конкурентність де можливо.
  • Симптом: Періодичні «зависання» кожні кілька секунд під сильними записами.
    Корінь: Сплески TXG sync + тиск алокатора/metaslab, іноді посилені метаданими.
    Виправлення: Зменшіть метадані‑записи (atime=off, xattr=sa), підберіть recordsize під навантаження, забезпечте достатньо RAM; перевірте ZFS vs диск латентність через zpool iostat -r -w.
  • Симптом: NVMe показує низьку дискову латентність, але додаток бачить високу IO‑латентність.
    Корінь: CPU‑вузьке горлечко в checksum/compression/обробці переривань; блокування.
    Виправлення: Використайте mpstat для пошуку hot spot softirq; розподіліть IRQ‑и; підтвердьте запас CPU; уникайте важкого стиснення; не надпаралелізуйте дрібні IO.
  • Симптом: Sync‑навантаження (fsync) повільне навіть на NVMe.
    Корінь: Латентність flush/FUA під навантаженням; прошивка; іноді віртуалізаційні шари.
    Виправлення: Виміряйте sync IO окремо; розгляньте дзеркальний PLP лог‑пристрій; уникайте sync=disabled, якщо політика не дозволяє втрату даних.
  • Симптом: Один пристрій у дзеркалі відстає або працює гірше.
    Корінь: Теплове дроселювання, зниження PCIe‑лінку, прошивкові особливості.
    Виправлення: Перевірте SMART темп‑час, статус lspci лінку і порівняйте iostat -x по пристроях; виправте охолодження або конфігурацію слота/BIOS.
  • Симптом: Стрибки латентності під час trim або після великих видалень.
    Корінь: Взаємодія TRIM/GC; прошивка пристрою зависає під деалокацією під навантаженням.
    Виправлення: Тимчасово відключіть autotrim і плануйте trim; перевірте, що довгострокова продуктивність запису не погіршується.

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

Крок за кроком: стабілізувати латентність на NVMe‑тільки ZFS‑пулі

  1. Спочатку базові показники: захопіть zpool iostat, iostat -x, mpstat і /proc/interrupts під нормальним піковим навантаженням.
  2. Підтвердьте апаратну реальність: ширина/швидкість PCIe‑лінку; температури NVMe; версії прошивки, якщо ваша організація їх відстежує.
  3. Виправте очевидні CPU‑вузькі місця: розподіл IRQ‑ів; вирівнювання NUMA; не допускайте голодування потоків ZFS.
  4. Підтвердьте семантику датасетів: переконайтесь, що sync відповідає вимогам надійності; встановіть atime=off там, де доречно; зберігайте compression=lz4, якщо CPU не є затвором.
  5. Підбирайте recordsize під навантаження: бази даних/VM зазвичай менші; стрімінг — більші. Вимірюйте з IO‑розмірами, які схожі на продакшн.
  6. Спостерігайте тиск пам’яті: встановіть ARC max, якщо сервер виконує додатки; запобігайте хвилям reclaim.
  7. Повторно перевіряйте p99: не зупиняйтесь на середній пропускній здатності. Запустіть змішане навантаження і дивіться хвости.
  8. Тільки потім розглядайте екзотичні налаштування: планувальники, глибина черг, polling. Кожна зміна має план відкату.

Чекліст: перед тим як звинувачувати ZFS

  • PCIe‑лінк має очікувану швидкість/ширину для кожного NVMe.
  • Жоден NVMe не знаходиться в зоні постійного температурного попередження.
  • IRQs розподілені та локальні для правильного NUMA‑вузла.
  • softirq CPU не концентрується на одному ядрі.
  • Достатньо RAM і немає swap/reclaim трешу.
  • Ви можете пояснити, чи проблема — sync, async, читання чи метадані.

FAQ

1) Чи потрібен SLOG на NVMe‑тільки пулі?

Зазвичай ні. Додавайте SLOG лише якщо у вас вимірювані проблеми з латентністю sync‑записів і є відповідний низьколатентний пристрій із захистом від втрати живлення. Інакше ви додаєте складність заради відчуття.

2) Чи слід встановлювати sync=disabled заради продуктивності?

Тільки якщо бізнес готовий до втрати даних під час збою і ви можете довести, що додаток безпечно поводиться в таких умовах. Для баз даних, NFS і VM‑записів це зазвичай поганий обмін.

3) Чи все ще корисне compression=lz4 на NVMe?

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

4) Чому мій NVMe «лише» виконує частину заявлених IOPS?

Тому що ваша система — не тестова платформа виробника. Обробка переривань, NUMA, чергування, перевірки ZFS і змішане навантаження знижують заголовні числа. Також важать дзеркальні vdev‑и і семантика sync.

5) Чи використовувати mq-deadline чи none для NVMe?

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

6) Як зрозуміти, що я CPU‑зв’язаний, а не обмежений сховищем?

Якщо дискова латентність низька, але ZFS/додаткова латентність висока, і ви бачите високий відсоток %soft або одне ядро насичене — ви CPU‑зв’язаний. Підтвердіть через mpstat і розподіл переривань.

7) Чи завжди більша глибина черги покращує продуктивність?

Ні. Це може підвищити пропускну здатність і погіршити латентність. Глибокі черги міняють передбачуваність на масове переміщення. Якщо ваше навантаження клієнтське, частіше хочуть контрольовані черги і стабільні хвости.

8) Яка найпоширеніша причина «загадкової» латентності NVMe на ZFS?

Обробка переривань і завершень, зосереджена на занадто малої кількості CPU, часто ускладнена NUMA‑невідповідністю. Це надзвичайно поширено і виправно.

9) Чи варто налаштовувати параметри TXG, щоб виправити стусани?

Тільки після виключення проблем з IRQ/CPU, тиском пам’яті і патологічними формами IO. TXG‑ручки допомагають у крайніх випадках, але їх легко неправильно застосувати і складно розуміти ефект.

Висновок: наступні кроки, які ви можете зробити цього тижня

Якщо ви хочете заспокоїти пейджер і покращити p99 латентність на NVMe‑тільки ZFS‑пулі, робіть нелюксну роботу спочатку. Перевірте PCIe‑лінки, усуньте теплове дроселювання, розподіліть IRQ‑и і вирівняйте NUMA. Потім підберіть властивості датасетів під навантаження: recordsize там, де важливо, atime=off там, де це шум, і тримайте lz4, доки не доведете, що CPU — вузьке горлечко.

Наступні кроки:

  1. Захопіть базові показники під піковим навантаженням: iostat -x, zpool iostat -r -w, mpstat і /proc/interrupts.
  2. Виправте очевидні питання IRQ/NUMA. Повторно виміряйте p99.
  3. Аудит датасетів: визначте, які з них — бази даних/VM/логи/стрімінг, і налаштуйте recordsize відповідно.
  4. Явно вирішіть, чи вам потрібні строгі семантики sync; не міняйте довговічність випадково.
  5. Напишіть короткий ран‑бук: які графіки/команди перевіряти спочатку і що означає «добре» для вашого флоту.
← Попередня
Реагування на рансомваре в ZFS: сценарій зі снапшотами, що рятує
Наступна →
Випадкові тайм‑аути в Debian/Ubuntu: трасування мережевого шляху за допомогою mtr і tcpdump (Випадок №4)

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