Стрибки затримок ZFS: чекліст, який знаходить причину

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

Стрибки затримок — це той тип проблем зі сховищем, через які розумні люди починають говорити дивні речі в інцидентних каналах. Все працює… поки не перестає. Ваш API p99 вискакує до 800ms, база даних починає «чекати IO», а сервер ZFS виглядає байдужим.

ZFS не «випадково повільний». Зазвичай він виконує щось дуже конкретне на певному рівні: синхронні записи, TXG commit, черги пристроїв, звільнення пам’яті, scrub/resilver або невдале поєднання навантаження. Трюк у тому, щоб перестати гадати і пройти чекліст, який звузить вузьке місце за хвилини, а не години.

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

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

Перший: підтвердьте, що це затримка сховища, а не планування CPU чи мережа

  1. Перевірте завантаження системи та IO wait. Якщо CPU завантажений або відбувається інтенсивний трешинг пам’яті, сховище виглядатиме винним, навіть коли це не так.

    cr0x@server:~$ uptime
     14:22:18 up 18 days,  3:11,  2 users,  load average: 7.81, 7.34, 6.92
    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  1      0  82120  15640 912340    0    0   120   980  540  890 12  5 73 10  0
     1  2      0  81288  15640 905112    0    0   240  2030  620 1010 10  6 62 22  0
    

    Що це означає: високий показник wa (IO wait) свідчить, що CPU часто стоїть у блокуванні, чекаючи на IO. Високий b означає заблоковані процеси, часто через диск.

    Рішення: Якщо піки wa збігаються з піками затримки застосунку, переходьте до специфічних перевірок ZFS. Якщо us/sy вичавлені вгору, спочатку вивчіть вузьке місце CPU (компресія, контрольні суми, шифрування).

  2. Перевірте мережу, якщо це NFS/SMB/iSCSI.

    cr0x@server:~$ ss -ti | head -n 15
    State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
    ESTAB 0      0      10.0.0.12:2049    10.0.2.45:51712  timer:(keepalive,38min,0) ino:0 sk:3b2
    	 cubic wscale:7,7 rto:204 retrans:0/0 rtt:0.337/0.012 ato:40 mss:1448 pmtu:1500 rcvmss:1448 advmss:1448 cwnd:10 bytes_sent:818244 bytes_acked:818244 bytes_received:94412 segs_out:571 segs_in:558 send 343Mb/s lastsnd:12 lastrcv:12 lastack:12 pacing_rate 686Mb/s
    

    Що це означає: повторні передачі та великі коливання RTT можуть імітувати «спайки сховища».

    Рішення: Якщо мережа чиста (стабільний RTT, немає retrans), зосередьтеся на диску/ZFS.

Другий: визначте, чи пов’язані спайки із синхронними записами/TXG

  1. Слідкуйте за затримкою ZFS на рівні пулу.

    cr0x@server:~$ zpool iostat -v -l 1 10
                                  capacity     operations     bandwidth     total_wait     disk_wait
    pool                        alloc   free   read  write   read  write   read  write   read  write
    tank                        3.12T  1.45T    210   9800  12.3M  402M   2.1ms  85ms   1.9ms  82ms
      raidz2-0                  3.12T  1.45T    210   9800  12.3M  402M   2.1ms  85ms   1.9ms  82ms
        sda                         -      -     20   1250  1.3M  50.1M  1.8ms  88ms   1.7ms  84ms
        sdb                         -      -     22   1210  1.4M  49.6M  2.0ms  86ms   1.8ms  83ms
        sdc                         -      -     19   1220  1.2M  50.0M  2.1ms  84ms   1.9ms  81ms
    

    Що це означає: total_wait — це те, що відчувають виклики; disk_wait відокремлює час обслуговування пристрою. Якщо total_wait високий, а disk_wait низький, черга утворюється вище за диски (TXG, throttling, конкуренція).

    Рішення: Якщо час очікування записів підскакує до десятків/сотень мс під час спайків, підозрюйте синхронні записи, SLOG, тиск TXG commit або насичення пулу.

Третій: перевірте «обслуговуючий IO» та очевидну конкуренцію

  1. Чи виконується scrub/resilver?

    cr0x@server:~$ zpool status -v tank
      pool: tank
     state: ONLINE
    status: One or more devices is currently being scrubbed.
      scan: scrub in progress since Mon Dec 23 02:01:13 2025
            1.22T scanned at 1.02G/s, 438G issued at 365M/s, 3.12T total
            0B repaired, 14.04% done, 0:02:45 to go
    config:
    
            NAME        STATE     READ WRITE CKSUM
            tank        ONLINE       0     0     0
              raidz2-0  ONLINE       0     0     0
                sda     ONLINE       0     0     0
                sdb     ONLINE       0     0     0
                sdc     ONLINE       0     0     0
    

    Що це означає: Scrub і resilver — це легальні IO-штормі. Вони також змінюють шаблони IO (більше читань, більше метаданих).

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

