ZFS recordsize і compression: комбінація, що змінює баланс CPU і диска

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

Ви можете купити швидші диски, підключити NVMe і все одно спостерігати графіки затримок ніби сейсмограф.
А потім ви перемикаєте дві властивості ZFS — recordsize і compression — і раптом той самий хардвер ніби отримує підвищення.

Або ви налаштуєте їх неправильно, і ваші CPU почнуть танцювальні інтерпретації, поки диски дрімають. Тут ми припиняємо гадати і робимо математику.

Що насправді робить recordsize (і чого він не робить)

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

Recordsize — це ліміт, а не обов’язкова величина

Для звичайних файлів ZFS використовує блоки змінного розміру до значення recordsize. Файл із 4K записами не перетвориться автоматично на 128K блоки
тільки тому, що у dataset встановлено recordsize 128K. ZFS охоче зберігатиме малі блоки, коли цього вимагає шаблон записів.
Але коли навантаження генерує великі послідовні записи, recordsize стає вашою реальною на-диску реальністю.

Ось чому recordsize найбільше видно на:

  • великих потокових читаннях/записах (резервні копії, object store, медіа, логи, що додаються великими шматками),
  • datasets з великими файлами і вигодою від read-ahead,
  • навantаженнях, де показуються штрафи read-modify-write (випадкові перезаписи всередині великих блоків).

Recordsize не застосовується до zvols так само

Якщо ви віддаєте iSCSI/LUN або диски віртуальних машин через zvols, керуванням є volblocksize, яке задається під час створення zvol.
Це фіксований розмір блоку, який видно споживачу. Розглядайте це як контракті API.

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

Режим болю для «recordsize занадто великий» — не в тому, що ZFS не може зробити маленький I/O; це те, що перезаписи невеликих шматків всередині великого рекорду
можуть викликати read-modify-write (RMW). ZFS мусить прочитати старий блок, змінити його частково і записати новий повний блок (copy-on-write).
Це коштує додаткового I/O і збільшує затримку — особливо коли робочий набір не вміщається в ARC і ви робите синхронні записи.

Думайте про recordsize як вибір одиниці «розмір транзакції зберігання». Великі блоки зменшують накладні витрати на метадані і можуть підвищити пропускну здатність стрімінгу.
Великі блоки також збільшують «радіус ураження» при дрібному перезаписі.

Жарт №1: Встановити recordsize=1M для бази даних з випадковими записами — це як привезти вантажівку для доставки ключа від квартири. Доставить, але нікому не сподобається.

Стиснення змінює економіку

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

Основний ефект: логічні байти проти фізичних байтів

Кожний dataset ZFS має дві реальності:

  • Логічна: що додаток вважає, що він записав/прочитав.
  • Фізична: що насправді потрапило на диск після стиснення (і потенційно після ефектів паддингу, RAIDZ парності тощо).

Коли стиснення ефективне, ви зменшуєте фізичні читання/записи. Це означає:

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

LZ4 зазвичай встановлений за замовчуванням недарма

У продакшні compression=lz4 — це «безпечний і швидкий» варіант для більшості даних: конфіги, логи, текстові payload, багато баз даних, образи VM.
Він достатньо швидкий, щоб CPU рідко був обмеженням, якщо тільки ви не працюєте на дуже високій пропускній здатності або на слабких ядрах.

Більш агресивні компресори (наприклад, рівні zstd) можуть дати великі виграші в просторі і часом у зменшенні I/O, але вартість CPU стає реальною.
Ця витрата CPU проявляється в затримках у невдалі моменти: сплески компактізації, вікна бекапів і «чому API повільний лише під час реплікації?» моменти.

Стиснення також взаємодіє з recordsize

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

Комбінована математика: IOPS, пропускна здатність і CPU

Recordsize і compression не просто «оптимізують». Вони визначають, яке саме ресурсне обмеження стане визначальним:
дискові IOPS, дискова пропускна здатність або CPU.

Почнемо з трьох режимів вузького місця

  • Обмеження IOPS: затримка визначається кількістю операцій (випадковий I/O, навантаження з великою кількістю метаданих, синхронні записи).
  • Обмеження пропускної здатності: не вдається пропхати більше байтів через диски/мережу (стрімінг, сканування, резервні копії).
  • Обмеження CPU: стиснення/декомпресія, контрольні суми, шифрування або важке ведення обліку ZFS споживають ядра.

