recordsize в ZFS: параметр, що визначає 80% продуктивності файлів

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

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

Якщо ви керуєте продакшн-системами — флотом ВМ, базами даних, NFS домашніми директоріями, резервними цілями — recordsize це ручка, до якої ви тягнетесь, коли продуктивність «таємничо хороша в деві», а потім стає «таємниче дорогою в проді». Ця стаття — польовий посібник: що насправді контролює recordsize, як він взаємодіє зі стисканням і кешуванням, і як вибирати значення, щоб не створити приховане write amplification або стрибки затримок.

Що таке recordsize насправді (і чого воно не є)

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

Важливі нюанси, які мають значення в продакшні:

  • Це максимум, а не мінімум. ZFS буде використовувати менші блоки, коли потрібно (невеликі файли, хвостові блоки, фрагментовані алокації або коли стискання робить блоки меншими).
  • Застосовується до блоків даних файлів. Це не те саме, що volblocksize (для zvols), і це безпосередньо не встановлює розміри метаданихних блоків.
  • Впливає на те, скільки даних потрібно прочитати/перезаписати при зміні частини файлу. Це оперативна важіль: випадкові малі записи в великий record можуть викликати шаблон read-modify-write на рівні блоку.
  • Не переписує існуючі блоки заднім числом. Зміна recordsize впливає на нові записи надалі. Старі блоки залишаються такими, якими вони були, поки не будуть перезаписані (через звичайну зміну даних або через операції перезапису/копіювання, які ви ініціюєте).

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

Чому цей один параметр домінує над продуктивністю файлів

Для багатьох навантажень «продуктивність файлу» зводиться до кількох вимірюваних поведінок:

  • Затримка для малих випадкових читань/записів (бази даних, образи VM, поштові скриньки)
  • Пропускна здатність для великих послідовних читань/записів (медіа, резервні копії, об’єктоподібні блоби)
  • Write amplification коли малі оновлення викликають перезапис великих блоків
  • Ефективність кешування в ARC/L2ARC: чи кешуєте ви в правильній градації?

recordsize зачіпає всі чотири пункти. Більші записи означають менше вказівників, менше I/O-операцій для послідовних сканувань і часто кращі коефіцієнти стиснення. Менші записи означають менше «побічної шкоди», коли змінюється невелика частина файлу, і вони можуть зменшити поведінку «читайте більше, ніж просили», коли програма робить випадкові читання.

З операційної точки зору: recordsize — один із небагатьох налаштувань ZFS, який (a) встановлюється на рівні датасету, (b) його легко змінити безпечним способом, і (c) дає миттєві, вимірювані відмінності. Поєднання рідкісне, і саме тому воно на практиці вирішує «80% продуктивності файлів» — особливо коли вузьким місцем є невідповідність I/O-патернів, а не чиста пропускна здатність дисків.

Факти та історичний контекст, що пояснюють сучасні значення за замовчуванням

Декілька конкретних контекстів допомагають пояснити, чому існують значення recordsize за замовчуванням і «народна мудрість»:

  1. ZFS успадкував світ, де були поширені сторінки по 4K та сектори дисків 4K. Багато I/O-шляхів ОС та баз даних природно працюють у одиницях 4K або 8K, що впливає на «розумні» розміри блоків для випадкового I/O.
  2. Класичне значення recordsize за замовчуванням було 128K. Це не магія; це компроміс, орієнтований на файлові системи загального призначення з великою кількістю послідовного I/O у змішаному навантаженні.
  3. Ранні масові диски часто «брехали» про сектори. Епоха 512e створила болісні проблеми вирівнювання; ashift у ZFS був контрзаходом «вирівняти до степеня двійки».
  4. Файлові системи з copy-on-write зробили часткові перезаписи дорогими. Традиційні файлові системи могли перезаписувати на місці; ZFS зазвичай пише нові блоки, що змінює вартість малих оновлень.
  5. Стискання, яке стало «достатньо дешевим», змінило пріоритети тюнінгу. Сучасні CPU зробили легке стиснення практичним, і великі записи часто стискаються краще (менше заголовків, більше надлишковості на блок).
  6. Спалах віртуалізації створив новий клас «файли, що поводяться як диски». QCOW2/VMDK/raw образи — це файли, але їхні моделі доступу виглядають як випадкові блочні пристрої — recordsize має відповідати цій реальності.
  7. SSD зробили малу затримку реальнішою, але й чутливішою. Коли носій швидкий, накладні витрати (контрольні суми, метадані, write amplification) стають більшою частиною загального часу.
  8. ARC зробив кешування більш стратегічним, ніж «більше RAM = швидше». Кешувати 128K шматки для випадкових 8K читань може бути марнотратством; кешувати 16K для послідовного потоку може бути надміром.