Практична модель затримок ZFS

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

  • Семантика застосунку: sync проти async записів, fsync-шторми, чекпоїнти баз даних.
  • Рівень файлової системи: властивості dataset (recordsize, compression, atime), поведінка метаданих і малих блоків, спеціальні vdev.
  • ARC і пам’ять: кеш-хіти швидкі; пропуски кешу та інтенсивне звільнення — ні. Тиск пам’яті робить усе нервовим.
  • TXG (групи транзакцій): ZFS пакетно обробляє зміни і комітить їх. Коли робота з комітом накопичується, можна побачити періодичні паузи або хвилі затримки запису.
  • ZIL/SLOG (синхронні записи): синхронні записи підтверджуються після безпечного журналювання. Якщо лог-пристрій повільний, ваш застосунок вивчить нові слова.
  • Топологія vdev і диски: RAIDZ-математика, глибина черги, SMR-особливості, глюки прошивки, політики кешування запису.
  • Рівень блочного пристрою: вибір планувальника, multipath, HBA, таймаути дисків.

Коли хтось каже «ZFS спайки», запитайте: де саме спайки? У латентності читання? запису? тільки синхронні записи? тільки метадані? тільки коли пул зайнятий на 80%? тільки під час бекапів? Ваше завдання — перетворити «спайкі» на графік з винуватцем.

Одна думка, яка заощадить час: ставтеся до ZFS як до бази даних. У нього є своє пакетування (TXG), логування (ZIL), кеш (ARC) і фонові роботи (scrub/resilver). Якщо ви б не налаштовували базу даних, рандомно крутячи ручки, не робіть цього з ZFS теж.

Перефразована ідея від Werner Vogels (CTO Amazon): «Все ламається постійно; проєктуйте системи, що це очікують». Це стосується й вашого ліміту затримок сховища.

Жарт #1: ZFS не має «настроїв настрою». У нього є «облік I/O». Це менш весело, але більш придатно до дій.

Цікаві факти та коротка історія (чому ZFS так поводиться)

  • ZFS з’явився в середині 2000-х з наскрізним контрольним сумуванням як первинною функцією, а не доповненням. Це рішення впливає на затримки, тому що кожне читання блоку може включати перевірку контрольної суми.
  • ZIL існує навіть без виділеного SLOG. Якщо ви не додаєте пристрій логів, ZFS використовує простір на пристроях пулу. Ваша конфігурація «без SLOG» все ще має синхронне журналювання; воно просто повільніше і конкурує з звичайними записами.
  • TXG commit відбувається періодично. ZFS пакетно обробляє брудні дані й метадані, а потім скидає їх. Це підвищує пропускну здатність, але може створювати ритмічні імпульси затримки при тривалому створенні бруду.
  • Copy-on-write — і перевага, і податок. Це запобігає перезаписам на місці (добре для цілісності й снапшотів), але може збільшувати фрагментацію і обсяг метаданих з часом, впливаючи на хвостову латентність.
  • RAIDZ — це не «безкоштовний RAID». Він економить диски, але робить малі випадкові записи дорогими (шаблони read-modify-write). Хвостова латентність страждає першою.
  • Реальність 4K секторів з’явилась пізніше за проєкти масиву. Неправильний вибір ashift (або старі припущення про 512e) може перетворити «звичайні записи» на посилений IO.
  • ARC створено адаптивним, а не чемним. Він із задоволенням поглине пам’ять для кешу; якщо ваше навантаження потребує RAM іншде (VM, page cache, бази даних), результатом може бути нестабільність затримок.
  • Спеціальні vdev — це сучасна історія ZFS про «metadata на SSD». Вони можуть драматично зменшити латентність метаданих — поки не наситяться або не вийдуть з ладу; тоді досвід всього пулу змінюється.
  • Компресія стала стандартом у ZFS раніше, ніж це стало модним. Вона обмінює цикл CPU на менше IO; на швидких NVMe CPU стає вузьким місцем частіше, ніж очікують, що призводить до симптомів «затримки сховища».

Чеклісти / покроковий план (з командами)

Це головна частина: практичні завдання, які ви можете виконати під час інциденту і знову під час спокійного постмортему. Кожне завдання включає (1) команду, (2) що означає вивід, (3) рішення, яке ви приймаєте.

Завдання 1: Зафіксуйте латентність на рівні пулу і відокремте чергу від часу обслуговування диска

cr0x@server:~$ zpool iostat -l 1 30
                              capacity     operations     bandwidth     total_wait     disk_wait
pool                        alloc   free   read  write   read  write   read  write   read  write
tank                        3.12T  1.45T    320   9200  18.2M  380M   3.2ms  96ms   2.8ms  91ms
tank                        3.12T  1.45T    310   9300  17.8M  387M   2.9ms  140ms  2.6ms  132ms
tank                        3.12T  1.45T    290   9100  16.4M  376M   3.0ms  35ms   2.7ms  31ms