Ваша мета налаштування — не «максимальна продуктивність». Це «перемістити вузьке місце до найдешевшого ресурсу».
CPU часто дешевший за IOPS на flash і значно дешевший за IOPS на обертових дисках. Але CPU не безкоштовний, коли бюджет затримки жорсткий.

Recordsize змінює математику IOPS

Припустимо, ваше навантаження читає 1 GiB послідовно.
Якщо recordsize 128K, це близько 8192 блоків. Якщо 1M — близько 1024 блоків.
Менше блоків означає менше пошуків метаданих, менше перевірок контрольних сум, менше I/O операцій і кращу поведінку prefetch.
Для потокових читань більші блоки часто виграють.

Тепер перемикаємося на випадкові перезаписи сторінок по 8K (привіт, бази даних).
Якщо ці сторінки живуть усередині 128K записів, випадкове оновлення сторінки може означати:

  • прочитати старий 128K (якщо не в ARC),
  • змінити 8K всередині нього,
  • записати новий 128K десь інде (copy-on-write),
  • оновити метадані.

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

Стиснення змінює математику пропускної здатності (і іноді IOPS)

Якщо ваш коефіцієнт стиснення 2:1, «100 MB читання» стає «50 MB фізичного читання + декомпресія».
Якщо диски були завантажені, ви щойно звільнили половину пропускної здатності. Затримка падає, пропускна здатність зростає, всі виглядають кмітливими.
Якщо диски не були завантажені, а ваші CPU вже були зайняті, ви щойно купили нову проблему.

Математика CPU: декомпресія на шляху читання

Записи платять за стиснення CPU-витратами. Читання платять за декомпресію. У багатьох системах читання домінують у хвостових затримках.
Це означає, що поведінка декомпресора важить більше, ніж ви думаєте.
Декомпресія LZ4 зазвичай швидка; більш «важкі» компресори — ні.

ARC і стиснення: «більше кешу» без покупки RAM

ZFS зберігає стиснені блоки в ARC (деталі реалізації залежать від версії та feature flags, але практичний ефект зберігається:
стиснені дані роблять кеш більш ефективним).
Краще стиснення може означати більше логічних даних у кеші на ГБ RAM.
Це одна з причин, чому compression=lz4 — стандартне «так» для загального використання.

Коли комбінація працює чудово

Ідеальний сценарій:

  • досить великий recordsize під шаблон доступу навантаження,
  • дешеве стиснення (lz4), яке ефективне (велика надлишковість),
  • диски — дорогий ресурс, CPU має запас потужності.

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

Коли комбінація — пастка

Сценарій пастки:

  • recordsize більший за границю перезапису,
  • навантаження робить випадкові оновлення,
  • стиснення додає CPU-витрати до кожного перепису великого запису,
  • синхронні записи + мала SLOG-пристрій (або відсутній SLOG) посилюють затримки.

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

Плейбук по навантаженнях: що ставити для чого

Значення за замовчуванням існують, бо вони безпечні, а не тому, що вони оптимальні. Ваша задача — бути безпечно впевненим.

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

  • recordsize: залишайте за замовчуванням (часто 128K), якщо немає причини змінювати.
  • compression: lz4.
  • Чому: змішані навантаження виграють від середнього recordsize; стиснення зменшує фізичний I/O для текстових файлів.

Образи VM на файловому dataset (qcow2/raw файли)

  • recordsize: зазвичай 16K або 32K; інколи 64K, якщо переважно послідовні читання.
  • compression: lz4 (часто допомагає більше, ніж очікують).
  • Чому: I/O VM має схильність бути випадковим і в менших блоках; зменшуйте ампліфікацію RMW.

zvols для VM дисків / iSCSI

  • volblocksize: співпадайте з очікуванням гостя/файлової системи (часто 8K або 16K; інколи 4K).
  • compression: lz4, якщо CPU не занадто завантажений.
  • Чому: volblocksize фіксований; помилка — податок на все життя, якщо не мігрувати.

PostgreSQL / MySQL / бази даних на файлових dataset

  • recordsize: підганяйте під розмір сторінки БД або кратне значення, яке не викликає чату (часто 8K або 16K).
  • compression: lz4 зазвичай підходить; тестуйте zstd лише якщо у вас є CPU і бажання зекономити місце.
  • Чому: бази даних роблять малі випадкові перезаписи; великі записи ведуть до RMW і фрагментації.