Як ZFS перетворює ваші записи в реальні дані на диску

Щоб відповідально налаштовувати recordsize, потрібно мати ментальну модель того, що ZFS робить з вашими байтами.

Recordsize і пастка «read-modify-write»

Припустимо, програма оновлює 8K у середині великого файлу. Якщо відповідний блок на диску (record) має 128K, ZFS не може просто перезаписати 8K «на місці», як то робили старі файлові системи. Зазвичай потрібно:

  1. Прочитати старий 128K блок (якщо він не в ARC)
  2. Змінити 8K частину в пам’яті
  3. Записати новий 128K блок в інше місце (copy-on-write)
  4. Оновити метадані, щоб вказувати на новий блок

Це спрощений погляд, але суть ясна: малі випадкові записи в великі записи створюють додатковий I/O і алокаційний шум. На HDD це означає Seek-болі. На SSD це — write amplification та джиттер затримок, особливо під синхронними записами або при високій конкуренції.

Чому більші записи можуть бути чудовими для послідовних навантажень

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

  • Менше вказівників і операцій з метаданими
  • Менше I/O-запитів для тієї ж кількості даних
  • Часто краща ефективність стиснення
  • Краща поведінка prefetch: ZFS може підвантажувати значущі шматки

На обертових дисках це різниця між стійкою пропускною здатністю і графіком, що нагадує сейсмограму. На SSD це різниця між насиченням пропускної здатності і насиченням CPU/IOPS-накладних витрат спочатку.

Градули ARC: кешування правильної «форми» даних

ARC кешує блоки ZFS. Якщо ваша програма робить 8K випадкові читання, але ваші блоки — 128K, кожен miss кеша підтягує 128K. Це «read amplification». Іноді це допомагає (просторова локальність), але для по-справжньому випадкових патернів це просто витрачає ARC і швидше витісняє корисні дані.

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

Стиснення і recordsize: друзі з межами

Стиснення виконується на рівні блоку. Великі блоки часто стискаються краще, бо надлишковість видніша в більшому вікні. Але стиснення також означає, що ваш фізичний розмір блоку може бути меншим за recordsize, що змінює форму I/O:

  • Для стиснених даних великий recordsize може бути «безкоштовним» виграшем: менше блоків, менше фізичного I/O.
  • Для нескомпресованих даних великий recordsize переважно змінює градацію I/O і вартість перезапису.
  • Для змішаних даних ви побачите мішанину фізичних розмірів; «максимум» все одно важливий для write amplification.

Recordsize vs volblocksize (не плутайте)

Датасети зберігають файли; zvols представляють блочні пристрої. Для zvols еквівалентний регулятор — volblocksize, який встановлюється під час створення і важко змінюється без перебудови zvol. Поширена продакшн-помилка — налаштовувати recordsize на датасеті, де в реальності зберігаються образи ВМ всередині zvol — або навпаки налаштовувати volblocksize для файлів. Це різні рівні.

Відображення навантажень на recordsize (з реальними числами)

Ось практичне відображення, яке я використовую, коли відповідаю за pager.

Загальні файлові шари (домашні директорії, змішані офісні файли)

Типовий доступ: невеликі документи, випадкові великі завантаження, багато дрібних операцій з метаданими. Значення за замовчуванням 128K часто підходить. Якщо у вас інтенсивний випадковий доступ у великих файлах (наприклад, величезні PST-подібні архіви), розгляньте зниження, але не оптимізуйте передчасно — спочатку виміряйте.

Звичний вибір: 128K. Іноді 64K, якщо багато малих випадкових I/O всередині великих файлів.

Бази даних (Postgres, MySQL/InnoDB) як файли в датасетах

Більшість баз даних роблять читання/записи 8K–16K (залежить від движка і конфігурації). Якщо ви зберігаєте файли бази даних на датасеті (не zvol), великий recordsize може спричинити read-modify-write і неефективність ARC. Recordsize ближче до розміру сторінки БД зазвичай кращий.

Звичний вибір: 16K для Postgres (8K сторінки, але 16K краще узгоджується з практикою ZFS); 16K для багатьох InnoDB налаштувань (часто 16K сторінки). Іноді 8K для дуже чутливих до затримки випадків, але слід слідкувати за накладними витратами.

Образи дисків ВМ як файли (qcow2, raw, vmdk) на датасетах