Що це означає: Коли total_wait роздувається, це видно користувачеві. Якщо disk_wait слідує за ним, диски (або HBA) повільні. Якщо disk_wait залишається помірним, а total_wait різко зростає, вузьке місце вище диска: throttling, TXG-затори, конкуренція ZIL тощо.

Рішення: Піки, спричинені disk_wait, ведуть до перевірок пристроїв/апаратури/черг. Піки лише total_wait — до перевірок sync/TXG/ARC.

Завдання 2: Знайдіть винний vdev або повільний диск

cr0x@server:~$ zpool iostat -v -l 2 10 tank
                              capacity     operations     bandwidth     total_wait     disk_wait
pool                        alloc   free   read  write   read  write   read  write   read  write
tank                        3.12T  1.45T    300   9100  16.8M  375M   3.0ms  72ms   2.7ms  68ms
  raidz2-0                  3.12T  1.45T    300   9100  16.8M  375M   3.0ms  72ms   2.7ms  68ms
    sda                         -      -     30   1200  1.8M  48.9M  2.6ms  70ms   2.4ms  66ms
    sdb                         -      -     29   1180  1.7M  48.2M  2.7ms  72ms   2.5ms  69ms
    sdc                         -      -     28   1190  1.6M  48.6M  35ms   250ms  33ms   240ms

Що це означає: Один пристрій з 10× латентністю тягне вниз RAIDZ vdev, бо паритетна логіка вимагає координації. Дзеркала також страждають: читання можуть обійти повільну сторону, записи — ні.

Рішення: Якщо один диск — аномалія, перевірте SMART, кабелі, шляхи HBA. Замініть диск, якщо він продовжує стрибати. Не «налаштовуйте ZFS» навколо помираючого диска.

Завдання 3: Підтвердіть, чи записи синхронні (і чи змушує їх так ваша робота)

cr0x@server:~$ zfs get -r sync tank
NAME        PROPERTY  VALUE  SOURCE
tank        sync      standard  default
tank/db     sync      standard  inherited from tank
tank/vm     sync      always    local

Що це означає: sync=always змушує кожен запис поводитися як синхронний. Деякі навантаження цього потребують (бази даних), багато — ні (масові логи). standard підкоряється застосунку (fsync/O_DSYNC).

Рішення: Якщо dataset має sync=always без очевидної причини, поверніть його до standard після підтвердження вимог до збереження даних застосунку. Якщо ж це справді потрібно, забезпечте якісний SLOG або прийміть затримку.

Завдання 4: Перевірте, чи є у вас SLOG, і чи він здоровий

cr0x@server:~$ zpool status tank
  pool: tank
 state: ONLINE
config:

        NAME        STATE     READ WRITE CKSUM
        tank        ONLINE       0     0     0
          raidz2-0  ONLINE       0     0     0
            sda     ONLINE       0     0     0
            sdb     ONLINE       0     0     0
            sdc     ONLINE       0     0     0
        logs
          nvme0n1p2 ONLINE       0     0     0

Що це означає: Якщо в конфігурації пулу є секція logs, у вас є окремий лог-пристрій. Якщо ні — синхронні записи потрапляють на основні vdev. Також: ONLINE SLOG може бути повільним.

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

Завдання 5: Валідируйте recordsize і volblocksize dataset у відповідності з навантаженням

cr0x@server:~$ zfs get recordsize,volblocksize,primarycache,logbias tank/vm tank/db
NAME      PROPERTY      VALUE   SOURCE
tank/vm   recordsize    128K    inherited from tank
tank/vm   volblocksize  -       -
tank/vm   primarycache  all     default
tank/vm   logbias       latency default
tank/db   recordsize    16K     local
tank/db   volblocksize  -       -
tank/db   primarycache  all     default
tank/db   logbias       latency local

Що це означає: Бази даних часто віддають перевагу recordsize 8K–16K для файлів; VM-образи можуть потребувати volblocksize, підібраного при створенні zvol (типові значення 8K–64K залежно від гіпервізора). Неправильні розміри збільшують read-modify-write і навантаження на метадані.

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

Завдання 6: Перевірте заповненість пулу та тиск фрагментації

cr0x@server:~$ zpool list -o name,size,alloc,free,cap,frag,health
NAME  SIZE  ALLOC  FREE  CAP  FRAG  HEALTH
tank  4.50T 3.12T 1.38T  69%   41%  ONLINE

Що це означає: Високий cap (особливо понад ~80–85%) і велика фрагментація часто корелюють з поганою хвостовою латентністю, особливо на HDD RAIDZ. У ZFS менше суміжних вільних сегментів; виділення стає дорожчим.

Рішення: Якщо cap високий — звільніть місце (видалення, переміщення, розширення). Якщо frag висока і продуктивність падає, розгляньте перезапис даних (send/recv на свіжий пул) або додавання vdev для збільшення варіантів виділення.

Завдання 7: Перевірте розмір ARC, коефіцієнт влучань і сигнали тиску пам’яті