Резервні копії, архіви, медіа, object-подібні blob

  • recordsize: 512K або 1M може мати сенс, якщо читання/записи потокові.
  • compression: zstd (помірний рівень) може окупитися, або lz4, якщо потрібна передбачувана CPU-навантаженість.
  • Чому: послідовний I/O винагороджує великі блоки; коефіцієнт стиснення покращується з більшими вікнами.

Логи (додавання в кінець)

  • recordsize: за замовчуванням зазвичай підходить; якщо логи великі і пізніше читаються послідовно, розгляньте 256K.
  • compression: lz4 майже завжди виграє.
  • Чому: логи дуже добре стискаються; зменшення фізичних записів також зменшує знос SSD.

Малі файли (maildirs, дерева вихідного коду, кеші пакетів)

  • recordsize: не основна ручка; малі файли вже використовують малі блоки.
  • compression: lz4 допомагає і може прискорити читання за рахунок зменшення дискового I/O.
  • Розгляньте: виділений vdev для метаданих/малих блоків, якщо ви серйозно ставитеся до затримок.

Цікаві факти та історичний контекст

  • ZFS створювався для наскрізної цілісності даних: контрольні суми на кожному блоці — ядро, а не додаткова функція.
  • Ранні значення за замовчуванням ZFS орієнтувалися на великі диски і змішані навантаження: 128K recordsize стало прагматичним компромісом для стрімінгу та загального використання файлів.
  • Стиснення в ZFS давно «прозоре»: додаткам не потрібно знати; файлова система вирішує подання на диску.
  • LZ4 став стандартним через дешевизну: він розроблений для швидкості, тому часто покращує продуктивність, зменшуючи I/O без різкого стрибка CPU.
  • Copy-on-write змінює вартість перезаписів: ZFS ніколи не перезаписує на місці; тому навантаження з великою кількістю перезаписів чутливі до розміру блоку.
  • RAIDZ write amplification реальна: малі випадкові записи можуть перетворюватися на великі цикли read-modify-write на рівні паритетної смуги, підсилюючи наслідки вибору recordsize.
  • ARC зробив «RAM як зберігання» мейнстримом: ZFS агресивно кешує, і стиснення фактично збільшує ємність кешу для стислих даних.
  • zvols створені для блокових споживачів: але їхній фіксований volblocksize означає, що помилки важче виправити, ніж для recordsize на файлових dataset.

Три короткі історії з практики

Інцидент: неправильне припущення («recordsize не має значення для баз даних, правда?»)

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

Кінець місяця означав масивні оновлення, зміну індексів і завдання обслуговування. Затримки зросли, потім різко підскочили. Потоїлися потоки додатка.
Команда зберігання дивилася на використання диска: не завантажено. Графіки CPU на хостах БД: не завантажено. Тож звинуватили мережу.
Вони налаштовували TCP буфери, ніби це 2009 рік.

Насправді проблема була нудною: dataset мав recordsize=1M, успадкований від профілю бекапу.
База даних писала 8K сторінки. Під час чату ZFS мусив робити read-modify-write на великих записах і гонитву метаданих.
Пул не був «завантажений» у термінах пропускної здатності; він робив неправильний тип роботи.

Вони вирішили проблему, перемістивши базу даних на новий dataset з recordsize=16K і зберегли compression=lz4.
Міграція була болючою частиною. Відновлення продуктивності було миттєвим, і графіки перестали кричати.

Урок: «воно змонтувалося, отже ок» — це як опинитися в дебаті з фізикою о 2:00 ранку.

Оптимізація, що відплатилася: фаза ентузіазму з zstd

Інша організація мала проблему з місцем: швидкі SSD пули наповнювалися образами VM і артефактами збірки.
Хтось запропонував сильніше стиснення. Вони ввімкнули compression=zstd на агресивному рівні на завантаженому dataset.
Збереження місця були хороші. Тикет закрили з задоволеним зітханням.

Через два тижні їх CI почав пропускати SLA для збірок. Не постійно — достатньо, щоб дратувати.
Кластер не був поза CPU в цілому, але підмножина вузлів показувала підвищений iowait і більше системного CPU під час пікових годин.
Масив зберігання не був завантажений. Мережа була в порядку. Тож почали шукати винних: оновлення ядра, налаштування планувальника, «можливо DNS».

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

Вони відкотилися до lz4 на гарячому dataset і зберегли zstd лише для холодніших, write-once архівів артефактів.
Правильний висновок не «ніколи не використовувати zstd». Правильний висновок — «використовувати його там, де патерн доступу відповідає бюджету CPU».

Нудна, але правильна практика, що врятувала день: per-workload datasets