Класичний «файл, що поводиться як диск». Гостьова ОС робить 4K випадкові записи; гіпервізор пише в файл. Великий recordsize карає write amplification і фрагментацією. Потрібні менші блоки. Багато організацій зупиняються на 16K або 32K як компромісі; 8K підходить для дуже чутливих до IOPS середовищ, але збільшує накладні витрати на метадані.

Звичний вибір: 16K або 32K.

Резервні цілі, медіаархіви, архіви логів

Послідовне, потокове, апенд-важке навантаження, зазвичай без перезаписів на місці. Більше — краще: зменшує накладні витрати, збільшує пропускну здатність стрімінгу, покращує стиснення. Для сучасного OpenZFS 1M recordsize — легітимний вибір для таких датасетів, якщо навантаження дійсно є великим послідовним I/O.

Звичний вибір: 1M (або 512K, якщо хочете безпечнішу золоту середину).

Образи контейнерів і кеші збірки

Це може бути підступно: багато дрібних файлів і метаданих, але й великі шари. Налаштування recordsize самостійно не вирішить усіх проблем; також важливі спеціальні vdev для метаданих, стиснення і можливо atime=off. Для recordsize зазвичай підходить значення за замовчуванням; якщо навантаження домінує багато малих випадкових читань всередині великих бінарних шарів, розгляньте 64K, але вимірюйте.

Звичний вибір: 128K, іноді 64K.

Аналітичні файли (parquet, ORC, колонкові формати)

Їх часто читають великими шматками, але з можливістю пропуску. Якщо ваші запити сканують великі діапазони, більші записи допомагають. Якщо багато «читати трішки з багатьох місць», менші зменшать read amplification. Це дуже специфічно до навантаження; почніть з 1M для чистих сканів, потім корегуйте.

Звичний вибір: 512K–1M для скан-важких; 128K–256K для змішаних.

Жарт №2 (короткий, до теми): У сховищах «one size fits all» — як «один timeout для всіх» — вірно лише до першого розбору інциденту.

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

1) Інцидент через неправильне припущення: «Це файл, тож recordsize=1M буде швидше»

У них був кластер віртуалізації, що зберігав диски ВМ як raw-файли на датасеті ZFS. Хтось прочитав допис у блозі про те, що великий recordsize підвищує пропускну здатність для резервних копій, і вирішив, що датасет для ВМ має бути з recordsize=1M. Звучало логічно: більші блоки, менше I/O, більше швидкості.

Зміни розгорнули під час рутинного вікна обслуговування. Першим попередженням був не бенчмарк, а служба підтримки. «Windows ВМ відчуваються гальмівними». Потім моніторинг загорівся: зростання IO wait, черги, і дивна картина — читання були в порядку, а записи мали джиттер. Графіки гіпервізора показували сплески: багато дрібних записів раптом перетворювалися на значно більшу бекенд-активність запису.

Під час розслідування пул ZFS виконував багато read-modify-write. Навантаження було типовим для ВМ: дрібні випадкові записи, оновлення метаданих файлової системи в гості, журнали й періодичні синки. З 1M блоками кожне дрібне оновлення могло перезаписати величезний блок (або принаймні викликати алокаційні шаблони великих блоків). ARC hit rates не рятували: робочий набір був більше за RAM і випадковий характер робив кеш менш ефективним.

Вирішення не було героїчним. Вони створили новий датасет з recordsize=16K, мігрували образи ВМ копіюванням, що змусило перезапис блоків, і симптоми здебільшого зникли. Постмортем дав різкий урок: «файл» — це не опис навантаження. Якщо ваш «файл» — віртуальний диск, ставтесь до нього як до блочного I/O.

Подальший виграш був культурним: вони додали легкий крок огляду дизайну сховища для нових датасетів. Не бюрократія — просто десятихвилинний чекліст: патерн доступу, очікувані розміри I/O, поведінка sync і явний вибір recordsize.

2) Оптимізація, що повернулась проти них: зменшення recordsize скрізь заради зниження затримок

Інша організація мала інцидент із затримками на базі даних і зробила простий висновок: «менші блоки швидші». Це зрозуміло, якщо ви постраждали від write amplification. Тож вони стандартизували recordsize=8K на більшості датасетів: бази даних, домашні папки, сховище артефактів, резервні копії — все підряд.

Звіти за наступний місяць були дивними. Затримки в БД трохи покращилися, так. Але система резервного копіювання почала пропускати вікно. Конвеєр обробки медіа став довше читати і писати великі файли. Навантаження CPU на вузлах зберігання зросло, і не тому, що користувачі стали щасливішими. Це був наклад: більше блоків, більше метаданих, більше роботи по контрольних сумах, більше I/O-операцій для переміщення тих самих даних.