cr0x@server:~$ arcstat 1 5
    time  read  miss  miss%  dmis  dm%  pmis  pm%  mmis  mm%  arcsz     c
14:25:01   820    44      5    10    1    34    4     0    0   62G   64G
14:25:02   910   210     23    80    9   130   14     0    0   62G   64G
14:25:03   840   190     22    70    8   120   14     0    0   58G   64G
14:25:04   870    55      6    12    1    43    5     0    0   58G   64G

Що це означає: Піки miss% і зменшення ARC можуть вказувати на тиск пам’яті або зміну навантаження. Коли ARC не вміщує гарячі дані, читання перетворюються на реальний диск IO, і латентність стає стрибкоподібною.

Рішення: Якщо ARC нестабільний і miss% підскакує під час інцидентів, перевірте загальну пам’ять, поведінку reclaim і чи щось інше (VM, контейнери) їсть RAM. Розгляньте резервування пам’яті, налаштування max ARC або переміщення «галасливого» орендатора.

Завдання 8: Визначте тиск синхронних записів через статистику ZIL

cr0x@server:~$ kstat -p | egrep 'zfs:0:zil:|zfs:0:vdev_sync' | head
zfs:0:zil:zil_commit_count                        184220
zfs:0:zil:zil_commit_writer_count                  12011
zfs:0:zil:zil_commit_waiter_count                  31255
zfs:0:vdev_sync:vdev_sync_write_bytes              98342199296

Що це означає: Зростання лічильників commit і waiter вказує на інтенсивну синхронну активність. Якщо waiters накопичуються, застосунки блокуються на комітах логів.

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

Завдання 9: Підтвердіть, чи scrub, resilver або відновлення пристрою споживають IO

cr0x@server:~$ zpool status tank | sed -n '1,25p'
  pool: tank
 state: ONLINE
  scan: scrub repaired 0B in 0 days 03:22:11 with 0 errors on Sun Dec 22 03:22:11 2025
config:

        NAME        STATE     READ WRITE CKSUM
        tank        ONLINE       0     0     0
          raidz2-0  ONLINE       0     0     0
            sda     ONLINE       0     0     0
            sdb     ONLINE       0     0     0
            sdc     ONLINE       0     0     0

Що це означає: Чистий статус пулу не означає «без фонового IO», але виключає очевидні завдання обслуговування.

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

Завдання 10: Перевірте черги IO і латентність на рівні блочного шару Linux

cr0x@server:~$ iostat -x 1 5
Linux 6.6.0 (server) 	12/25/2025 	_x86_64_	(32 CPU)

Device            r/s     w/s   rMB/s   wMB/s avgrq-sz avgqu-sz await r_await w_await  svctm  %util
sda              25.0  1200.0     1.6    49.0    86.0     9.2  62.0   18.0   63.0   0.8   98.0
sdb              24.0  1180.0     1.5    48.3    86.0     8.9  60.0   17.5   61.0   0.8   97.0
sdc              23.0  1190.0     1.4    48.6    86.0    28.1  220.0  21.0  225.0   0.8   99.0

Що це означає: avgqu-sz і await показують накопичення черги. %util близько 100% вказує на насичення. Одиничний диск з великою чергою і await — джерело ваших стрибків латентності.

Рішення: Якщо блочний шар показує того самого проблемного диска, що й ZFS, це не проблема налаштування ZFS. Беріть SMART, дивіться логи контролера і готуйте заміну.

Завдання 11: Перевірте здоров’я диска і лічильники типу «диск вам бреше»

cr0x@server:~$ smartctl -a /dev/sdc | egrep -i 'Reallocated|Pending|Uncorrect|CRC|Power_On_Hours|Temperature|Error'
Power_On_Hours          38122
Temperature_Celsius     44
Reallocated_Sector_Ct   0
Current_Pending_Sector  8
Offline_Uncorrectable   2
UDMA_CRC_Error_Count    19

Що це означає: Pending/uncorrectable сектори можуть викликати довгі внутрішні повтори. CRC-помилки часто означають проблеми кабелю/бэкплейну, що дають переривчасті піки латентності замість чистих відмов.

Рішення: Pending сектори і uncorrectable — ознака «замінити незабаром»; CRC-помилки — «поправити шлях». Будь-яке з цього може пояснити періодичні затримки 200ms–2s.

Завдання 12: Перевірте властивості dataset, що тихо викликають додатковий IO

cr0x@server:~$ zfs get -r atime,compression,xattr,acltype,logbias tank | head -n 20
NAME      PROPERTY     VALUE     SOURCE
tank      atime        off       local
tank      compression  lz4       local
tank      xattr        sa        local
tank      acltype      posixacl  local
tank      logbias      latency   default
tank/db   atime        off       inherited from tank
tank/db   compression  lz4       inherited from tank
tank/db   xattr        sa        inherited from tank
tank/db   acltype      posixacl  inherited from tank
tank/db   logbias      latency   local