Фінансовий сервіс мав просте правило: кожному навантаженню свій dataset, навіть якщо здається, що це паперовий клопіт.
Бази даних, логи, образи VM, бекапи — окремі datasets, явні властивості і коротке README в mountpoint.
Нові інженери бурчали. Старші посміхалися і продовжували це робити.

Одного дня після оновлення платформи з’явилася регресія продуктивності. Старт батьків VM був повільніший, і підмножина гостьових ОС мала високу затримку записів.
Оскільки організація мала окремі datasets, вони могли швидко порівняти властивості і побачити, які навантаження постраждали.
Dataset для VM був налаштований під це середовище, і стиснення було консистентним.

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

Урок: правильність часто — це просто дисципліноване розділення. Це не гламурно. Але так ви уникаєте випадкового саботажу трьох інших навантажень при налаштуванні одного.

Практичні завдання: команди, виходи, рішення (12+)

Це реальні операційні кроки. Кожне завдання містить команду, приклад виходу, що це означає та яке рішення ви приймаєте далі.
Підлаштуйте імена пулів/dataset під ваше середовище.

Завдання 1: Показати властивості dataset, що мають значення (recordsize, compression)

cr0x@server:~$ zfs get -o name,property,value -s local,inherited recordsize,compression rpool/data
NAME        PROPERTY     VALUE
rpool/data  recordsize   128K
rpool/data  compression  lz4

Значення: Ви бачите ефективні налаштування. Якщо вони успадковані, знайдіть звідки.

Рішення: Якщо цей dataset хостить БД або образи VM, 128K може бути неправильним. Не змінюйте відразу — спочатку виміряйте.

Завдання 2: Перевірити, чи dataset фактично стискає дані

cr0x@server:~$ zfs get -o name,property,value compressratio,logicalused,used rpool/data
NAME        PROPERTY       VALUE
rpool/data  compressratio  1.85x
rpool/data  logicalused    1.62T
rpool/data  used           906G

Значення: Стиснення працює. logical — це те, що записали додатки; used — фізичний обсяг, що займає місце.

Рішення: Якщо compressratio близько 1.00x на гарячих даних, стиснення може марнувати CPU. Проте залишайте lz4, якщо CPU не тісний.

Завдання 3: Швидко знайти успадковані властивості (хто це встановив?)

cr0x@server:~$ zfs get -o name,property,value,source recordsize,compression -r rpool
NAME                  PROPERTY     VALUE  SOURCE
rpool                 recordsize   128K   default
rpool                 compression  off    default
rpool/data            recordsize   128K   inherited from rpool
rpool/data            compression  lz4    local
rpool/data/db         recordsize   1M     local
rpool/data/db         compression  lz4    inherited from rpool/data

Значення: rpool/data/db має локальний recordsize 1M. Це червоний прапорець для багатьох баз даних.

Рішення: Підтвердіть тип навантаження. Якщо це БД з 8K сторінками, плануйте міграцію до dataset з правильним розміром.

Завдання 4: Спостерігати реальний I/O по пулу, щоб знайти “шумного сусіда”

cr0x@server:~$ zpool iostat -v rpool 2
                              capacity     operations     bandwidth
pool                        alloc   free   read  write   read  write
rpool                       2.10T  1.40T    180    950  12.3M  88.1M
  mirror                    2.10T  1.40T    180    950  12.3M  88.1M
    nvme0n1                 -      -        90    480  6.2M   44.0M
    nvme1n1                 -      -        90    470  6.1M   44.1M

Значення: Висока кількість операцій запису при помірних читаннях вказує на write-heavy навантаження. Саме по собі цього недостатньо — корелюйте з затримкою.

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

Завдання 5: Перевірити здоров’я пулу і помилки перед тюнінгом

cr0x@server:~$ zpool status -v rpool
  pool: rpool
 state: ONLINE
status: Some supported features are not enabled on the pool.
action: Enable all features using 'zpool upgrade'. Once this is done,
        the pool may no longer be accessible by software that does not support the features.
  scan: scrub repaired 0B in 00:12:41 with 0 errors on Tue Dec 24 03:12:02 2025
config:

        NAME        STATE     READ WRITE CKSUM
        rpool       ONLINE       0     0     0
          mirror    ONLINE       0     0     0
            nvme0n1 ONLINE       0     0     0
            nvme1n1 ONLINE       0     0     0

errors: No known data errors

Значення: Немає помилок, scrub чистий. Добре — проблеми продуктивності ймовірно конфігураційні або викликані навантаженням.

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