А потім настав справжній удар: їхній пристрій L2ARC здавався «зайнятим», але не покращував результати. Малий recordsize означав більше записів у кеш і тиск на метадані. Це збільшило наклад при управлінні робочим набором, і кеш став менш ефективним для стрімінгових навантажень, які раніше вигравали від великих записів і prefetch.

Виправлення полягало в тому, щоб перестати трактувати recordsize як універсальний регулятор. Вони розділили датасети по класам навантажень: 16K для файлів БД, 128K для загальних шарів, 1M для резервних копій і великих артефактів. Результат був не лише в продуктивності — а в передбачуваності. Системи, що стрімінгово передавали дані, повернули свою пропускну здатність; системи з випадковим I/O отримали менше ампліфікації; і CPU зберігання перестав прикидатися майнером криптовалют.

Урок: «універсальний» recordsize — універсальний компроміс, а компроміси — місце народження інцидентів.

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

Ось історія, якою ніхто не хизується, але саме вона дає деяким командами спокійний сон.

Ця компанія працювала на спільному пулі ZFS для кількох внутрішніх сервісів: флот PostgreSQL, NFS-сервіс домашніх директорій і репозиторій резервних копій. Раніше вони звикли створювати датасет для кожного класу навантаження з явними властивостями: recordsize, compression, atime, політика sync і правила mountpoint. Це було не складно. Просто послідовно.

Коли прийшов новий аналітичний сервіс — великі колонкові файли, переважно послідовні читання — команда зберігання не чіпала існуючі датасети. Вони створили новий датасет з recordsize=1M і відповідним стисненням, а потім підклали сервіс туди. Продуктивність була хорошою з першого дня, бо вони не змушували аналітичне навантаження жити на датасеті, налаштованому під домашні каталоги.

Через місяць регрес в аналітиці змусив систему робити більше випадкових читань. Затримки підросли. SRE на виклику швидко діагностував: підтвердив зміну I/O-патерну, перевірив розміри блоків, інспектував поведінку ARC. Вони помірно знизили recordsize (з 1M до 256K) у стейджингу, перевірили на реалістичних запитах, потім провели контрольовану міграцію, що перезаписала гарячі дані. Ніякої екстреної операції в проді. Ніякого «змінемо налаштування по всьому пулу і побачимо». День пройшов без дзвінків аварійної служби.

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

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

Наступні завдання написані так, як я б їх запускав на Linux-хості з OpenZFS. Підлаштуйте імена пулів/датасетів за потреби. Кожна команда включає, на що звернути увагу, щоб ви могли прийняти рішення, а не просто отримати вивід.

Завдання 1: Перелік датасетів і recordsize

cr0x@server:~$ zfs list -o name,used,avail,recordsize,compression,mountpoint
NAME                     USED  AVAIL  RECORDSIZE  COMPRESS  MOUNTPOINT
tank                     3.21T  7.80T     128K     lz4       /tank
tank/vm                  1.40T  7.80T      16K     lz4       /tank/vm
tank/db                  820G   7.80T      16K     lz4       /tank/db
tank/backups             900G   7.80T       1M     zstd      /tank/backups

Інтерпретація: перевірте, що датасети відповідають класам навантажень. Якщо ваш VM-датасет має 1M, ви, ймовірно, знайшли наступний тікет по продуктивності.

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

cr0x@server:~$ zfs get -o name,property,value,source recordsize tank/vm
NAME      PROPERTY    VALUE  SOURCE
tank/vm   recordsize  16K    local

Інтерпретація: якщо SOURCEinherited, підтвердьте, що батьківський датасет навмисно налаштований так. Випадкове успадкування — часта річ у середовищах «швидкого фіксу».

Завдання 3: Безпечна зміна recordsize (впливає лише на нові записи)

cr0x@server:~$ sudo zfs set recordsize=16K tank/vm
cr0x@server:~$ zfs get -o name,property,value,source recordsize tank/vm
NAME      PROPERTY    VALUE  SOURCE
tank/vm   recordsize  16K    local

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

Завдання 4: Переглянути фактичні розміри блоків, використані існуючим файлом

cr0x@server:~$ sudo zdb -bbbbb tank/vm | head -n 20
Dataset tank/vm [ZPL], ID 56, cr_txg 124567, 1.40T, 1045320 objects

    Object  lvl   iblk   dblk   dsize  dnsize  lsize   %full  type
        96    1   128K    16K   1.40T   512B   1.40T   100%  ZFS plain file