Що це означає: atime=on на читано-важких навантаженнях генерує записи при читанні — особливий різновид самопідриву. Компресія може зменшувати IO, але збільшувати затримки CPU. xattr=sa часто допомагає навантаженням з великою кількістю метаданих.

Рішення: Якщо бачите atime=on на гарячих dataset і воно не потрібне — вимкніть. Якщо компресія сильна і піки CPU збігаються з піками латентності, розгляньте зміну компресії або додавання CPU.

Завдання 13: Перевірте, чи снапшоти і видалення не створюють патологічної роботи з метаданими

cr0x@server:~$ zfs list -t snapshot -o name,used,creation -s creation | tail -n 5
tank/db@auto-2025-12-25-0100   2.1G  Mon Dec 25 01:00 2025
tank/db@auto-2025-12-25-0200   2.2G  Mon Dec 25 02:00 2025
tank/db@auto-2025-12-25-0300   2.2G  Mon Dec 25 03:00 2025
tank/db@auto-2025-12-25-0400   2.3G  Mon Dec 25 04:00 2025
tank/db@auto-2025-12-25-0500   2.4G  Mon Dec 25 05:00 2025

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

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

Завдання 14: Перевірте ashift і вирівнювання секторів (налаштування «на все життя»)

cr0x@server:~$ zdb -C tank | egrep 'ashift|path' | head -n 20
            path: '/dev/disk/by-id/ata-ST12000NM0008-2H2161_ZHZ12345'
            ashift: 12
            path: '/dev/disk/by-id/ata-ST12000NM0008-2H2161_ZHZ23456'
            ashift: 12

Що це означає: ashift=12 означає 4K сектори; ashift=9 — 512B. Встановлення ashift занадто низького на 4K-дисках може створити посилення записів і неприємну хвостову латентність.

Рішення: Якщо ashift неправильний, ви не зможете «виправити це» на місці. Плануйте перебудову/міграцію. Внесіть це в постмортем як «ми так більше не робитимемо».

Завдання 15: Знайдіть сигнали throttling і тиск TXG (Linux OpenZFS)

cr0x@server:~$ cat /proc/spl/kstat/zfs/txgs
txg                            birth                    state                    ndirty
1064217                        1703510152               open                     2147483648
1064216                        1703510150               quiescing                1987654321
1064215                        1703510148               syncing                  1876543210

Що це означає: Кілька TXG у стані quiescing/syncing з дуже великими bрудними байтами свідчать, що система бореться зі скидом. Це може проявлятися як затримки запису та іноді зупинки, коли ZFS застосовує зворотний тиск.

Рішення: Якщо TXG застрягли у syncing, зменшіть швидкість забруднення (обмеження застосунків, налаштування пакувань), підвищте продуктивність vdev або зменшіть фонові завдання. Не збільшуйте ліміти брудних даних в надії, що це вирішить проблему.

Завдання 16: Перевірте стан і завантаження special vdev (метадані на SSD)

cr0x@server:~$ zpool status -v tank | sed -n '1,80p'
  pool: tank
 state: ONLINE
config:

        NAME           STATE     READ WRITE CKSUM
        tank           ONLINE       0     0     0
          raidz2-0     ONLINE       0     0     0
            sda        ONLINE       0     0     0
            sdb        ONLINE       0     0     0
            sdc        ONLINE       0     0     0
        special
          mirror-1     ONLINE       0     0     0
            nvme1n1    ONLINE       0     0     0
            nvme2n1    ONLINE       0     0     0

Що це означає: Special vdev тримає метадані (і за потреби малі блоки). Якщо він хворий або замалий, операції з метаданими можуть мати піки навіть тоді, коли основні дані в нормі. Також: якщо special vdev відмовляє і ви втрачаєте надлишковість, ризик пулу різко зростає.

Рішення: Якщо є special vdev, ставтесь до нього як до Tier‑0 інфраструктури. Моніторьте його, як моніторите WAL бази даних. Якщо він замалий або повільний — виправте це перед тим, як шукати привидів.

Завдання 17: Корелюйте поведінку синхронізацій застосунку зі спайками сховища

cr0x@server:~$ pidstat -d 1 5 | egrep 'postgres|mysqld|qemu|java' || true
14:28:11      UID       PID   kB_rd/s   kB_wr/s kB_ccwr/s iodelay  Command
14:28:12      113      2218      0.00  402112.00  1024.00     312  postgres
14:28:13      113      2218      0.00  398740.00   980.00     411  postgres

Що це означає: Зростання iodelay показує, що процес чекає на IO. Якщо ваш процес БД — той, що блокується під час спайків, перестаньте блатити балансувачем навантаження.

Рішення: Якщо один процес домінує в IO wait, аналізуйте його патерн записів (чекпоїнти, fsync-шторми, завдання бекапу). Виправлення може бути на боці застосунку, а не ZFS.

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

Міні-історія 1: Інцидент через неправильне припущення