Завдання 6: Виміряти витрати CPU на стиснення через системний час під навантаженням

cr0x@server:~$ mpstat -P ALL 1 3
Linux 6.5.0 (server)  12/26/2025  _x86_64_  (16 CPU)

12:40:01 AM  CPU  %usr %nice %sys %iowait %irq %soft %steal %guest %gnice %idle
12:40:02 AM  all  18.1  0.0   9.7   2.4    0.0  0.3    0.0    0.0    0.0   69.5
12:40:03 AM  all  20.2  0.0  13.5   1.9    0.0  0.2    0.0    0.0    0.0   64.2
12:40:04 AM  all  19.6  0.0  14.1   1.8    0.0  0.2    0.0    0.0    0.0   64.3

Значення: Підвищений %sys вказує на роботу ядра (включно з ZFS). Низький iowait означає, що диски не є вузьким місцем.

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

Завдання 7: Спостерігати ефективність ARC і тиск пам’яті

cr0x@server:~$ arcstat 1 3
    time  read  miss  miss%  dmis  dm%  pmis  pm%  mmis  mm%  arcsz     c
12:41:10   820   110     13    28    3    72    9    10    1   28.5G  31.8G
12:41:11   790    95     12    22    3    63    8    10    1   28.6G  31.8G
12:41:12   815   120     15    35    4    74    9    11    1   28.6G  31.8G

Значення: Рівень промахів не жахливий. Розмір ARC близький до цільового. Стиснення може зробити ARC «тримати більше логічних даних», але промахи все одно болючі.

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

Завдання 8: Перевірити логічні vs фізично записані дані по dataset (чи зменшилося навантаження на запис?)

cr0x@server:~$ zfs get -o name,property,value -r logicalused,used,compressratio rpool/data/vm
NAME           PROPERTY       VALUE
rpool/data/vm  logicalused    800G
rpool/data/vm  used           610G
rpool/data/vm  compressratio  1.31x

Значення: Помірне стиснення. Образи VM часто трохи стискаються, іноді дуже сильно, залежно від ОС і нульових блоків.

Рішення: Залишайте lz4. Якщо CPU високий і compressratio близький до 1.00x, розгляньте відключення, якщо вас турбує хвостова затримка.

Завдання 9: Підтвердити фактичні розміри блоків, що записуються (не тільки recordsize)

cr0x@server:~$ zdb -bbbbb rpool/data/db | head -n 12
Dataset rpool/data/db [ZPL], ID 236, cr_txg 41292, 1.12G, 2948 objects
Indirect blocks:
               0 L0  16K   1.23G  11%  1.00x   79.3M

Значення: Це показує фактичний розподіл блоків. Тут видно, що домінують L0 блоки по 16K, незважаючи на те, що recordsize могло бути більшим.

Рішення: Якщо ви очікували 128K блоки для стрімінгового навантаження, але бачите багато 8K/16K, навантаження не стрімінгове (або воно фрагментоване). Налаштуйте відповідно.

Завдання 10: Визначити, чи синхронні записи вас вбивають

cr0x@server:~$ zfs get -o name,property,value sync,logbias rpool/data/db
NAME          PROPERTY  VALUE
rpool/data/db sync      standard
rpool/data/db logbias   latency

Значення: Sync — standard (безпечний). logbias latency віддає перевагу SLOG, коли він присутній.

Рішення: Якщо у вас багато fsync-інтенсивних записів і немає хорошого SLOG, очікуйте проблем з продуктивністю. Не «вирішуйте» це, встановивши sync=disabled, якщо не хочете пояснювати втрату даних.

Завдання 11: Перевірити наявність спеціального vdev (прискорення метаданих/малих блоків)

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

        NAME           STATE     READ WRITE CKSUM
        rpool          ONLINE       0     0     0
          mirror       ONLINE       0     0     0
            sda        ONLINE       0     0     0
            sdb        ONLINE       0     0     0
        special
          mirror       ONLINE       0     0     0
            nvme0n1    ONLINE       0     0     0
            nvme1n1    ONLINE       0     0     0

Значення: Є special vdev mirror. Туди потрапляють метадані і, опційно, малі блоки.

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

Завдання 12: Перевірити налаштування special_small_blocks (чи малі дані дійсно йдуть на special?)

cr0x@server:~$ zfs get -o name,property,value special_small_blocks rpool/data
NAME        PROPERTY             VALUE
rpool/data  special_small_blocks 0

Значення: Тільки метадані йдуть на special, не малі дані блоків.