Інтерпретація: зверніть увагу на dblk (розмір блоку даних). Якщо ваш dataset recordsize 16K, але існуючі файли показують 128K, вони були записані до зміни або іншим профілем запису.

Завдання 5: Перевірити коефіцієнт стиснення та логічний vs фізичний простір

cr0x@server:~$ zfs get -o name,property,value -H compressratio,logicalused,used tank/backups
tank/backups	compressratio	1.82x
tank/backups	logicalused	1.63T
tank/backups	used	900G

Інтерпретація: якщо стиснення допомагає, більший recordsize може посилювати цю вигоду для послідовних навантажень. Якщо compressratio близько ~1.00x на несжимаємих даних, вибір recordsize в основному впливає на форму I/O і вартість перезапису.

Завдання 6: Швидко виміряти випадковий vs послідовний I/O за допомогою fio (саніті-чек)

cr0x@server:~$ fio --name=randread --directory=/tank/vm --rw=randread --bs=4k --iodepth=32 --numjobs=4 --size=4G --time_based --runtime=30 --group_reporting
randread: (groupid=0, jobs=4): err= 0: pid=18432: Thu Dec 24 12:10:33 2025
  read: IOPS=38.2k, BW=149MiB/s (156MB/s)(4468MiB/30001msec)
    slat (usec): min=3, max=240, avg=12.7, stdev=4.9
    clat (usec): min=60, max=3900, avg=820.2, stdev=210.5

Інтерпретація: запускайте це на датасеті, що відповідає вашому навантаженню. Якщо ваш реальний додаток робить 4K/8K випадкові I/O і затримки погані, recordsize може бути надто великим (або налаштування sync/лог-пристрій обмежують). Використовуйте fio, щоб спочатку підтвердити форму I/O.

Завдання 7: Спостерігати I/O пулу і затримки під реальним навантаженням

cr0x@server:~$ iostat -x 1
Linux 6.8.0 (server)  12/24/2025  _x86_64_  (32 CPU)

avg-cpu:  %user %nice %system %iowait  %steal  %idle
           9.2   0.0     6.1    18.7     0.0   66.0

Device            r/s     w/s   rMB/s   wMB/s  avgrq-sz avgqu-sz await  svctm  %util
nvme0n1         420.0   980.0    52.0   120.0    180.2      4.6   3.6   0.4   54.0
nvme1n1         410.0   970.0    51.3   119.1    179.6      4.8   3.8   0.4   55.0

Інтерпретація: якщо await зростає при малих I/O, можливо, ви стикаєтесь з затримками синхронних записів, насиченням пристрою або write amplification. recordsize — підозрюваний, коли записна пропускна здатність пулу здається високою відносно заявленої скорості записів додатку.

Завдання 8: Дивитись I/O на рівні ZFS за допомогою zpool iostat

cr0x@server:~$ zpool iostat -v 1
                              capacity     operations     bandwidth
pool                        alloc   free   read  write   read  write
--------------------------  -----  -----  -----  -----  -----  -----
tank                        3.21T  7.80T  8.10K  12.5K   210M   380M
  mirror                    1.60T  3.90T  4.05K  6.20K   105M   190M
    nvme0n1                    -      -  2.02K  3.10K  52.5M  95.0M
    nvme1n1                    -      -  2.03K  3.10K  52.5M  95.0M
--------------------------  -----  -----  -----  -----  -----  -----

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

Завдання 9: Перевірити статистику ARC на предмет сигналів ефективності кеша

cr0x@server:~$ arcstat 1 5
    time  read  miss  miss%  dmis  dm%  pmis  pm%  mmis  mm%  arcsz     c
12:11:10  185K  21.2K   11     9K   4  10K    5   2K    1   58G   64G
12:11:11  192K  30.1K   15    14K   7  12K    6   4K    2   58G   64G

Інтерпретація: зростання miss% під час навантаження, яке має бути кеш-дружнім, може означати, що блоки занадто великі для патерну доступу (або робочий набір надто великий). recordsize може бути частиною цієї історії.

Завдання 10: Підтвердити розмір I/O навантаження (перед тюнінгом)

cr0x@server:~$ sudo pidstat -d 1 5 -p 12345
Linux 6.8.0 (server)  12/24/2025  _x86_64_  (32 CPU)

12:12:01      UID       PID   kB_rd/s   kB_wr/s kB_ccwr/s  Command
12:12:02     1001     12345    5120.0    2048.0     128.0  postgres

Інтерпретація: це показує швидкості, не розміри, але дає першу підказку про домінування читань або записів. Поєднайте це з знаннями додатку (розмір сторінки БД, розміри блоків VM). Якщо ви не можете описати розміри I/O, ви налаштовуєте навмання.