Вони перенесли транзакційний сервіс на новий кластер зі сховищем на ZFS. Огляд архітектури пройшов гладко. «Ми тепер на SSD», — сказав хтось, і це сприйняли як універсальний розв’язувач ризиків продуктивності.

Перший тиждень був тихим. Потім настав кінець місяця. Сервіс почав показувати зависання записів 1–3 секунди. Не постійно — достатньо, щоб викликати повтори, підсилити навантаження і зробити решту системи хиткою. Керівник інциденту пройшов звичну діагностику: CPU в нормі, мережа в нормі, «ZFS спайки».

Неправильне припущення було тонким: наявність NVMe не дорівнює «швидкі синхронні записи». У пулі не було виділеного SLOG, а основний vdev був RAIDZ. Навантаження часто виконувало fsync. Під час сплесків пул мусив вести синхронне журналювання на ті самі пристрої, що і звичайні записи та паритетна робота.

Коли вони поглянули на total_wait проти disk_wait, все стало очевидним: disk_wait злітав під час sync-сплесків. Вони додали дзеркальний, захищений від втрати живлення SLOG і перемістили тільки dataset із важкими sync-записами на нього (інші datasets лишились стандартними). Піки латентності не зникли зовсім; вони впали до рівня шуму, де графіки моніторингу губляться.

Урок, що закріпився: «SSD» — це не гарантія продуктивності. Це середовище. Архітектура все одно ваша проблема.

Міні-історія 2: Оптимізація, що відкотилась

Інша команда боролась із випадковими пиками читань на завантаженому файловому сервері. Хтось запропонував вимкнути компресію «щоб зменшити навантаження на CPU». Звучало розумно: менше CPU, більше швидкість. Вони вимкнули compression=off на найгарячішому dataset.

За кілька годин p99 погіршився. Не трохи — суттєво. CPU дійсно впав трохи, але дискова пропускна здатність зросла, як і глибина черги. Пул почав обслуговувати більше фізичних IO для того ж логічного навантаження, і саме ці додаткові IO постраждали в хвостовій латентності.

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

Вони повернули компресію до lz4, виправили політику пам’яті контейнерів і встановили пріоритет: «Оптимізуємо p99, а не порожні метрики CPU». Компресія не була лиходієм; безконтрольний тиск пам’яті був.

Жарт #2: Вимкнути компресію, щоб зменшити латентність — це як зняти ремінь безпеки, щоб покращити розгін автомобіля: технічно менше обмежень, практично — поганий день.

Міні-історія 3: Нудна, але правильна практика, що врятувала ситуацію

Велике підприємство мало змішані навантаження: VM, файлові шари та кілька баз даних, які всі удавали, що не «продукційні», доки не стали критичними. У них була політика: щотижневі вікна scrub, щомісячні перевірки SMART і негайне розслідування будь-якого зростання CRC-помилок. Це не було гламурно. Це було також причиною, чому їх найгірший інцидент ніколи не стався.

Одного четверга затримки почали стрибати короткими хвилями: 200ms, потім нормально, потім 500ms, потім нормально. Графіки були нестерпні. Статистика ZFS не кричала «пул помирає». Жоден диск офіційно не відмовив. Але користувачі помічали.

Онкалл запустив zpool iostat -v -l і побачив, що один диск час від часу підскакує до величезного disk_wait. SMART показав зростаючий UDMA_CRC_Error_Count. Це не «замінити диск», це «полагодити шлях». Команда переустановила диск, замінила кабель SAS і продовжила роботу.

Тиждень потому на іншому шасі з’явилася схожа хвиля. Та сама процедура, та сама фіксація, без драм. Нудна практика — ставити CRC як першу ознаку — запобігла повільному resilver-інциденту, який міг би розвалити продуктивність на дні.

Урок: найкращий стрибок латентності — той, якого ви ніколи не мали, бо вірили своїй телеметрії.

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

1) «Кожні 5–10 секунд записи зупиняються»

Симптом: ритмічні хвилі затримки запису; застосунки таймаутять коміт; графіки нагадують серцебиття.

Корінь: Тиск TXG commit. Брудні дані накопичуються швидше, ніж пул може скидати, тому ZFS застосовує зворотний тиск, і ви відчуваєте це як періодичні зупинки.

Виправлення: Зменшіть джерела імпульсних записів (пакетні роботи, чекпоїнти), додайте продуктивність vdev, уникайте RAIDZ для важких випадкових записів і тримайте scrub/resilver поза піковими годинами.

2) «Тільки синхронні записи повільні; async в порядку»

Симптом: читання в нормі; буферизовані записи в нормі; fsync-важкі застосунки страждають; NFS з export sync відчувається жахливо.

Корінь: повільний шлях ZIL — або нема SLOG, або SLOG з жахливою хвостовою латентністю, або примусова настройка (sync=always).

Виправлення: Додайте правильний дзеркальний SLOG (захищений від втрати живлення), встановіть sync=standard, якщо справді не потрібне always, і перевірте, чи застосунок не робить забагато fsync.