Рішення: Якщо ви хочете, щоб малі блоки потрапляли на special, встановіть special_small_blocks=16K або подібне — після ретельного обдумування ємності і домену відмови.

Завдання 13: Безпечно протестувати зміну recordsize на новому dataset

cr0x@server:~$ zfs create -o recordsize=16K -o compression=lz4 rpool/data/db16k
cr0x@server:~$ zfs get -o name,property,value recordsize,compression rpool/data/db16k
NAME            PROPERTY     VALUE
rpool/data/db16k recordsize  16K
rpool/data/db16k compression lz4

Значення: Ви створили місце для тесту/міграції, не змінюючи існуючий dataset на місці.

Рішення: Перемістіть представницьку частину даних і запустіть бенчмарк. Уникайте «героїчних» змін прямо в продакшні.

Завдання 14: Перенести дані через send/receive з збереженням снапшотів і зменшеним простоєм

cr0x@server:~$ zfs snapshot rpool/data/db@pre-migrate
cr0x@server:~$ zfs send -R rpool/data/db@pre-migrate | zfs receive -u rpool/data/db16k
cr0x@server:~$ zfs list -t snapshot -o name,used -r rpool/data/db16k | head
NAME                         USED
rpool/data/db16k@pre-migrate 0B

Значення: Dataset і snapshot існують на цілі. Параметр -u тримає його відмонтованим, доки ви не будете готові.

Рішення: Зріз перемикання за допомогою обміну mountpoint-ами або оновлення конфігурації сервісу у вікні технічних робіт.

Завдання 15: Підтвердити, що ви дійсно зменшили фізичний I/O після ввімкнення стиснення

cr0x@server:~$ zfs get -o name,property,value written,logicalused rpool/data/logs
NAME            PROPERTY    VALUE
rpool/data/logs written     145G
rpool/data/logs logicalused 312G

Значення: Logical used значно більше за фізично записане, що свідчить про те, що стиснення зменшує записний трафік.

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

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

Це робочий процес «хтось кричить у Slack». Мета — швидко визначити, чи ви обмежені IOPS, пропускною здатністю, CPU чи синхронними записами.

Перше: переконайтеся, що це не падаючий пул або scrub/resilver

  • Запустіть zpool status. Якщо ви бачите помилки, деградовані vdev або триває resilver — зупиніться з тюнінгом і почніть виправляти апарат або завершувати відновлення.
  • Перевірте, чи не йде scrub. Scrub може бути ввічливим або агресивним залежно від налаштувань і навантаження.

Друге: вирішіть, чи вузьке місце в диску чи в CPU

  • Ознаки обмеження диска: високий iowait, велике завантаження пристрою, пропускна здатність біля межі пристрою, затримка зростає з ростом throughput.
  • Ознаки обмеження CPU: низький iowait але високий системний CPU, пропускна здатність досягає плато рано, затримки зростають без насичення дисків.

Третє: перевірте, чи синхронні записи — справжній лиходій

  • Перевірте dataset sync і чи використовує навантаження fsync/O_DSYNC.
  • Якщо у вас є SLOG, переконайтеся, що він здоровий і швидкий. Якщо немає — прийміть, що sync-важкі навантаження будуть обмежені латентністю основного vdev.

Четверте: перевірте recordsize/volblocksize проти шаблону I/O

  • Для БД/випадкових перезаписів занадто великий recordsize може спричиняти RMW-амліфікацію.
  • Для потокових навантажень занадто малий recordsize марнує IOPS і CPU на метадані/контрольні суми.

П’яте: перевірте, чи стиснення допомагає, а не лише «включене»

  • Дивіться на compressratio та логічне проти фізичного використання.
  • Якщо коефіцієнт поганий і CPU високий, розгляньте lz4 замість важчих алгоритмів або відключення стиснення для вже стиснених даних.

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

1) «Диски прості, але затримка висока»

  • Симптом: низька пропускна здатність, низький iowait, але затримка додатка підскакує.
  • Корінь проблеми: шлях обмежений CPU (занадто сильне стиснення, витрати на контрольні суми/шифрування) або контенція синхронних записів.
  • Виправлення: понизьте стиснення до lz4, забезпечте запас CPU, перевірте SLOG для синхронних навантажень і уникайте надмірного recordsize для перезаписів.