Завдання 11: Примусово перезаписати файл, щоб застосувати новий recordsize

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

cr0x@server:~$ sudo zfs create -o recordsize=16K -o compression=lz4 tank/vm_new
cr0x@server:~$ sudo rsync -aHAX --inplace --progress /tank/vm/ /tank/vm_new/
cr0x@server:~$ sudo zfs set mountpoint=/tank/vm_old tank/vm
cr0x@server:~$ sudo zfs set mountpoint=/tank/vm tank/vm_new

Інтерпретація: rsync змушує нові блоки виділятися з налаштуваннями нового датасету. Заміна mountpoint — «нудний, але безпечний» перехід. Перевірте сервіси перед видаленням старого датасету.

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

cr0x@server:~$ sudo zdb -bbbbb tank/vm_new | head -n 20
Dataset tank/vm_new [ZPL], ID 102, cr_txg 124890, 1.39T, 1039120 objects

    Object  lvl   iblk   dblk   dsize  dnsize  lsize   %full  type
        96    1   128K    16K   1.39T   512B   1.39T   100%  ZFS plain file

Інтерпретація: dblk має відповідати бажаному recordsize для новозаписаних даних. Якщо ні, дослідіть: чи файли розріджені? Чи працюєте ви з zvols? Чи додаток пише більшими chunками, ніж очікувалося?

Завдання 13: Контроль гарячих файлів датасету за допомогою filefrag

cr0x@server:~$ sudo filefrag -v /tank/vm/disk01.raw | head -n 15
Filesystem type is: 0x2fc12fc1
File size of /tank/vm/disk01.raw is 107374182400 (26214400 blocks of 4096 bytes)
 ext:     logical_offset:        physical_offset: length:   expected: flags:
   0:        0..     255:   1200000..  1200255:    256:             last,eof
   1:      256..     511:   1310000..  1310255:    256:   1200256:

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

Завдання 14: Перевірити сигнали поведінки sync-записів (щоб не звинувачувати recordsize за проблеми slog)

cr0x@server:~$ zfs get -o name,property,value,source sync tank/db
NAME     PROPERTY  VALUE  SOURCE
tank/db  sync      standard  inherited

Інтерпретація: якщо у вас висока затримка синхронних записів (бази даних часто так роблять), налаштування recordsize не вирішить повільний SLOG або відсутність окремого лог-пристрою. Діагностуйте шлях синку окремо; recordsize в основному змінює вартість перезапису блоків і градацію I/O.

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

Це план «що перевірити спочатку, потім, потім», коли продуктивність не та і ви підозрюєте, що налаштування ZFS може бути причиною.

1) Підтвердьте I/O-патерн навантаження перед тим, як чіпати ZFS

  • Чи це випадкове чи послідовне?
  • Типовий розмір I/O: 4K, 8K, 16K, 128K, 1M?
  • Більше читань чи записів? Чи багато sync?

Запустіть базові спостереження: метрики додатку, iostat -x, zpool iostat. Якщо патерн випадкових малих записів і ваш датасет налаштований під величезні послідовні блоки — швидше за все ви знайшли курця з пістолетом.

2) Перевірте властивості датасету та успадкування

  • zfs get recordsize,compression,atime,primarycache,logbias,sync
  • Переконайтесь, що ви на датасеті (файли), а не на zvol (блочний пристрій)

Якщо датасет успадковує від батьківського «catch-all», перевірте, чи батько не налаштовувався під інше навантаження шість місяців тому в паніці.

3) Перевірте, що фактично лежить на диску (не те, чого ви сподіваєтесь)

  • zdb -bbbbb pool/dataset для розмірів блоків
  • Коефіцієнт стиснення і логічне проти фізичного використання

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

4) Шукайте ампліфікацію та вузькі місця, які маскуються під проблеми recordsize

  • Пул записує значно більше, ніж додаток пише (ампліфікація)
  • Висока затримка синхронних записів (SLOG або затримка пристрою)
  • Пропуски ARC через надмірно великі блоки для випадкових читань
  • Насичення CPU через контрольні суми/стискання при занадто малих блоках

5) Зробіть зміну, яку можна відкотити, а потім застосуйте правильно

Зміна recordsize відкотна, але відчутний ефект вимагає перезапису даних. Плануйте міграцію (новий датасет, rsync, cutover), а не крутити властивість і сподіватися.

Типові помилки: симптоми та виправлення

Помилка 1: Використання величезного recordsize для образів ВМ

Симптоми: ВМ зависають під час оновлень, періодичні сплески записів, записну активність сховища значно перевищує записи гостьової ОС, стрибки затримок при конкурентних записах.