3) «Випадкове читання p99 погіршилося після додавання орендарів»

Симптом: середнє в нормі; p99 ні; спайки корелюють з іншими роботами.

Корінь: витіснення ARC та пропуски кешу через конкуренцію за пам’ять (контейнери/VM), плюс IO-конкуренція на тих самих vdev.

Виправлення: Резервуйте RAM, встановіть розумні ліміти ARC, ізолюйте шумні навантаження на окремі пули/vdev або додайте швидший носій для метаданих/малих IO.

4) «Піки латентності з’явились після увімкнення шифрування/компресії»

Симптом: CPU стрибає під час піків; диски не завантажені повністю.

Корінь: CPU-bound конвеєр: компресія, контрольні суми або шифрування вивантажують роботу на CPU. Якщо планування CPU підвисає, завершення IO виглядає «повільним».

Виправлення: Профілюйте CPU, прикрутіть завдання, оновіть CPU або налаштуйте рівень компресії. Не вимикайте функції цілісності наосліп; спочатку доведіть, що CPU — вузьке місце.

5) «Все добре, доки не почнеться scrub/resilver, тоді користувачі кричать»

Симптом: передбачувані обриви продуктивності під час обслуговування.

Корінь: недостатній запас. IO обслуговування конкурує з продакшн-IO; на HDD RAIDZ штраф особливо різкий.

Виправлення: Плануйте scrubs, налаштовуйте пріоритети, якщо платформа дозволяє, і — так, купіть достатньо дисків, щоб мати запас.

6) «Одна VM викликає стрибки затримки для всіх»

Симптом: поведінка «шумного сусіда»; сплески sync-записів або малих випадкових IO домінують.

Корінь: змушене sync-навантаження (журнали, бази даних), що ділиться пулом із чутливими читаннями; або невідповідність volblocksize zvol, що створює посилення записів.

Виправлення: Ізолюйте зберігання тієї VM, налаштуйте zvol правильно, забезпечте SLOG, якщо потрібен sync, або застосуйте QoS вище по стеку, якщо є така можливість.

7) «Піки зникають після перезавантаження… потім повертаються»

Симптом: перезавантаження тимчасово лікує симптоми.

Корінь: «теплий» ARC ховає проблеми з диском, поки кеш не охолоне; або довготривала фрагментація/хрон перезаписів повертається; або апаратні повтори знову накопичуються.

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

Суворіший чекліст: ізолюйте шар вузького місця

Крок 1: Класифікуйте стрибок за типом IO

  • Піки читання: шукайте пропуски ARC, латентність метаданих, один повільний диск або насичення special vdev.
  • Піки запису: визначте sync vs async. Для async: TXG, насичення vdev, RAIDZ-математика. Для sync: шлях ZIL/SLOG.
  • Піки метаданих: багато створень/видалень файлів, снапшотів, малі файли, операції з каталогами. Special vdev допомагає; HDD RAIDZ це ненавидить.

Крок 2: Вирішіть, чи ви насичені або маєте джиттер

  • Насиченість: %util близько 100%, довгі черги, високий disk_wait. Виправлення — ємність/продуктивність: більше vdev, швидший носій, менше конкурентних робіт.
  • Джиттер: середня завантаженість помірна, але p99 жахливий. Часто прошивкові повтори, CRC/кабель, SMR поведінка, GC на споживчих SSD/NVMe або хвостова латентність логів.

Крок 3: Перевірте, чи топологія відповідає навантаженню

  • Важкі випадкові записи і синхронні навантаження віддають перевагу дзеркалам (або смугам дзеркал) для латентності.
  • RAIDZ може бути відмінним для послідовної пропускної здатності і економії ємності, але не є спеціалістом низьколатентних випадкових записів.
  • Special vdev може перетворити продуктивність з метаданими, але він тепер частина історії виживання пулу. Дзеркальте його.

Крок 4: Спочатку робіть зміни, які можна відкатити

  • Переплануйте scrubs і бекапи.
  • Налаштуйте властивості dataset, такі як atime, logbias і primarycache, коли це виправдано.
  • Лише після наявності доказів: додайте SLOG, додайте vdev, мігруйте навантаження, перебудуйте пул для правильного ashift.

Шаблони стрибків латентності і що вони зазвичай означають

Шаблон: «Короткі спайки, один диск завжди гірший»

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

Шаблон: «Спайки тільки під час fsync-важких подій»

Класична історія ZIL/SLOG. Також проявляється під NFS із синхронною семантикою, коміт-шторами баз даних або файловими системами гостьових VM з агресивними бар’єрами. Якщо вам потрібна надійність, потрібне низьколатентне пристрій журналу.

Шаблон: «Спайки коли пам’ять натиснута»

Зменшення ARC + пропуски збільшують фізичний IO. Якщо ви запускаєте ZFS на вузлі з купою контейнерів, явно визначайте бюджет пам’яті. «Буде нормально» — не стратегія пам’яті.