2) «Записи бази даних стали повільніші після збільшення recordsize заради throughput»

  • Симптом: вища затримка запису, більш непередбачувані сплески під час обслуговування/вакууму/компактації.
  • Корінь проблеми: RMW-амліфікація від перезапису малих сторінок у великих записах; фрагментація погіршується з часом.
  • Виправлення: мігруйте на dataset з recordsize, вирівняним під розмір сторінки БД (часто 8K/16K). Залишайте compression lz4, якщо CPU в нормі.

3) «Стиснення увімкнене, але пул все одно заповнюється і продуктивність не покращилась»

  • Симптом: compressratio ~1.00x, немає значного зменшення фізичних записів.
  • Корінь проблеми: дані вже стиснені/зашифровані (медіа, zip, багато образів VM з зашифрованими файловими системами).
  • Виправлення: залишайте lz4, якщо CPU дешевий і хочете випадкові виграші; або вимкніть стиснення для цього dataset і не платіть CPU-податок даремно.

4) «Послідовні читання повільніші, ніж очікувалося, на швидких дисках»

  • Симптом: пропускна здатність нижча за апаратні можливості під час сканів/бекапів.
  • Корінь проблеми: recordsize занадто малий, що спричиняє надмірні IOPS і накладні витрати; або дані фрагментовані в малі блоки через історичний патерн запису.
  • Виправлення: використовуйте dataset з великими recordsize для стрімінгових даних (256K–1M). Для існуючих даних розгляньте перезапис (send/recv) для переблокування.

5) «Увімкнули сильне стиснення, зекономили місце, але порушили SLA»

  • Симптом: виграші по місцю, але хвостова затримка/регресії під піковим навантаженням.
  • Корінь проблеми: контенція CPU від важкого стиснення/декомпресії на гарячому шляху.
  • Виправлення: використовуйте lz4 для гарячих даних; залишайте zstd для холодніших, менш чутливих до затримки datasets; тестуйте з реальною конкурентністю.

6) «Ми змінили recordsize і нічого не змінилося»

  • Симптом: жодних видимих змін після встановлення recordsize.
  • Корінь проблеми: recordsize впливає на нові записи. Існуючі блоки зберігають старий розмір, поки їх не перезапишуть.
  • Виправлення: перезапишіть дані через реплікацію/міграцію (send/recv) або перезапишіть файли на рівні додатка; перевірте розподіл блоків за допомогою zdb.

Жарт №2: Якщо ви змінили recordsize в dataset і очікуєте, що старі блоки самі по собі переформуються — вітаю, ви винайшли storage yoga.

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

Покроково: безпечний вибір recordsize + compression

  1. Класифікуйте навантаження: випадкові перезаписи (БД), випадкове читання (VM), послідовне (бекап/медіа), змішане (домашні директорії).
  2. Знайдіть одиницю I/O: розмір сторінки БД, блок VM, типовий розмір об’єкта, розмір чанку логу.
  3. Оберіть початковий recordsize:
    • БД: 8K–16K (вирівняти сторінки).
    • Файли VM: 16K–64K.
    • Стрімінг: 256K–1M.
    • Змішане: 128K.
  4. Увімкніть compression lz4, якщо немає конкретної причини проти (CPU/латентність).
  5. Створіть новий dataset з цими властивостями; по можливості не мутуйте старий.
  6. Мігруйте представницьку вибірку даних і запускайте бенчмарк з продакшен-подібною конкуренцією.
  7. Перевірте результати:
    • Чи покращився compressratio?
    • Чи знизилися перцентилі затримок?
    • Чи залишається запас CPU?
  8. Розгортайте поступово: мігруйте сервіс за сервісом, а не «весь пул одразу».

Чекліст: перед тим як чіпати продакшн-ручки

  • Пул здоровий (zpool status).
  • Нещодавній scrub завершився без помилок.
  • Ви знаєте, чи навантаження синхронно-важке.
  • Є можливість відкотитися (snapshots + протестований план міграції).
  • Є базові метрики (перцентилі затримки, CPU, IOPS, пропускна здатність).
  • Ви не змішуєте несумісні навантаження в одному dataset з універсальними властивостями.

Чекліст: політика стиснення за типом даних

  • Текст/логи/конфіги: lz4 увімкнено.
  • Бази даних: lz4 увімкнено (зазвичай), recordsize вирівняний.
  • Медіа (вже стиснені): опціонально; часто вимкнено, якщо немає вимірюваних виграшів.
  • Зашифровані дані на диску: стиснення не допоможе; не очікуйте дива.
  • Холодні архіви: розгляньте zstd, якщо бюджет CPU і затримки розділені.

Одна ідея для надійності (перефразовано), варта уваги

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