Виправлення: розмістіть образи ВМ на датасеті, налаштованому для випадкового I/O: recordsize=16K або 32K. Мігруйте/перезапишіть образи, щоб існуючі блоки були відтворені у новому розмірі.

Помилка 2: Використання надмалої recordsize для резервних копій і медіа

Симптоми: резервні завдання не встигають, високий CPU на вузлах зберігання, багато IOPS але низький MB/s, prefetch неефективний.

Виправлення: створіть датасет для резервів/медіа з recordsize=1M (або 512K), відповідним стисненням (часто zstd або lz4) і перенаправте потоки даних туди.

Помилка 3: Змінити recordsize і очікувати миттєвого покращення

Симптоми: «Ми встановили recordsize 16K і нічого не змінилося.» Бенчмарки і поведінка додатку залишились ідентичними.

Виправлення: перезапишіть гарячі дані. Для існуючих великих файлів потрібно копіювання/rsync до нового датасету або контрольований перезапис, що змусить виділення нових блоків.

Помилка 4: Плутати recordsize з volblocksize

Симптоми: ви налаштовували recordsize, але навантаження насправді працює на zvol (iSCSI/LUN). Ніякого ефекту; плутанина у розборі інциденту.

Виправлення: для zvol плануйте volblocksize під час створення. Якщо воно неправильне, чисте виправлення — новий zvol з правильним volblocksize і міграція даних.

Помилка 5: Ігнорувати поведінку синхронних записів і звинувачувати recordsize

Симптоми: коміти бази даних повільні, читання в порядку; затримка корелює з fsync/commit; твіки recordsize не допомагають.

Виправлення: оцініть шлях синку: якість окремого лог-пристрою (SLOG), затримку основних vdev, очікування sync=standard і налаштування додатку. recordsize допомагає з write amplification, але не з «мій синхронний пристрій повільний».

Помилка 6: Налаштовувати recordsize без врахування стиснення

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

Виправлення: якщо ви використовуєте стиснення, зрозумійте, що фізичні розміри блоків можуть зменшуватись; вимірюйте compressratio, CPU-голосність і пропускну здатність пулу. Більший recordsize може покращити стиснення для деяких даних, але також збільшити вартість перезапису для випадкових оновлень.

Контрольні списки / покроковий план

Чекліст A: Вибір recordsize для нового датасету

  1. Назвіть навантаження. «VM images», «Postgres», «backup streams», «змішані домашні каталоги». Не «файли».
  2. Визначте типовий розмір I/O. 4K/8K/16K/128K/1M. Використайте документацію додатку або вимірювання.
  3. Вирішіть, чи часто відбуваються перезаписи. Бази даних і ВМ перезаписують; резервні копії здебільшого додають.
  4. Виберіть початковий recordsize. Типові стартові точки: 16K (DB/VM), 128K (загальне), 1M (резерви/медіа).
  5. Налаштуйте стиснення навмисно. lz4 зазвичай безпечний за замовчуванням; сильніше стиснення має бути обґрунтованим.
  6. Запишіть це в властивостях датасету. Не покладайтеся на усну пам’ять. Мінімум: recordsize, compression, atime, очікування sync.

Чекліст B: Зміна recordsize для існуючого навантаження (безпечний операційний підхід)

  1. Переконайтесь, що ви тюните правильний шар. Датасет проти zvol.
  2. Виміряйте поточну поведінку. Базова лінія: затримка, пропускна здатність, пропускна здатність пулу, miss% ARC. Зніміть до/після.
  3. Встановіть recordsize на новому датасеті. Уникайте зміни живого датасету спочатку, якщо дані все одно треба перезаписувати.
  4. Мігруйте з перезаписом. Використовуйте rsync/copy так, щоб виділялися нові блоки (копія в новий датасет).
  5. Зробіть cutover через зміну mountpoint або конфігурації сервісу. Покладіть механізми відкату.
  6. Підтвердіть розміри блоків і продуктивність. Використайте zdb для вибіркових перевірок і навантажувальні тести.
  7. Залиште старий датасет тимчасово. Страховка для відкату; видаліть пізніше, коли впевнені.

Чекліст C: «Чи справді recordsize — це проблема?»

  1. Якщо стрибки затримок корелюють з синхронними записами, спочатку дослідіть SLOG/затримку пристрою.
  2. Якщо пропускна здатність низька, але CPU високий, перевірте занадто малий recordsize або інтенсивне стиснення.
  3. Якщо пропускна здатність пулу велика порівняно з записами додатку, підозрюйте ампліфікацію (невідповідність recordsize, фрагментація, метадані).
  4. Якщо продуктивність погіршується зі заповненням пулу, перевірте фрагментацію і вільний простір; recordsize може погіршувати симптоми, але не бути їх джерелом.

