У вашому пулі на папері багато «пропускної здатності». Але база даних зависає на крихітних оновленнях, хост VM повідомляє про спайки затримки збереження, а p99 вашого застосунку змінюється з «нормально» на «чому займається генеральний директор?» Це класична пастка ZFS для дрібних випадкових записів: навантаження обмежене IOPS і затримкою, тоді як ваша архітектура оптимізована під пропускну здатність.
Коли люди кажуть «ZFS повільний», вони зазвичай мають на увазі «я побудував парні vdev-и і потім попросив їх поводитись як дзеркала при дрібних випадкових записах». ZFS вас не зрадив. Фізика — от винуватець. А ще підключилась арифметика парності і зробила колективний проєкт.
Що насправді означають «дрібні випадкові записи» в термінах ZFS
Дрібні випадкові записи — це навантаження, яке карає архітектури, побудовані для послідовної пропускної здатності. Думайте про 4K–32K записи, розкидані по великому робочому набору, зазвичай з конкуренцією. Сюди потрапляють бази даних, образи VM, шарові файлові системи контейнерів, черги пошти та дерева файлів з великою кількістю метаданих.
Для ZFS «дрібні» та «випадкові» — це не лише характеристика застосунку. Це також характеристика макету та транзакцій. ZFS — copy-on-write (CoW). Це означає, що перезапис існуючого блоку — не оновлення на місці; це «виділити нові блоки, записати їх, потім оновити метадані, щоб вказувати на них». Отже фактичний I/O часто більше, ніж «один 8K запис».
Додайте парні vdev-и (RAIDZ1/2/3). Кожен запис може вимагати зачеплення кількох дисків плюс обчислення парності, інколи з шаблоном read-modify-write, якщо запис не вирівняний на межі повної смуги. Тут IOPS і затримка потрапляють під суд.
Дві дефініції, які треба розрізняти
- IOPS-bound: пропускна здатність низька, бо кожен I/O малий і кожен коштує вам затримки. Більше пропускної здатності не допоможе.
- Latency-bound: ваш рівень черги зростає, бо кожна операція займає занадто багато часу для завершення; застосунки таймаутяться, логи показують спайки, а «диск зайнятий» вводить в оману.
Дзеркала та парні масиви можуть обидва пропонувати багато сирої ємності й прийнятну послідовну пропускну здатність. Тільки один з них зазвичай дає передбачувану затримку для дрібних випадкових записів без героїчної тонкої настройки: дзеркала.
Дзеркала проти парності в одному реченні (і довше, що вам справді потрібно)
В одному реченні: Дзеркала зазвичай випереджають парні vdev-и на дрібних випадкових записах, бо запис у дзеркалі — це «записати в два місця», тоді як парність часто означає «прочитати деякі речі, порахувати парність, записати кілька речей», з більшою кількістю I/O операцій і очікування.
Довше, але потрібніше: Парні vdev-и ZFS добре працюють, коли записи великі, вирівняні й потокові, але сильно деградують, коли записи малі, фрагментовані або синхронні, бо вони підсилюють I/O, збільшують затримку на операцію і зменшують гнучкість планування на рівні vdev.
Шлях запису в парності: куди йдуть ваші IOPS, щоб швидко вмерти
Парність (RAIDZ) чудова, коли вам потрібна ефективність ємності та толерантність до відмов. Вона не чудова, коли вам потрібна низька затримка випадкових записів. Причина не містична; це арифметика плюс механіка.
Парні записи й проблема «повної смуги»
У RAIDZ vdev дані розподілені по дисках разом з парністю. Повний запис смуги означає, що ви записуєте повний набір колонок даних плюс колонки парності для смуги. Якщо ви можете стабільно виконувати повні смуги записів, парність досить ефективна: не потрібно читати старі дані для перерахунку парності; у вас вже є всі нові дані.
Дрібні випадкові записи зазвичай не є повними смугами. Вони часткові за смугою і часто нерівномірно вирівняні відносно recordsize vdev, розміру сектору (ashift) і геометрії RAIDZ. Коли ви робите оновлення часткової смуги, парність може вимагати read-modify-write:
- Прочитати старі блоки даних, які є частиною смуги (або парності), щоб обчислити зміну
- Обчислити нову парність
- Записати нові блоки даних і нові блоки парності
Це означає, що маленький логічний запис може перетворитися на кілька фізичних I/O по кількох дисках. Кожен диск має власну затримку. Операція завершується, коли завершується найповільніший потрібний I/O.
Чому це болючіше на HDD
На HDD випадкові IOPS обмежені шуком і ротаційною затримкою. Якщо операція парності розкидається на кілька дисків, ви множите шанс натрапити на повільний пошук. Дзеркала також розкидають записи (два диски), але парність може задіяти більше дисків плюс читання, і зазвичай має гіршу «хвостову» затримку під навантаженням.
Чому це все одно важливо для SSD
SSD зменшують біль від пошуку, але не усувають його. SSD мають внутрішнє управління стиранням, збір сміття та write amplification. Підсилені й розкидані записи парності можуть збільшити внутрішній write amplification та спричинити довгі хвостові затримки під час GC. Дзеркала пишуть більше байтів, ніж один диск, але парність може створювати більш фрагментовані та дрібні записи по більшій кількості пристроїв.
Черги: тихий вбивця
ZFS видає I/O на рівні vdev. RAIDZ vdev поводиться як один логічний vdev, підкріплений кількома дисками. При дрібних випадкових записах внутрішнє відображення vdev і математика парності обмежують, скільки незалежних операцій воно може виконати без конкуренції. Дзеркала, навпаки, можуть розподіляти читання між дисками і, маючи кілька mirror vdev-ів, розподіляти записи по vdev-ах з меншими накладними витратами координації.
Жарт №1: Парні масиви схожі на командні проєкти — усі повинні брати участь, тому нічого не завершується вчасно.
Шлях запису в дзеркалі: нудно, прямо, швидко
Запис у дзеркалі простий: ZFS виділяє блок, записує його на обидві сторони дзеркала та вважає операцію завершеною, коли запис є стійким згідно з вашою політикою sync. Немає парності для обчислення і зазвичай немає потреби читати існуючі блоки лише для оновлення парності.
Дзеркала масштабуються так, як насправді масштабується випадковий I/O
Практичний блок продуктивності в ZFS — це top-level vdev. IOPS пула приблизно дорівнюють сумі IOPS його top-level vdev-ів, особливо для випадкових навантажень. Десять mirror vdev-ів означає десять місць, куди ZFS може планувати незалежні записи (кожна дзеркальна пара працює сама по собі). Один великий RAIDZ vdev — це один домен планування.
Отже так: один 2-дисковий mirror не магічний. Магія в пулі, побудованому з кількох дзеркал.
Різниця у підсиленні записів, яку відчувають у продакшені
- Дзеркало: записати нові дані двічі (або тричі для 3-way mirror), плюс оновлення метаданих через CoW.
- RAIDZ: записати нові дані плюс парність, іноді спочатку прочитати старі дані/парність, плюс оновлення метаданих через CoW.
Коли навантаження домінують численні дрібні оновлення, система живе і вмирає через накладні витрати на операцію та хвостову затримку. Дзеркала утримують ці накладні нижчими і більш передбачуваними.
Специфічні множники ZFS: CoW, txg, метадані та sync
Навіть з дзеркалами ZFS може капризувати під дрібними випадковими записами, якщо ігнорувати нюанси ZFS. Ось що має значення при діагностиці дебатів «дзеркала проти парності» у реальному світі.
Copy-on-write: блок, який ви перезаписали, не той, який ви записали
ZFS записує нові блоки, а потім оновлює вказівники. Це означає, що навантаження на перезапис генерує додаткові записи метаданих. На парних vdev-ах ці метадані підпадають під ті самі штрафи часткових смуг що й дані.
Транзакційні групи (txg) і вибухи
ZFS групує записи в транзакційні групи і скидає їх періодично. Під важким навантаженням дрібних записів ви можете бачити патерн: система буферизує, потім скидає, і під час скиду з’являються спайки затримки. Дзеркала, як правило, скидають плавніше; парність може стати «лопатоподібною», якщо скидання перетворюється на шторм RMW операцій.
Синхронні записи: коли затримка стає політикою
Якщо ваше навантаження робить sync-записи (бази даних часто так роблять, хости VM часто так роблять), ZFS повинен зафіксувати дані на надійному накопичувачі перед підтвердженням запису — якщо ви явно не послабите це через sync=disabled (не робіть так, хіба що любите пояснювати втрату даних).
На HDD синхронні записи — жорстока річ без належного SLOG (окремого журналу), бо кожна sync-операція змушує завершити commit, що пов’язано з затримкою. Парність не допоможе. Дзеркала теж не творять чудес, але дзеркала зменшують додатковий наклад парності, тож ваш SLOG матиме реальний шанс бути вузьким місцем замість масиву.
Метадані та важіль «special vdev»
Метадані ZFS можуть домінувати у навантаженнях на дрібні файли та випадкові оновлення. special vdev (зазвичай mirrored SSD) може розміщувати метадані (і опційно дрібні блоки) на швидшому носії, різко зменшуючи затримку. Це допомагає і дзеркалам, і RAIDZ — але часто рятує RAIDZ від стану «непридатно до використання» під метаданим чатом.
Recordsize, volblocksize та фактичний розмір I/O
ZFS recordsize (для файлових систем) і volblocksize (для zvol) впливають на те, як ZFS ділить дані. Якщо ваша БД записує 8K сторінки у датасет з recordsize 128K, ZFS може робити більше роботи, ніж потрібно, особливо під фрагментацією. Але не зменшуйте recordsize скрізь бездумно; це може збільшити наклад на метадані й знизити послідовну ефективність. Налаштовуйте там, де навантаження це виправдовує.
Цікаві факти та історичний контекст (те, що люди забувають)
- Факт 1: ZFS з’явився у Sun Microsystems у середині 2000-х з end-to-end checksumming та CoW як базові ідеї — значно раніше, ніж «цілісність даних» стала маркетинговою галочкою.
- Факт 2: RAIDZ був спроєктований, щоб уникнути класичної RAID-5 «write hole», інтегруючи парність з транзакційною моделлю файлової системи.
- Факт 3: Керівництво «top-level vdev — це одиниця продуктивності» з’явилось раніше за сучасний NVMe; воно спочатку було ще важливіше на HDD, де домінувала затримка пошуку.
- Факт 4: Ранні розгортання ZFS часто будувалися для потокових навантажень (домашні директорії, медіа, бекапи). Пізніше бази даних і віртуалізація стали поширеними, і тоді біль парності став масовою проблемою.
- Факт 5: ZIL (ZFS Intent Log) існує навіть без окремого SLOG-пристрою. Без SLOG ZIL розміщується на дисках пулу, і sync-записи конкурують із рештою операцій.
- Факт 6: Сучасний OpenZFS ввів функції, як-от special vdev і persistent L2ARC, щоб вирішити саме проблему «метадані і дрібні випадкові I/O», з якою парні масиви борються найсильніше.
- Факт 7: 4K-native диски і SSD зробили вибір
ashiftпостійним підводним каменем; неправильний ashift може мовчки підрізати продуктивність дрібного I/O на весь термін життя vdev. - Факт 8: Розширення RAIDZ (додавання диска до існуючого RAIDZ vdev) історично було складним; експлуатаційно дзеркала часто обиралися простіше, бо їх легше розширювати без міграції.
- Факт 9: «Парність повільніша» — не закон. Для великих послідовних записів RAIDZ може бути дуже конкурентоспроможним — іноді швидшим за дзеркала через кращу використовувану пропускну здатність на диск.
Швидкий план діагностики
Ось у якому порядку я перевіряю речі, коли хтось каже «дрібні записи ZFS повільні». Мета — знайти вузьке місце за хвилини, а не виграти теоретичну суперечку.
По-перше: підтвердьте, що навантаження справді дрібне і випадкове
- Ми говоримо про DB WAL + data pages? Випадкові записи VM? Метадані-шторм?
- Скарга щодо затримки (p95/p99), чи пропускної здатності, чи обох?
По-друге: ідентифікуйте тип vdev і скільки у вас top-level vdev-ів
- Один великий RAIDZ vdev — дуже інша тварина, ніж вісім дзеркал.
- Для випадкового I/O «більше vdev-ів» зазвичай краще, ніж «ширші vdev-и».
По-третє: перевірте поведінку sync і стан SLOG
- Якщо sync-записи увімкнені і немає швидкого SLOG (або він неправильно налаштований), ви обмежені затримкою стабільного зберігання.
- Якщо SLOG існує, але він насичений, він стає вашим горлом пляшки.
По-четверте: перевірте заповненість пулу і фрагментацію
- Пули понад ~80% заповнення й сильно фрагментовані можуть перетворити дрібні записи на кошмар для аллокатора.
- Парні пули страждають більше, бо їм потрібне краще вирівнювання, щоб залишатися ефективними.
По-п’яте: перевірте ashift, recordsize/volblocksize та special vdev-и
- Неправильний ashift: постійний податок.
- Несумісні розміри блоків: уникнений податок.
- Метадані на повільних дисках: самонанесена плата за затримки.
По-шосте: перевірте, що вузьке місце — це сховище, а не CPU або пам’ять
- Парність може спалювати CPU на checksumming/compression/parity math під важким IOPS.
- Тиск ARC може спричинити додаткові читання та пропуски метаданих.
Практичні завдання: команди, виводи та як приймати рішення
Ось реальні перевірки, які ви можете виконати на типовій Linux/OpenZFS системі. Кожне завдання містить: команду, приклад виводу, що це значить і яке рішення прийняти.
Завдання 1: Визначити макет vdev і порахувати top-level vdev-и
cr0x@server:~$ sudo zpool status -v 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
sdd ONLINE 0 0 0
sde ONLINE 0 0 0
sdf ONLINE 0 0 0
logs
nvme0n1p2 ONLINE 0 0 0
special
mirror-1 ONLINE 0 0 0
nvme1n1p1 ONLINE 0 0 0
nvme2n1p1 ONLINE 0 0 0
errors: No known data errors
Що це означає: У вас є один top-level RAIDZ2 vdev (одна «полоса» продуктивності для випадкових записів), плюс SLOG і спеціальний vdev mirror.
Рішення: Якщо проблема — затримка випадкових записів, у вас два варіанти: (a) прийняти обмеження парності й пом’якшувати їх спеціальним vdev/SLOG/тонкою настройкою, або (b) переробити архітектуру в бік кількох mirror vdev-ів.
Завдання 2: Спостерігати IOPS та затримку по vdev під час інциденту
cr0x@server:~$ sudo zpool iostat -v tank 1 5
capacity operations bandwidth
pool alloc free read write read write
-------------------------- ----- ----- ----- ----- ----- -----
tank 6.20T 1.80T 120 980 3.2M 12.4M
raidz2-0 6.20T 1.80T 120 980 3.2M 12.4M
sda - - 20 165 520K 2.1M
sdb - - 18 170 510K 2.0M
sdc - - 22 160 540K 2.1M
sdd - - 21 162 530K 2.0M
sde - - 19 163 510K 2.1M
sdf - - 20 160 520K 2.1M
-------------------------- ----- ----- ----- ----- ----- -----
Що це означає: Записи розподілені, але пул виконує ~980 операцій запису в секунду загалом. На HDD це вже межа «обмеження пошуком».
Рішення: Якщо застосунку потрібно більше IOPS або нижчий p99, RAIDZ на HDD — ймовірно не те, що вам треба. Розгляньте дзеркала, SSD або переміщення «гарячого» набору на special vdev / SSD пул.
Завдання 3: Перевірити налаштування sync на рівні dataset/zvol
cr0x@server:~$ sudo zfs get -r sync tank
NAME PROPERTY VALUE SOURCE
tank sync standard default
tank/db sync standard inherited from tank
tank/vmstore sync standard inherited from tank
Що це означає: Sync-записи обробляються звично.
Рішення: Добре. Тепер переконайтесь, що у вас є швидкий, захищений від втрати живлення SLOG, якщо затримка sync висока. Не «виправляйте» це вимкненням sync, хіба що дані можна втратити.
Завдання 4: Підтвердити наявність SLOG і чи він справді використовується
cr0x@server:~$ sudo zpool iostat -v tank 1 3
capacity operations bandwidth
pool alloc free read write read write
-------------------------- ----- ----- ----- ----- ----- -----
tank 6.20T 1.80T 200 1100 4.1M 13.0M
raidz2-0 6.20T 1.80T 150 700 3.5M 10.8M
nvme0n1p2 - - 50 400 620K 2.2M
-------------------------- ----- ----- ----- ----- ----- -----
Що це означає: Лог-пристрій показує активність запису. Синхронний трафік потрапляє туди.
Рішення: Якщо лог-пристрій повільний або насичений, оновіть його. Якщо він швидкий, а затримка все одно висока, проблема в записах пулу (часто парність + фрагментація).
Завдання 5: Перевірити заповненість пулу (тиск алокації стає потворним)
cr0x@server:~$ zfs list -o name,used,avail,refer,mountpoint tank
NAME USED AVAIL REFER MOUNTPOINT
tank 6.20T 1.80T 96K /tank
Що це означає: Близько 77% використано. Ще не катастрофа.
Рішення: Якщо ви вище ~80–85% і бачите проблеми з випадковими записами, заплануйте проект розвантаження ємності. Дзеркала краще переносять «повноту» ніж RAIDZ, але жоден пул не любить голод алокатора.
Завдання 6: Перевірити фрагментацію (проксі для болю аллокатора)
cr0x@server:~$ sudo zpool list -o name,size,alloc,free,frag,health tank
NAME SIZE ALLOC FREE FRAG HEALTH
tank 8T 6.2T 1.8T 41% ONLINE
Що це означає: 41% фрагментації — не злочин, але під дрібними випадковими перезаписами вона може давати реальну затримку.
Рішення: Якщо frag високий і навантаження — випадкові записи, плануйте міграцію/реплікацію на свіжий пул або додайте vdev-и (дзеркала допоможуть більше), щоб дати аллокатору місце.
Завдання 7: Перевірити ashift (податок вирівнювання назавжди)
cr0x@server:~$ sudo zdb -C tank | egrep -i "ashift|vdev_tree" -n | head
45: vdev_tree:
78: ashift: 12
Що це означає: ashift=12 (4K сектори). Зазвичай коректно для сучасних дисків.
Рішення: Якщо ви бачите ashift=9 на 4K дисках, ви створили проблему продуктивності, яку не виправити тонкою настройкою. Виправляти треба rebuild/migrate.
Завдання 8: Перевірити recordsize (файлові системи) і volblocksize (zvol)
cr0x@server:~$ sudo zfs get recordsize,compression tank/db
NAME PROPERTY VALUE SOURCE
tank/db recordsize 128K local
tank/db compression lz4 local
Що це означає: Датасет БД використовує recordsize 128K. Якщо БД робить 8K сторінки, це може бути занадто великим залежно від патернів доступу.
Рішення: Розгляньте встановлення recordsize відповідно до розміру сторінки БД лише коли датасет дійсно містить файли БД і навантаження — випадкові перезаписи. Вимірюйте до/після.
Завдання 9: Перевірити чи compression допомагає або шкодить CPU
cr0x@server:~$ sudo zfs get compression,compressratio tank/db
NAME PROPERTY VALUE SOURCE
tank/db compression lz4 local
tank/db compressratio 1.68x -
Що це означає: Стиснення ефективне; менше байтів потрапляє на диск, що часто допомагає продуктивності випадкових записів.
Рішення: Тримайте lz4, якщо CPU не завантажений. Стиснення часто більше допомагає парним пулам, ніж дзеркалам, бо зменшує роботу парності на логічний запис.
Завдання 10: Перевірити чи затримка sync домінує через лог-пристрій
cr0x@server:~$ sudo iostat -x 1 3
Linux 6.8.0 (server) 12/26/2025 _x86_64_ (16 CPU)
Device r/s w/s r_await w_await aqu-sz %util
sda 20.0 165.0 12.1 35.8 3.2 92.0
sdb 18.0 170.0 11.7 36.5 3.3 93.5
sdc 22.0 160.0 12.8 34.2 3.1 90.1
nvme0n1 50.0 400.0 0.3 0.8 0.2 18.0
Що це означає: HDD показують високе w_await (~35 ms) і високу завантаженість; NVMe лог-пристрій у порядку. Горло — пул, а не SLOG.
Рішення: Тут парність на HDD для дрібних випадкових записів зазвичай програє. Перемістіть навантаження на дзеркала/SSD, додайте mirror vdev-и або розділіть навантаження між пулами.
Завдання 11: Перевірити тиск ARC (пропуски пам’яті можуть спричиняти додаткові I/O)
cr0x@server:~$ cat /proc/spl/kstat/zfs/arcstats | egrep "c_max|c |size|hits|misses" | head
c 4 51539607552
c_max 4 51539607552
size 4 49821667328
hits 4 182736455
misses 4 32736455
Що це означає: ARC близький до максимуму і має пропуски. Пропуски не обов’язково погані; важлива їхня частка і характер навантаження.
Рішення: Якщо ARC трясеться (спайки пропусків) і ви робите read-modify-write парні смуги, затримка може погіршитися. Додайте RAM, налаштуйте навантаження або ізолюйте датасети.
Завдання 12: Шукати повільний I/O і таймаути на рівні vdev у логах ядра
cr0x@server:~$ sudo dmesg | egrep -i "zfs|I/O error|blk_update_request|timeout" | tail -n 8
[12345.678901] zfs: vdev disk sdb: slow I/O, 35 sec, zio 0x0000000abcd123
[12347.112233] blk_update_request: I/O error, dev sdb, sector 123456789
[12347.445566] zfs: vdev disk sdb: repairing
Що це означає: Один диск повільний або повертає помилки. Парні vdev-и особливо чутливі до відстаючого диска; хвостова затримка всього vdev-а погіршується.
Рішення: Замініть або усуньте повільний диск. Не намагайтесь налагоджувати систему навколо обладнання, що виходить з ладу.
Завдання 13: Перевірити поведінку throttle записів ZFS (тиск txg)
cr0x@server:~$ cat /proc/spl/kstat/zfs/dmu_tx | head -n 15
dmu_tx statistics:
dmu_tx_assigned: 9876543
dmu_tx_delay: 12345
dmu_tx_error: 0
Що це означає: dmu_tx_delay зростає, коли ZFS мусить обмежувати записувачів, бо пул не може скинути дані достатньо швидко.
Рішення: Якщо delay зростає під час інциденту, ви обмежені сховищем на записах. Дзеркала з більшою кількістю vdev-ів можуть зменшити це; також допоможуть швидші носії або формування навантаження.
Завдання 14: Підтвердити, що special vdev справді приймає метадані/дрібні блоки
cr0x@server:~$ sudo zpool list -v tank
NAME SIZE ALLOC FREE CKPOINT EXPANDSZ FRAG CAP DEDUP HEALTH ALTROOT
tank 8T 6.2T 1.8T - - 41% 77% 1.00x ONLINE -
raidz2-0 8T 6.2T 1.8T - - 41% 77%
special 1T 220G 804G - - 9% 21%
Що це означає: Special vdev має алокацію, що свідчить про те, що метадані (і можливо дрібні блоки) розміщуються там.
Рішення: Якщо special показує майже нуль алокації, незважаючи на конфігурацію, перевірте властивості датасету, як-от special_small_blocks. Якщо метадані на HDD — ви платите зайву затримку.
Завдання 15: Підтвердити властивості датасету, що часто саботують затримку дрібних записів
cr0x@server:~$ sudo zfs get atime,logbias,primarycache,secondarycache tank/db
NAME PROPERTY VALUE SOURCE
tank/db atime on default
tank/db logbias latency default
tank/db primarycache all default
tank/db secondarycache all default
Що це означає: atime увімкнений (додаткові записи метаданих), logbias — latency (добре для sync-важких), кешування за умовчанням.
Рішення: Для DB/VM датасетів встановіть atime=off, якщо це не критично. Розгляньте logbias=throughput лише якщо розумієте свій профіль sync і маєте вагому причину.
Завдання 16: Визначити, чи ви використовуєте zvol-и для VM і який у них розмір блоку
cr0x@server:~$ sudo zfs list -t volume -o name,volsize,volblocksize,used tank/vmstore
NAME VOLSIZE VOLBLOCKSIZE USED
tank/vmstore 2T 8K 1.4T
Що це означає: zvol використовує 8K блоки, що може бути розумним для VM навантажень з дрібними випадковими записами.
Рішення: Якщо volblocksize занадто великий (наприклад, 128K) для образів VM з випадковими записами, ви створюєте write amplification. Змінити volblocksize можна лише перегенерувавши zvol — плануйте міграцію.
Три корпоративні міні-історії з бойових сховищ
Міні-історія 1: Інцидент, спричинений хибним припущенням
Компанія: середній SaaS, один основний кластер PostgreSQL, кілька read replica і фонова система, що любила дрібні оновлення. Вони мали новий нод з великим RAIDZ2 пулом, бо «нам потрібна ємність і відмовостійкість». У таблиці розрахунків послідовна пропускна здатність виглядала чудово.
Припущення: «IOPS — це IOPS, і RAIDZ2 має більше дисків, тож він буде швидшим». Ніхто цього прямо не говорив, але це жило в архітектурі.
День запуску пройшов нормально. Потім датасет почав фрагментуватись і робочий набір виріс. Симптом не був «низька пропускна здатність». Симптом був періодичні затримки 1–3 секунди на commit-ах. Потоки застосунку накопичувались, p99 різко зростав, і на-call отримував знайому схему оповіщень: CPU низький, мережа низька, диск зайнятий, але мало даних рухається.
Післямова виявила справжнього винуватця: синхронні дрібні записи потрапляли на парний vdev з HDD. ZIL був на пулі (без SLOG), тож кожен fsync затягував диски в бій. Під конкуренцією масив став генератором хвостової затримки.
Виправлення не було хитрим. Вони мігрували основну БД на пул з кількома mirror vdev-ами на SSD, залишили RAIDZ2 для бекапів та масивних даних, і раптом той самий код виглядав «оптимізованим». Архітектура зберігання була багом.
Міні-історія 2: Оптимізація, що обернулася проти
Інше місце, інша проблема: кластер віртуалізації з десятками змішаних навантажень. Хтось помітив, що парний пул «марнує» потенціал і запропонував просте рішення: вимкнути sync на VM датасеті і збільшити recordsize, щоб зменшити наклад. Це продавали як «безпечне, бо гіпервізор уже кешує записи».
Тиждень пахло медом. Графіки затримки вирівнялись. Люди вітали зміну. Потім хост впав — відключення живлення і некоректне завантаження. Кілька VM повернулись з помилками файлової системи. Одна БД не змогла коректно стартувати.
Неприємний урок: sync=disabled не означає «трохи менш безпечно». Це означає «ZFS дозволено брехати про довговічність». Для образів VM і баз даних ця брехня рано чи пізно стане інцидентом, і це завжди відбувається в найменш зручний час.
Вони повернули датасет на sync=standard, додали правильні mirrored SLOG-пристрої з захистом від втрати живлення, і налаштували систему по-бездушному: правильний volblocksize для zvol-ів, дзеркала для критичних шарів, і парність для ємнісних шарів.
Жарт №2: Вимкнути sync — як зняти пожежні датчики, бо вони пищать — тиша в домі, цікаве майбутнє.
Міні-історія 3: Нудна, але правильна практика, що врятувала день
Фінансова організація з суворими вимогами до аудиту використовувала ZFS для суміші файлових сервісів і транзакційної системи. Вони не були хитромудрі. Вони були дисципліновані. Кожна зміна пулу вимагала базової продуктивності і плану відкату. Щокварталу вони тестували відновлення та відмовостійкість. Щомісяця переглядали стан пулу й результати scrub.
Одного ранку парний пул почав показувати вищу затримку запису й інколи помилки контрольних сум. Нічого не «лежало», але команда сприйняла це як ранній індикатор, а не фон. Вони перевірили zpool status, побачили диск з наростаючими помилками і замінили його у контрольованому вікні.
Під час resilver вони обмежили пакетні задачі і переключили транзакційне навантаження на дзеркальний пул, який тримали спеціально для «гарячих» даних. Оскільки все було сплановано, бізнес майже не помітив.
Після інциденту у команди була така постмортем, яку люблять SRE: коротка, спокійна і здебільшого про те, що пройшло добре. Нудна практика — scrubs, базові лінії і сегрегація навантаження — була саме тим, що не дозволило інциденту стати заголовком.
Поширені помилки: симптоми → корінь проблеми → виправлення
Помилка 1: «RAIDZ має більше дисків, отже має більше IOPS»
Симптоми: p99 затримки запису, низький MB/s але високий %util на дисках, зависання commit-ів БД.
Корінь проблеми: Один RAIDZ vdev — одна смуга випадкового I/O; дрібні записи спричиняють часткові смуги і RMW шаблони.
Виправлення: Використовуйте кілька mirror vdev-ів для шарів з випадковими записами. Залишайте RAIDZ для ємності/потоку. Якщо застрягли з RAIDZ — додайте special vdev і впевніться, що sync/SLOG налаштовані правильно.
Помилка 2: Пул занадто заповнений для комфортної геометрії парності
Симптоми: Поступове падіння продуктивності, CPU алокатора, зростання dmu_tx затримок, підвищення фрагментації.
Корінь проблеми: Високе використання пулу зменшує вибір алокацій; фрагментація зростає, часткові смуги стають частішими.
Виправлення: Тримайте пули нижче ~80% для важких випадкових записів. Додайте ємність (краще нові vdev-и) або мігруйте на свіжий пул.
Помилка 3: Немає SLOG для sync-важких навантажень (або використання дешевої SSD як SLOG)
Симптоми: жахлива fsync затримка, зависання WAL БД, гості VM скаржаться на латентність flush.
Корінь проблеми: ZIL записи потрапляють на основний пул або на лог-пристрій з поганою затримкою чи без захисту від втрати енергії.
Виправлення: Додайте mirror SLOG з захистом від втрати живлення для sync-важливих навантажень. Перевіряйте zpool iostat -v і метрики затримки пристрою.
Помилка 4: Неправильний ashift
Симптоми: Хронічно погана продуктивність дрібного I/O незалежно від налаштувань; write amplification відчувається «таємничо».
Корінь проблеми: ashift занадто малий спричиняє read-modify-write на рівні пристрою через невирівняні сектори.
Виправлення: Перебудувати/перемістити vdev-и з правильним ashift. Ніяких чарівних sysctl, що це відкотить.
Помилка 5: Ставити recordsize як універсальний «ручний регулятор продуктивності»
Симптоми: Деякі навантаження покращуються, інші погіршуються; наклад на метадані зростає; бекапи сповільнюються.
Корінь проблеми: recordsize впливає на розташування на диску та поведінку метаданих; його зменшення всюди підвищує наклад.
Виправлення: Налаштовуйте на рівні датасету. Дані БД можуть хотіти 8K/16K; медіа-архіви — 1M. Міряйте на реальному навантаженні.
Помилка 6: Ігнорувати метадані як окреме навантаження
Симптоми: «Але ми записуємо трохи даних» при тому, що затримка жахлива; повільні операції з директоріями; повільні snapshot-операції VM.
Корінь проблеми: Метадані домінують; парні vdev-и погано обробляють оновлення метаданих під навантаженням.
Виправлення: Використовуйте special vdev (дзеркальні SSD) для метаданих і, можливо, дрібних блоків; тримайте його надлишковим; моніторьте його як production-дані (бо він ними й є).
Контрольні списки / покроковий план
Контрольний список дизайну: вибір дзеркал чи парності для нового пулу
- Якщо це бази даних, сховище VM або будь-що sync-важке: за замовчуванням обирайте кілька mirror vdev-ів.
- Якщо це бекапи, медіа, логи, дампи аналітики: RAIDZ підходить; оптимізуйте під ємність.
- Рахуйте vdev-и, а не диски: випадкові IOPS масштабуються з top-level vdev-ами. Плануйте достатню кількість mirror vdev-ів, щоб досягти цільових IOPS з запасом.
- Плануйте історію sync: або прийміть затримку на дисках пулу, або додайте відповідний mirrored SLOG. Не «вирішуйте» це через
sync=disabled. - Плануйте метадані: якщо навантаження метаданеное, закладайте special vdev mirror.
- Тримайте запас місця: проєктуйте так, щоб залишатись нижче ~80% використання, якщо важлива випадкова записуваність.
- Вибирайте ashift свідомо: передбачайте 4K сектори як мінімум; вибирайте ashift=12, якщо нема причин інакше.
- Визначайте розмір блоку на рівні датасету: встановлюйте recordsize/volblocksize відповідно до навантаження, а не настрою.
План міграції: переміщення з RAIDZ до дзеркал без драм
- Побудуйте новий пул (дзеркала, правильний ashift, SSD tiering за потреби) паралельно зі старим пулом.
- Встановіть властивості датасетів на новому пулі перед копіюванням (recordsize, compression, atime, sync, special_small_blocks).
- Використовуйте ZFS send/receive для файлових систем; для zvol-ів плануйте простої гостя або стратегію реплікації згідно з гіпервізором.
- Запустіть паралельний бенчмарк перед переключенням: виміряйте fsync затримку, випадкові write IOPS, p99.
- Переключайтесь з планом відкату: тримайте старий пул у режимі read-only на вікно; моніторьте помилки й продуктивність.
- Після переключення перевірте фрагментацію і рівень заповнення пулу; виправте наступне вузьке місце (часто мережа або CPU).
Операційний чекліст: як не допустити деградації продуктивності дрібних записів
- Моніторьте заповненість пулу і тренди фрагментації щомісяця.
- Періодично запускайте scrub; розглядайте помилки контрольних сум як термінові, не косметичні.
- Слідкуйте за затримкою, не лише за пропускною здатністю. p99 write latency — це правда.
- Тримайте прошивки SSD для SLOG/special vdev однаковими.
- Проводьте контрольовані навантажувальні тести після великих змін (ядро, версія ZFS, заміна дисків).
ЧАСТІ ПИТАННЯ
1) Чи RAIDZ завжди повільніший за дзеркала?
Ні. Для великих послідовних читань/записів і шарів, орієнтованих на ємність, RAIDZ може бути відмінним. Більше болю виникає саме на дрібних випадкових записах і sync-важких патернах.
2) Чому дзеркала «краще масштабуются» для випадкового I/O?
Тому що кожен top-level vdev — це одиниця планування. Багато mirror vdev-ів дають ZFS більше незалежних «смужок» для випадкових операцій. Один широкий RAIDZ vdev — все ще одна смуга.
3) Якщо я додам більше дисків до RAIDZ, чи отримаю більше IOPS?
Ви отримаєте більше агрегованої пропускної здатності і іноді кращу конкуренцію, але також розширите смуги і посилитесь координація парності. Для дрібних випадкових записів це рідко масштабується так, як вам треба.
4) Чи може швидкий SLOG зробити RAIDZ придатним для дрібних випадкових записів?
SLOG допомагає затримці sync-записів, прискорюючи commit ZIL. Він не усуває наклад парності для основних даних, які все одно потрапляють у пул під час flush txg.
5) Чи варто ставити sync=disabled для продуктивності?
Тільки для даних, які ви можете втратити без жалю. Для баз даних і образів VM це очікуваний інцидент при відключенні живлення. Краще використовуйте правильний SLOG і дзеркала.
6) Чи замінює special vdev потребу в дзеркалах?
Ні. Special vdev може радикально покращити метадані і дрібноблокову продуктивність, що робить RAIDZ менш жахливим при певних навантаженнях. Він не змінює арифметику парності для звичних блоків даних.
7) Чи підходить SSD RAIDZ для дрібних випадкових записів?
Краще, ніж HDD RAIDZ, але все одно часто поступається кільком дзеркалам за хвостовою затримкою, особливо під sync-важким або фрагментованим навантаженням. SSD маскують проблему, але не усувають її.
8) Які налаштування датасету найчастіше покращують поведінку при дрібних випадкових записах?
compression=lz4, atime=off для DB/VM датасетів, робочо-орієнтований recordsize/volblocksize, та адекватна стратегія sync/SLOG. Special vdev для метаданих — великий важіль.
9) Який найнадійніший спосіб довести, що парність — вузьке місце?
Корелюйте спайки затримки застосунку з zpool iostat -v, дисковими метриками iostat -x await/%util і індикаторами throttling ZFS як dmu_tx_delay. Якщо диски пулу зайняті з високим await і низьким MB/s — ви обмежені IOPS/затримкою.
10) Яку цитату варто, щоб SRE пам’ятали?
«Сподівання — не стратегія.»
— Генерал Гордон Р. Салліван
Наступні кроки, які ви можете виконати цього тижня
- Класифікуйте датасети: які з них чутливі до затримки (DB/VM), а які орієнтовані на пропускну здатність/ємність (бекапи, архіви).
- Запустіть швидкий план діагностики під час реального інциденту: захопіть
zpool iostat -vіiostat -x, коли користувачі скаржаться. - Виправте дешеві виграші:
atime=offтам, де доречно, підтвердьтеcompression=lz4, перевірте політику sync і стан SLOG. - Якщо у вас парність для «гарячих» робочих навантажень: вирішіть, чи ви (a) додасте mirror-based «гарячий пул», (b) додасте special vdev і SSD tiering, або (c) переробите на дзеркала цілком.
- Плануйте міграцію як операційне завдання, а не як домашнє хобі: базова лінія, реплікація, переключення з відкатом.
Якщо ваше навантаження — дрібні випадкові записи і бізнес дбає про затримку, дзеркала — не розкіш. Це розумчий вибір за замовчуванням. Парність — коли важливіша ефективність ємності, а не хвостова затримка. Обирайте свідомо, і ZFS виконуватиме свою роботу: зберігати ваші дані коректно, поки ви спите.