FAQ

1) Чи завжди потрібно вмикати compression=lz4?

Для більшості datasets — так. Воно часто зменшує фізичний I/O і підвищує ефективність кешу без помітної витрати CPU.
Винятки: вже стиснені/зашифровані дані на системах з обмеженим CPU і жорстким бюджетом затримки.

2) Якщо я зміню recordsize, чи перезапишуться існуючі блоки?

Ні. Recordsize застосовується до нових записів. Існуючі блоки зберігають свій розмір, поки їх не перезапишуть.
Щоб «переблокувати», зазвичай мігрують дані в новий dataset (send/recv) або перезаписують файли на рівні додатка.

3) Який recordsize використовувати для PostgreSQL?

Типові початкові значення — 8K або 16K (сторінки PostgreSQL зазвичай 8K). 16K може бути нормальним, якщо ви бачите певну послідовну поведінку.
Правильна відповідь: вирівняти під сторінкову поведінку і провести бенчмарки під вашим робочим навантаженням, особливо для таблиць з інтенсивними оновленнями.

4) А MySQL/InnoDB?

InnoDB зазвичай використовує 16K сторінки за замовчуванням. recordsize 16K — розумна відправна точка для навантажень з інтенсивними перезаписами.
Якщо ви виконуєте великі послідовні сканування і здебільшого додаєте, можна дозволити більші значення. Вимірюйте перед творчістю.

5) Чому великий recordsize шкодить випадковим записам, якщо ZFS може зберігати малі блоки?

Проблема не в початкових малих записах — вона у часткових перезаписах і поведінці copy-on-write, які створюють цикли read-modify-write і фрагментацію.
Великі записи збільшують обсяг даних, що перезаписуються через невелику зміну.

6) Чи вартий zstd витрат?

Інколи. Він може дати краще стиснення, ніж lz4, особливо для холодних або write-once даних.
На гарячих, чутливих до затримки datasets він може нашкодити через підвищену контенцію CPU і хвостову затримку.

7) Чи стиснення допомагає IOPS, чи лише пропускній здатності?

Переважно воно зменшує пропускну здатність (байти). Але зменшення байтів може побічно зменшити навантаження на IOPS, якщо операції виконуються швидше,
і воно покращує щільність кешу, що може зменшити фізичні читання (а отже й IOPS), коли покращується ARC hit rate.

8) Recordsize vs volblocksize: що налаштовувати для VM-зберігання?

Якщо ви зберігаєте диски VM як файли у dataset — налаштовуйте recordsize. Якщо ви використовуєте zvols — налаштовуйте volblocksize під час створення.
Не плутайте їх; volblocksize змінити пізніше важче.

9) Чи можу я встановити різні recordsize значення всередині одного dataset?

Ні — не по директоріям через стандартні властивості ZFS. Це властивість на рівні dataset. Практичний підхід: створюйте кілька datasets з різними властивостями
і монтуйте їх там, де додаток очікує різної поведінки I/O.

10) Як дізнатися, чи я CPU-bound через стиснення?

Зазвичай ви побачите низький iowait, підвищений системний CPU і пропускна здатність, яка рано досягає плато до насичення дисків.
Підтвердіть, порівнявши продуктивність з lz4 та важчим стисненням на тестовому dataset з продакшен-подібною конкуренцією.

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

  1. Перелічіть ваші datasets: виведіть recordsize, compression і джерела успадкування. Знайдіть «профіль бекапу, що випадково працює в продакшні».
  2. Оберіть одне навантаження, яке або чутливе до затримки, або дороге (база даних, зберігання VM, кеш збірки). Дайте йому виділений dataset.
  3. Встановіть розумні значення за замовчуванням:
    • Гарячі загальні дані: compression=lz4, recordsize=128K.
    • БД: compression=lz4, recordsize=8K або 16K.
    • Стрімінгові бекапи: recordsize=512K або 1M, стиснення залежить від бюджету CPU.
  4. Міґруйте, не мутуйте: створюйте новий dataset з правильними налаштуваннями і переносіть дані через send/recv. Це допомагає уникнути несподіваних регресій.
  5. Перевимірюйте: логічне проти фізичного, перцентилі затримок, запас CPU і питання «чи справді диски зайняті?».

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

← Попередня
MySQL vs SQLite: наскільки далеко SQLite може зайти, перш ніж зіпсує ваш сайт
Наступна →
ZFS L2ARC: SSD-кеш, який допомагає… поки не починає шкодити

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