Часті питання

1) Чи переписує зміна recordsize існуючі дані?

Ні. Вона впливає на нові записи. Існуючі блоки зберігають старі розміри, поки ділянки файлу не будуть перезаписані. Якщо потрібен негайний ефект — мігруйте/перезапишіть гарячі дані.

2) Який найкращий recordsize для PostgreSQL?

Зазвичай 16K на датасеті, що зберігає файли PostgreSQL, оскільки це добре узгоджується з типовим I/O БД і зберігає write amplification на розумному рівні. Але вимірюйте: робоче навантаження, обсяг RAM/ARC і чи обмежує вас синхронність важливіші за лозунги.

3) Який найкращий recordsize для образів VM, збережених як файли?

Зазвичай 16K або 32K. I/O в гостей зазвичай малий і випадковий; велике recordsize часто викликає ампліфікацію. Якщо середовище вкрай чутливе до IOPS, 8K може підійти, але це збільшує накладні витрати і може зменшити послідовну пропускну здатність для повних сканів гостя.

4) Чи варто встановити recordsize=1M всюди, бо це «швидше»?

Ні. Це швидше для великих послідовних потоків і часто гірше для малих випадкових перезаписів. 1M на датасетах для ВМ або баз даних — звична причина тікетів «чому записи такі стрибкоподібні?».

5) Як стиснення змінює рішення про recordsize?

Стиснення працює по блоках. Більші блоки можуть краще стискатися, що зменшує фізичний I/O для стискуваних даних. Але максимум recordsize все одно контролює обсяг перезапису: мала зміна в великому блоці все ще може викликати перезапис великого блоку, навіть якщо стисканий результат менший.

6) Чи менший recordsize завжди краще для затримок?

Ні. Менші блоки можуть зменшувати read amplification для випадкових читань і розмір перезапису для малих оновлень, але вони також збільшують накладні витрати на метадані і роботу CPU. Для послідовних навантажень занадто малий recordsize може знизити пропускну здатність і підвищити споживання CPU.

7) Як я дізнаюся, які розміри блоків фактично використовуються?

Використовуйте zdb -bbbbb pool/dataset, щоб інспектувати розміри блоків об’єктів датасету, і валідуйте вибірковими перевірками після міграції. Не припускайте, що зміна recordsize змінює існуючі файли.

8) У чому різниця між recordsize і ashift?

ashift — це налаштування вирівнювання сектора на рівні пулу/vdev (зазвичай 12 для 4K, 13 для 8K). Воно впливає на мінімальну градацію алокації та вирівнювання. recordsize — це максимальний розмір блоку файлу на рівні датасету. Вони взаємодіють — невирівняність може спричинити ампліфікацію — але вирішують різні проблеми.

9) Чи може recordsize спричинити фрагментацію?

recordsize сам по собі не «створює» фрагментацію, але невідповідність (великі записи при випадкових перезаписах) збільшує алокаційний шум і може погіршити фрагментацію з часом. Збереження здорового вільного простору і підбір recordsize під патерн I/O допомагають.

10) Чи варто налаштовувати recordsize для NFS шейрів?

Налаштовуйте під поведінку додатків за NFS. NFS — це транспорт; реальне питання в тому, чи клієнти роблять малі випадкові I/O всередині великих файлів (CAD, образи VM, файли БД) або здебільшого послідовні/апенд-операції.

Висновок

Якщо ви хочете один налаштувальний важіль ZFS, який надійно змінює результати, recordsize — це він, тому що визначає градацію блоків даних, а градація визначає все: вартість перезапису, ефективність кешування, частоту I/O-запитів і те, наскільки добре ви справляєтесь з випадковим проти послідовного доступу.

Операційний трюк — перестати ставитись до recordsize як до дрібного твіку і почати розглядати його як проєктне рішення. Створюйте датасети під кожне навантаження, вибирайте recordsize на основі I/O-патернів і пам’ятайте про те, що всі забувають: зміна має значення лише якщо ви перезапишете дані. Зробіть це, і у вас буде менше загадкових проблем з продуктивністю і більше нудних графіків — саме ті нудні, що тримають продакшн живим.

← Попередня
WordPress «Помилка встановлення з’єднання з базою даних»: швидке відновлення та запобігання
Наступна →
Дизайн охолодження: 2-вентилятори проти 3-вентиляторів — що насправді змінюється

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