Шаблон: «Спайки після зростання ретенції снапшотів»

Довгі ланцюги снапшотів не злі, але можуть збільшити витрати на видалення/перезаписи і підвищити фрагментацію. Латентність страждає першою; пропускна здатність здається нормальною, поки не стане ні.

Шаблон: «Спайки після додавання special vdev»

Special vdev допомагає, коли він швидкий і не насичений. Якщо він замалий, він стає «гарячою плямою». Якщо він незахищений (не робіть так), стає єдиною точкою смерті пулу.

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

1) Чи нормальні стрибки латентності ZFS через TXG commits?

Певна періодичність може бути нормальною при стійкому навантаженні запису, але великі p99-піки — це не функція. Якщо TXG викликає видимі користувачеві паузи, ви перевантажуєте пул або боретесь із вузьким місцем sync/log.

2) Чи варто встановлювати sync=disabled, щоб виправити латентність?

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

3) Чи завжди мені потрібен SLOG?

Ні. Якщо ваше навантаження рідко робить sync-запити (або ви терпимі до латентності), можете жити без нього. Якщо ви запускаєте бази даних, зберігання VM або синхронний NFS і піклуєтесь про p99, якісний SLOG часто різниця між спокоєм і хаосом.

4) Чому один повільний диск шкодить всьому пулу так сильно?

Тому що vdev виконують координовані IO. У RAIDZ паритетна координація змушує найповільнішого учасника задавати темп. У дзеркалах записи йдуть на обидві сторони. Хвостова латентність домінує через найгіршого учасника.

5) Наскільки повний — «занадто повний» для пулу ZFS?

Залежить від навантаження і типу vdev, але понад ~80–85% варто очікувати ефектів виділення і фрагментації — особливо на HDD RAIDZ. Якщо латентність критична, залишайте запас, як насправді маєте намір.

6) Чи корисна компресія lz4 для латентності?

Часто так. Вона зменшує фізичний IO і може покращити p99, якщо ви були I/O-bound. Вона може погіршити ситуацію, якщо ви опинитесь CPU-bound (слабкі ядра, додаткове шифрування). Виміряйте CPU і IO перед рішенням.

7) Чи можуть снапшоти викликати стрибки латентності?

Опосередковано — так. Часті снапшоти плюс великий churn можуть збільшити фрагментацію і навантаження на метадані. Видалення великих наборів снапшотів також може створити фонову активність, яку відчують користувачі.

8) Чи завжди special vdev покращує латентність?

Вони покращують метадані і малі блоки, коли правильно розміровані і дзеркалені. Але вони додають новий клас вузького місця: насичення special vdev. Ставтесь до них як до Tier‑0 і моніторте відповідно.

9) Чи RAIDZ завжди гірший за дзеркала для латентності?

Для випадкових записів із низькою латентністю дзеркала зазвичай виграють. RAIDZ може бути відмінний для послідовних навантажень і ефективності ємності. Обирайте за шаблоном IO, а не за ідеологією.

10) Чому іноді піки латентності виглядають як мережеві проблеми?

Бо застосунок відчуває «час очікування» і не розрізняє, чи це диск, планування CPU, блокування чи втрата пакетів. Саме тому починають з розділення шарів: vmstat/iostat/zpool iostat, а потім мережа.

Висновок: подальші кроки, що реально зменшують стрибки

Якщо ви хочете менше стрибків латентності ZFS — не починайте з налаштувань. Почніть з класифікації та доказів.

  1. Інструментуйте і збирайте: тримайте короткий rolling capture zpool iostat -l, iostat -x і статистики пам’яті під час пікових годин. Піки, які не зафіксовані, перетворюються на фольклор.
  2. Виправляйте очевидний апаратний джиттер: аномальні диски, CRC-помилки і ненадійні шляхи. Заміняйте або ремонтуйте спочатку; налаштовуйте потім.
  3. Поважайте синхронні семантики: якщо навантаження потребує sync, дайте йому реальний SLOG і провалідуйте хвостову латентність. Якщо не потребує — не змушуйте sync «щоб бути в безпеці».
  4. Тримайте запас: запас по ємності та по продуктивності. Scrubs, resilvers та бекапи — не опціональні; плануйте їх.
  5. Вирівняйте топологію з навантаженням: дзеркала для чутливих до латентності випадкових записів, RAIDZ для ємності/пропускної здатності, special vdev для метаданих якщо можете ним відповідально керувати.
  6. Робіть одну зміну за раз: і вимірюйте. Якщо не можете сказати, чи вона допомогла — значить не допомогла, принаймні надійно.

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

← Попередня
Швидкість відновлення MariaDB та PostgreSQL: як отримати RTO менше 15 хвилин
Наступна →
Debian 13 — «Не вдалося розв’язати ім’я хоста»: DNS/проксі/IPv6 — найшвидший шлях тріажу

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