Міграція recordsize у ZFS: змінюємо стратегію без переписування всього

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

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

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

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

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

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

Recordsize — це компроміс між ефективністю послідовного доступу і вартістю випадкових оновлень. Великі записи чудові при потоковій передачі великих файлів (резервні копії, медіа, логи, у які лише додають запис),
малі записи кращі при дрібних випадкових читаннях/записах (деякі бази даних, образи VM, поштові спули, навантаження з великою кількістю метаданих).
Помилка — вважати, що існує одне правильне значення. Немає. Є правильне значення для кожного датасету та навантаження.

Також: recordsize стосується саме файлових систем (zfs датасети). Для блочних пристроїв (zvol) ви маєте справу з volblocksize, який встановлюється при створенні і його не так просто змінити потім.
Якщо ви запускаєте VM на zvol і налаштовуєте recordsize… ви намагаєтесь підвищити економічність вантажівки з допомогою полірування велосипеда.

Як recordsize взаємодіє з реальним світом

  • Підсилення IO (IO amplification): оновлення 4K в середині 128K запису може коштувати більше ніж 4K. ZFS має copy-on-write, контрольні суми і компресію; іноді потрібно переписати велику кількість структур, щоб змінити дрібні дані.
  • Поведінка ARC: великі блоки можуть споживати ARC більшими шматками; це буває корисно (менше метаданих для пошуку) або шкідливо (часті зміни кешу) залежно від шаблонів доступу.
  • Компресія: більші записи часто краще стискаються. Але стиснення 1M запису не «безкоштовне», якщо ваше навантаження — випадкові читання невеликих ділянок.
  • Спеціальні vdev: якщо у вас є спеціальні класи алокації для метаданих/малих блоків, зміни recordsize можуть змінити поріг «малого» і вплинути на те, що потрапляє на special vdev. Це може бути як подарунок, так і підпал.
  • Знімки (snapshots): переписування даних, щоб прийняти новий recordsize, збереже старі блоки через знімки. Плани міграції, які ігнорують утримання знімків, навчають команди зберігання скромності.

Одна цитата, щоб бути чесним із собою

Перефразована ідея (Gene Kim): «Покращення надійності приходить від розуміння потоків і зворотних зв’язків, а не від героїчних дій після збою.»

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

ZFS існує досить давно, щоб накопичити фольклор. Частина корисна; частина — на компост. Ось кілька конкретних фактів і контекстних моментів,
які справді важливі для рішень щодо міграції recordsize:

  1. Recordsize походить з епохи Solaris як прагматичний «достатньо великий для послідовного доступу, але не надто великий для пам’яті» — 128K стало поширеним, бо працювало на реальних дисках і навантаженнях.
  2. За замовчуванням recordsize історично лишався стабільним саме тому, що це компроміс, а не оптимум. Він призначений бути «достатньо добрим» для загального обміну файлами, а не «відмінним» для вашої OLTP-бази.
  3. ZFS завжди підтримував змінні розміри блоків до recordsize; тому малі файли не паддяться до 128K. Саме тому recordsize — це стеля, а не наказ.
  4. OpenZFS розвивався і розходився по платформах. Функції як large dnodes, special vdevs і покращений prefetch змінили ландшафт продуктивності навколо recordsize, навіть якщо сам recordsize «не змінювався».
  5. Алгоритми компресії покращилися (наприклад, LZ4 став практичним дефолтом). Це зробило тезу «більші записи краще стискаються» більш релевантною, бо компресія стала дешевшою у використанні.
  6. Диски з секторами 4K підштовхнули до уваги ashift. Несумісність ashift і фізичних секторів може домінувати у продуктивності; налаштування recordsize не виправить погане фізичне вирівнювання.
  7. Тенденції збереження VM вимагали ясності: спільнота (іноді голосно) навчилася, що налаштування zvol відрізняється від файлової системи, і поради щодо recordsize туди не переходять.
  8. SSD/NVMe змінили режими відмов: випадкові IOPS вже не такі страшні як на обертових дисках, але write amplification і стрибки латентності все ще мають значення — особливо з паритетними RAID, малими оновленнями та синхронними записами.

Чому ви мігруєте: мотивація, що виходить із симптомів

«Ми хочемо змінити recordsize» — це не мета. Це тактика. Ваша мета зазвичай одна з таких:

  • Зменшити затримки при випадкових читаннях або записах для додатків, які працюють із невеликими областями.
  • Збільшити пропускну здатність для великих послідовних потоків (резервні копії, обробка медіа, ETL-пайплайни, архіви).
  • Зменшити write amplification для робочих навантажень із частими перезаписами (деякі шаблони баз даних, образи VM, шари контейнерів під змінами).
  • Вирішити проблему зі “шелестом” кешу коли ARC заповнений великими записами, а навантаження хоче маленькі вирізи.
  • Припинити перевантаження special vdev через надто багато блоків, що класифікуються як «малі».
  • Зробити поведінку реплікації та send/receive прогнозованою шляхом вирівнювання нових записів під відому блокову стратегію.

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

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

Коли продуктивність погана і recordsize отримує звинувачення (часто необґрунтовано), не починайте з переписування даних.
Почніть з доведення, що саме повільне і де.

По-перше: ідентифікуйте патерн навантаження

  • Переважно випадкові читання? Випадкові записи? Послідовні читання/записи?
  • Який розмір IO фактично посилає додаток? 4K? 16K? 128K? Змішані?
  • Сповільнення пов’язане з латентністю чи з пропускною здатністю?

По-друге: перевірте шлях збереження на «жорсткі обмеження»

  • Чи синхронні записи чекають на SLOG? (Або гірше: немає SLOG на системі із великою кількістю sync.)
  • Паритетні vdev роблять малі випадкові перезаписи? Це несплячий збирач податків.
  • Special vdev перевантажений? Метадані IO можуть підколіни весь пул.
  • Ashift неправильний? Невирівнювання може домінувати над усім іншим.

По-третє: валідуйте, що recordsize робить сьогодні

  • Який зараз recordsize у датасеті?
  • Які розміри блоків існують на диску для “гарячих” файлів?
  • Чи знімки фіксують старі layout-и так, що «міграція» фактично нічого фізично не змінює?

Recordsize — це важіль. Але важіль не тягнуть, поки не підтвердите, що тягнете правильний.

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

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

Завдання 1: Підтвердіть поточний recordsize (і чи ви взагалі дивитесь на правильний датасет)

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

Значення: tank/app має recordsize 128K, встановлений локально. Якщо показує inherited, налаштування походить від батьківського датасету.

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

Завдання 2: Перевірте, чи ви на файловому датасеті або zvol

cr0x@server:~$ zfs list -o name,type,volblocksize,recordsize,mountpoint tank
NAME            TYPE         VOLBLOCKSIZE  RECORDSIZE  MOUNTPOINT
tank            filesystem   -             128K        /tank
tank/app        filesystem   -             128K        /tank/app
tank/vm-01      volume       16K           -           -

Значення: tank/vm-01 — це zvol; recordsize не застосовується. Застосовується volblocksize.

Рішення: Якщо ваша проблема — IO дисків VM і вони на zvol, припиніть налаштовувати recordsize і оцініть volblocksize, вирівнювання zvol та шаблони IO гостя.

Завдання 3: Перевірте ashift і макет vdev (recordsize не врятує погані фундаментальні налаштування)

cr0x@server:~$ zdb -C tank | egrep "ashift|vdev_tree|type:|path:"
        ashift: 12
        type: 'raidz'
          path: '/dev/disk/by-id/nvme-SAMSUNG_MZQLB1T9HAJR-00007_...'

Значення: ashift=12 означає сектори 4K. Зазвичай це нормально. Якщо бачите ashift=9 на сучасних дисках, очікуйте проблем з невирівнюванням.

Рішення: Якщо ashift неправильний, плануйте перебудову пулу. Не витрачайте тиждень на «налаштування recordsize» як спосіб пристосуватись.

Завдання 4: Підтвердіть компресію і її взаємодію з розміром IO

cr0x@server:~$ zfs get -o name,property,value compression,compressratio tank/app
NAME      PROPERTY      VALUE     SOURCE
tank/app  compression   lz4       local
tank/app  compressratio 1.43x     -

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

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

Завдання 5: Спостерігайте реальні шаблони розміру IO (ви припускаєте чи вимірюєте?)

cr0x@server:~$ iostat -x 1 5
avg-cpu:  %user   %nice %system %iowait  %steal   %idle
           8.12    0.00    3.94    1.25    0.00   86.69

Device            r/s     w/s   rMB/s   wMB/s  avgrq-sz  await  svctm  %util
nvme0n1         820.0   610.0   12.8    9.6      32.0    1.9    0.4   58.0

Значення: avgrq-sz ~32K запити. Це не «великий стрімінг». Це IO помірного розміру. Якщо ваш recordsize — 1M, а ви робите 32K запити, слід насторожитись.

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

Завдання 6: Перевірте IO і латентність на рівні ZFS за допомогою zpool iostat

cr0x@server:~$ zpool iostat -v tank 1 3
                              capacity     operations     bandwidth
pool                        alloc   free   read  write   read  write
--------------------------  -----  -----  -----  -----  -----  -----
tank                        2.10T  5.15T  1.20K  900     85.0M  60.0M
  raidz1-0                  2.10T  5.15T  1.20K  900     85.0M  60.0M
    nvme0n1                     -      -  400    300     28.0M  20.0M
    nvme1n1                     -      -  400    300     28.0M  20.0M
    nvme2n1                     -      -  400    300     28.0M  20.0M
--------------------------  -----  -----  -----  -----  -----  -----

Значення: Пул виконує багато дрібних операцій. Якщо ops високі, а пропускна здатність скромна, ймовірно ви обмежені IOPS/латентністю, а не пропускною здатністю.

Рішення: Якщо ви обмежені IOPS на RAIDZ, менший recordsize може зменшити read-modify-write amplification для перезаписів у деяких шаблонах, але паритетні витрати можуть все одно домінувати. Розгляньте mirror-и для навантажень з великими випадковими записами.

Завдання 7: Перевірте тиск на синхронні записи (recordsize не вирішить fsync-шторм)

cr0x@server:~$ zpool get -o name,property,value,source sync,logbias tank
NAME  PROPERTY  VALUE  SOURCE
tank  sync      standard  default
tank  logbias   latency   local

Значення: sync=standard — нормально. logbias=latency віддає перевагу SLOG для sync-записів (якщо він присутній).

Рішення: Якщо ваш додаток часто робить fsync і латентність погана, перевірте стан/продуктивність SLOG перед тим, як чіпати recordsize. Recordsize не зробить повільний sync-пристрій швидшим.

Завдання 8: Підтвердіть наявність або відсутність SLOG-пристрою

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

        NAME        STATE     READ WRITE CKSUM
        tank        ONLINE       0     0     0
          raidz1-0  ONLINE       0     0     0
            nvme0n1 ONLINE       0     0     0
            nvme1n1 ONLINE       0     0     0
            nvme2n1 ONLINE       0     0     0
        logs
          nvme3n1   ONLINE       0     0     0

Значення: Є окремий лог-пристрій. Добре. Тепер перевірте, що це не звичайний SSD із поганими PLP-характеристиками.

Рішення: Якщо SLOG відсутній і у вас є sync-навантаження, не робіть recordsize козлом відпущення.

Завдання 9: Перевірте розміри блоків файлів на диску (чи справді ваші «нові налаштування» присутні?)

cr0x@server:~$ zdb -ddddd tank/app | egrep "Object|blksz|type" | head -n 12
Object  lvl   iblk   dblk  dsize  dnsize  bonustype  bsize  data
      7    1   128K   128K  9.00M     512  DMU dnode  16K   ZFS plain file
        dblk [0] L0 128K 1.00M
        dblk [1] L0 128K 1.00M

Значення: Блоки даних цього файлу — 128K. Якщо ви змінили recordsize на 16K вчора і все ще бачите блоки 128K, цей файл не був переписаний.

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

Завдання 10: Ідентифікуйте фіксацію знімків перед плануванням перепису

cr0x@server:~$ zfs list -t snapshot -o name,used,refer,creation -s creation tank/app | tail -n 5
tank/app@snap-2025-12-01   85.2G  120G  Mon Dec  1 03:00 2025
tank/app@snap-2025-12-08   91.7G  120G  Mon Dec  8 03:00 2025
tank/app@snap-2025-12-15   96.4G  120G  Mon Dec 15 03:00 2025
tank/app@snap-2025-12-22  101.9G  120G  Mon Dec 22 03:00 2025
tank/app@snap-2025-12-26  104.1G  120G  Thu Dec 26 03:00 2025

Значення: Показник «used» у знімках зростає. Переписування подвоїть блоки, якщо знімки зберігають старі — потенційно подвоєння простору.

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

Завдання 11: Реалістично оцініть запас місця в датасеті (не лише «df каже, що все ок»)

cr0x@server:~$ zfs list -o name,used,avail,refer,logicalused,logicalavail tank/app
NAME      USED  AVAIL  REFER  LOGICALUSED  LOGICALAVAIL
tank/app  2.1T  3.4T   1.9T   2.8T         4.5T

Значення: Фізично використано vs логічно використано може відрізнятись через компресію і знімки. Розрахунок запасу місця має враховувати поведінку знімків під час перепису.

Рішення: Якщо AVAIL обмежений, уникайте підходів, які дублюють дані (rsync в тому самому датасеті, копіювання файлів, наївний перепис).

Завдання 12: Перевірте, чи використовується special_small_blocks

cr0x@server:~$ zfs get -o name,property,value special_small_blocks tank
NAME  PROPERTY              VALUE  SOURCE
tank  special_small_blocks  0      default

Значення: special_small_blocks=0 означає, що на special vdev йде лише метадані (якщо він присутній). Якщо це встановлено (наприклад, 16K), то малі блоки файлів можуть йти на special-пристрої.

Рішення: Якщо special_small_blocks увімкнено і special vdev гарячий, подумайте про підвищення recordsize або коригування special_small_blocks, щоб не штовхати занадто багато даних на special.

Завдання 13: Підтвердіть, що саме змінилося при встановленні recordsize (джерела властивостей мають значення)

cr0x@server:~$ zfs get -r -o name,property,value,source recordsize tank | head
NAME           PROPERTY    VALUE  SOURCE
tank           recordsize  128K   default
tank/app       recordsize  16K    local
tank/app/logs  recordsize  16K    inherited from tank/app

Значення: Ви змінили tank/app і дочірній успадкував це. Це може бути коректно — або випадковою побічною дією.

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

Завдання 14: Зробіть контрольний тест «перепис підмножини» в жертвенній директорії

cr0x@server:~$ zfs create -o recordsize=16K tank/app_test
cr0x@server:~$ rsync -aHAX --info=progress2 /tank/app/hotset/ /tank/app_test/hotset/
sending incremental file list
      3.42G  12%    95.11MB/s    0:00:32
     28.40G 100%   102.88MB/s    0:04:38 (xfr#231, to-chk=0/232)

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

Рішення: Пропрофілюйте додаток проти цього тестового датасету. Якщо покращення є — у вас є доказ. Якщо ні — припиніть звинувачувати recordsize.

Жарт №1: Змінювати recordsize і чекати, що це змінить старі дані — це як змінити марку кави в офісі і чекати, що зникнуть аварії минулого кварталу.

Стратегії міграції, що не переписують все

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

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

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

Де це працює:

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

Що робити:

  • Встановіть новий recordsize.
  • Переконайтесь, що нові записи потрапляють у правильний датасет (за потреби окремі mountpoint-и).
  • Спостерігайте розподіл розмірів блоків на нових файлах протягом днів/тижнів.

Стратегія B: Розділяйте датасети за навантаженням, а не за орг-структурою

Більшість провалів через recordsize відбуваються тому, що хтось намагався змусити один датасет обслуговувати двох «панів»: дрібні випадкові IO і великі послідовні потоки.
Не робіть так. Використовуйте кілька датасетів з різними властивостями і переміщуйте дані по межах, які відповідають їх використанню.

Практичні розділення, що працюють:

  • db/ (дробні випадкові): recordsize 16K–32K часто має сенс для OLTP-патернів, але вимірюйте.
  • wal-or-redo/ (послідовне додавання, sync): зазвичай хочеться більших записів і ретельного ставлення до sync/SLOG.
  • backups/ (великий послідовний потік): recordsize 1M може бути відмінним.
  • home/ або shared/ (змішані навантаження): залишайте дефолт 128K, якщо немає доказів інакшого.

Розділення датасетів дає вам: окремі знімки, окремі квоти, окремі резервування і — критично — окреме тонке налаштування.
Це те, як серйозні інженери експлуатують ZFS у продакшені.

Стратегія C: Цілеспрямоване переписування лише гарячих даних

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

Техніки:

  • Створіть новий датасет з бажаним recordsize.
  • Копіюйте гарячий деревоподібний каталог (rsync або експорт/імпорт на рівні додатка).
  • Переключіть mountpoint-и або межі через symlink-и (використовуйте mountpoint-и; symlink-и плутають людей о 3:00 ночі).
  • Тримайте старі дані доступними для відкату, але не залишайте їх змонтованими там, де додаток може випадково ними користуватися.

Для баз даних інструменти на рівні додатка (dump/restore, фізичне відновлення, релокація tablespace) часто дають чистіший перепис, ніж копіювання на файловому рівні.
Вони також дозволяють базі даних перепакуватися і зменшити внутрішню фрагментацію. File-level rsync грубий; іноді грубий підхід підходить.

Стратегія D: Перепис «на місці» копіюванням файл-файл (використовуйте обережно)

Класичний трюк: копіювати файл на самого себе через тимчасове ім’я, щоб ZFS зарезервував нові блоки під новим recordsize.
Це може працювати для обмеженого набору файлів, якщо ви можете терпіти додаткове місце і IO.

Недоліки:

  • Знімки тримають старі блоки живими. Використання простору може вибухнути.
  • Ви можете випадково спричинити масивне write amplification і фрагментацію.
  • Складно безпечно виконати в масштабі без плану оркестрації.

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

Стратегія E: ZFS send/receive як механізм міграції

ZFS send/receive відмінно підходить для переміщення датасетів між пулами або реорганізації дерева датасетів. Але сам по собі він не магічно перезаписує розмір блоків.
Send/receive відтворює вміст датасету і зазвичай зберігає структуру блоків у стрімі, тобто не «перепишеться» під інший recordsize.

Для чого send/receive таки чудовий у цьому контексті:

  • Переміщення даних на новий пул з іншим макетом vdev, кращим для вашого IO-патерну (mirror-и vs RAIDZ).
  • Реорганізація датасетів так, щоб майбутні записи йшли за правильними політиками.
  • Надання чистої точки відкату: якщо план перепису пішов не так, ви можете відкотитися, змонтувавши отриманий датасет.

Стратегія F: Не переносити recordsize; переносіть навантаження

Іноді вузьке місце не в recordsize. Воно в:

  • Однопотоковому додатку, що виконує дрібні синхронні записи.
  • Базі даних з IO-планувальником, ворожим до SSD.
  • VM, що робить 4K випадкові записи на RAIDZ-пулі без SLOG і з частими знімками.

Якщо ви можете змінити навантаження (пакетні записи, змінити розмір сторінки бази даних, відокремити WAL в інший датасет),
ви можете залишити recordsize і отримати покращення без ризикованих переписів.

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

Міні-історія 1: Інцидент через хибне припущення

Середня компанія експлуатувала внутрішню аналітичну платформу на ZFS. Команда зберігання помітила зростання латентності запитів і почула низку блог-постів:
«Бази даних люблять менший recordsize.» Хтось поміняв recordsize датасету з 128K на 16K посеред релізного циклу.

Нічого не покращилося. Через два дні стало гірше. Команда вирішила, що «трюк з 16K» не працює, і відкотила зміни.
Відкат теж нічого не дав. Тепер було дві зміни і нуль причинно-наслідкових зв’язків.

Насправді проблема була в наборі великих послідовних сканів, що конфліктували з нічною задачею, яка писала великі parquet-файли і відразу їх перечитувала.
Гарячі файли були здебільшого щойно згенеровані і стримінгові; 16K записи підвищили метадані і зменшили ефективність read-ahead.
Пік латентності корелював із тією задачею, а не з випадковими операціями бази даних.

Хибне припущення було в тому, що «база даних = випадкові IO», але навантаження не було OLTP; воно було аналітичним із сильними послідовними читаннями.
Виправлення було нудне: залишити recordsize 128K (або більше для датасету parquet), ізолювати виходи задач у свій датасет
і запланувати суперечливі задачі так, щоб вони не задавлювали одні й ті ж черги vdev.

Пост-інцидентний пункт дій, який залишився: будь-яка зміна recordsize вимагає двох вимірювань — розподілу розміру IO додатка і ZFS-рівневих ops/пропускної здатності — до і після.
Ніяких вимірювань — ніяких змін.

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

Інша організація мала великий репозиторій резервних копій на ZFS і хотіла швидшої реплікації. Хтось встановив recordsize 1M для бекап-датасету.
Пропускна для реплікації зросла. Всі святкували і розходились по домівках рано — це якраз запрошення до проблем.

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

Повернення удару полягало не в тому, що 1M «завжди погано». Проблема в тому, що датасет не був лише бекапами; там також зберігався величезний каталог дрібних файлів, які часто використовувалися під час відновлень.
З великими записами читання крихітного файлу тягнуло за собою більше даних, ніж потрібно, і створювало зміну ARC. Система робила більше роботи за той самий результат.

Рішення — розділити репозиторій: один датасет для великих послідовних бекап-потоків (1M), інший для каталогів дрібних файлів та індексів (128K або менше).
Реплікація залишилась швидкою, шлях відновлення перестав каратися при доступі до дрібних файлів.

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

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

Регульована компанія запускала клієнтські сервіси на ZFS зі строгим контролем змін. Вони хотіли мігрувати датасет із VM-образами
з застарілого пулу на новий з швидшим обладнанням. Також була бажанка «пофіксити recordsize, поки ми це робимо», бо так завжди буває.

Команда зробила непопулярну дію: побудувала staging-датасет і протягом тижня запускала на ньому канаркове навантаження.
Вони збирали zpool iostat, розміри IO гостей і p99 латентність додатків. Також задокументували процедуру відкату
і протестували її двічі.

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

Вони мігрували, створюючи нові zvol з правильним volblocksize, виконали копії на рівні зберігання і переключили гіпервізор на нові томи.
Нудно, уважно і безперечно правильно. Це також дозволило уникнути простою через когось, хто намагався тонувати recordsize не на тому типі об’єкта.

Тижневий канар здавався довгим. Він врятував їх від набагато довшого тижня пізніше: того, де ви перебудовуєте системи під тиском.

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

Це часто зустрічається в реальних таймлайнах інцидентів. Симптоми знайомі. Корені зазвичай можна уникнути. Фікси — конкретні.

1) «Ми змінили recordsize, але продуктивність зовсім не змінилася.»

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

2) «Малі випадкові записи повільні; ми знизили recordsize і все ще повільно.»

  • Симптоми: Висока латентність, багато операцій, низька пропускна здатність; RAIDZ-пул; часті перезаписи; можливо інтенсивні знімки.
  • Корінь: Витрати паритету read-modify-write домінують; синхронні записи можуть змушувати поведінку ZIL; знімки збільшують фрагментацію.
  • Виправлення: Розгляньте mirror-и для навантажень із випадковими записами; ізолюйте sync-важкі компоненти; оцініть SLOG; зменшіть частоту знімків на гарячих даних.

3) «Пропускна здатність впала після встановлення recordsize 16K.»

  • Симптоми: Послідовні задачі повільніші; більше CPU в ядрі; вища IO метаданих; нижча ефективність prefetch.
  • Корінь: Датасет — послідовний/стрімінговий, але recordsize зменшили, що збільшило кількість блоків і накладні витрати.
  • Виправлення: Відновіть більший recordsize (128K–1M) для стрімінгових датасетів; розділіть навантаження по різних датасетах.

4) «Ми переписали дані і закінчилось місце.»

  • Симптоми: Пул заповнюється під час міграції; видалення не звільняє простір; все швидко погіршується.
  • Корінь: Знімки фіксують старі блоки; перепис дублює дані; недостатньо запаса місця.
  • Виправлення: Скоригуйте утримання знімків на час міграції; клонування в новий датасет і cutover; додайте тимчасову ємність; плануйте запас перед переписом.

5) «Special vdev наситився після налаштування recordsize.»

  • Симптоми: Special-пристрій показує високий %util; латентність метаданих стрибнула; загальна латентність пула зросла.
  • Корінь: special_small_blocks спричинив потрапляння більше блоків на special vdev; розподіл малих блоків змінився через recordsize.
  • Виправлення: Перегляньте поріг special_small_blocks; перемістіть дані з великою кількістю малих блоків з special; забезпечте достатню продуктивність і ресурсність special vdev.

6) «Ми налаштовували recordsize для VM і нічого не змінилося.»

  • Симптоми: Продуктивність VM незмінна; ви змінили recordsize на датасеті, де зберігаються zvol або sparse-файли з іншими патернами IO.
  • Корінь: VM знаходяться на zvol, де важливий volblocksize; або гіпервізор використовує direct IO, не погоджений з вашими припущеннями.
  • Виправлення: Налаштовуйте volblocksize при створенні zvol; використовуйте окремі датасети для образів VM, якщо вони файлові; вимірюйте IO розміри гостя.

Жарт №2: Найшвидший спосіб дізнатися, що у вас недостатньо запасу в пулі — почати «швидкий» перепис о 16:55 в п’ятницю.

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

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

Чекліст 1: Вирішіть, чи recordsize — справді важіль

  1. Виміряйте розподіл розміру IO (iostat -x, метрики додатка, метрики гіпервізора).
  2. Виміряйте ops vs bandwidth на ZFS (zpool iostat -v).
  3. Визначте, чи ви обмежені sync (шукайте fsync-важку поведінку; перевірте наявність/продуктивність SLOG).
  4. Підтвердіть тип датасету (filesystem vs zvol).
  5. Підтвердіть знімки і запас місця для будь-якого плану перепису.

Чекліст 2: Оберіть стратегію міграції

  • Зміна політики (без перепису): Використовуйте, коли дані природно churn-яться або гарячі дані — здебільшого нові записи.
  • Розділення датасетів: Використовуйте, коли у вас змішані навантаження в одному датасеті.
  • Цілеспрямоване переписування: Використовуйте, коли конкретний каталог/набір файлів спричиняє проблему.
  • Міграція навантаження: Використовуйте, коли додаток можна змінити (пакетні записи, розмір сторінки, відокремлення WAL).
  • Міграція макету пулу: Використовуйте, коли проблема в паритетних витратах і потрібні mirror-и або інше обладнання.

Чекліст 3: Виконання безпечного цілеспрямованого cutover-а

  1. Створіть новий датасет з бажаним recordsize та іншими властивостями (compression, atime, xattr тощо).
  2. Запустіть невеликий канар-копію і перевірте розміри блоків на декількох гарячих файлах (zdb -ddddd).
  3. Пропрофілюйте додаток проти нового місця (той самий набір запитів, те саме навантаження VM, та сама задача бекапу).
  4. Сплануйте політику знімків: зменште утримання під час міграції або забезпечте достатній запас.
  5. Rsync/копіюйте гарячий піднабір; перевірте контрольні суми або цілісність на рівні додатка.
  6. Переключіть mountpoint; залиште старий датасет в режимі лише для читання для відкату.
  7. Моніторте p95/p99 латентність, глибину черги пула, special vdev, SLOG якщо присутній.
  8. Після стабілізації — виведіть старі дані і відновіть політику знімків.

Чекліст 4: Валідація після зміни (не пропускайте нудну частину)

  1. Підтвердіть джерела властивостей: recordsize встановлений там, де треба, і успадкований там, де треба.
  2. Підтвердіть, що гарячі файли записані за новою політикою.
  3. Підтвердіть, що ніякі несподівані датасети не успадкували зміну.
  4. Порівняйте метрики до/після: ops, bandwidth, latency, CPU.
  5. Задокументуйте фінальну розкладку датасетів і чому для кожного обраний саме такий recordsize.

FAQ

1) Якщо я зміню recordsize, чи мої існуючі файли автоматично перемастерингуються?

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

2) Який recordsize слід використовувати для баз даних?

Це залежить від патернів доступу і розміру сторінки бази даних. Багато OLTP-навантажень виграють від менших recordsize (зазвичай 16K–32K),
але аналітичні сканування часто віддають перевагу більшим. Спочатку вимірюйте розміри IO і латентність; потім тестуйте на підмножині датасету.

3) Чи завжди 1M recordsize найкращий для бекапів?

Часто — так, але не завжди. Якщо датасет справді складається з великих послідовних потоків, 1M може підвищити пропускну здатність і стиснення.
Якщо там також є дрібні часто доступні каталоги або індекси, розділіть їх в інший датасет.

4) Чи може ZFS send/receive переписати дані у новий recordsize?

Сам по собі — ні. Send/receive зберігає існуючу блокову структуру у стрімі.
Використовуйте send/receive для переміщення датасетів між пулами або реорганізації; використовуйте цілеспрямовані переписи або відбудову на рівні додатка, щоб змінити блокування на диску.

5) Чому іноді зменшення recordsize робить швидкість гіршою?

Менші записи збільшують кількість блоків, накладні витрати на метадані і можуть знизити ефективність read-ahead для послідовних навантажень.
Ви міняєте ефективність випадкових оновлень на пропускну здатність послідовного доступу. Якщо ваше навантаження переважно стрімінгове, малий recordsize — самосаботаж.

6) Як знімки впливають на міграцію recordsize?

Знімки тримають старі блоки живими. Якщо ви переписуєте дані, щоб отримати нові розміри блоків, ви виділяєте нові блоки, а знімки утримують старі.
Використання простору може різко зрости. Плануйте запас і політику знімків перед переписом.

7) Чи слід налаштовувати recordsize для дисків VM?

Якщо диски VM — це zvol, recordsize не має значення; важливий volblocksize, який зазвичай обирається при створенні zvol.
Якщо диски VM — це файли в файловому датасеті, recordsize може впливати — але шаблони IO гостя все одно вирішують. Вимірюйте їх.

8) Як визначити, чи мої гарячі файли використовують новий recordsize?

Використовуйте zdb -ddddd на репрезентативних файлах і дивіться розмір блоків даних (наприклад, 16K vs 128K).
Також скопіюйте невеликий тестовий набір у новий датасет з потрібним recordsize і порівняйте продуктивність.

9) Чи змінює компресія «правильний» recordsize?

Може. Великі записи часто краще стискаються, що може зменшити фізичні IO.
Але якщо ви часто читаєте дрібні фрагменти, ви можете платити за декомпресію і read amplification. Тестуйте з реальними патернами доступу.

10) Якщо recordsize — лише максимум, чому люди так за ним стежать?

Бо він сильно впливає на те, як ZFS розміщує великі файли, а в великих файлах і ховаються проблеми продуктивності:
бази даних, образи VM, великі логи, бекапи, індекси. Хвилювання виправдане; спрощені поради — ні.

Практичні подальші кроки

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

  1. Спочатку виміряйте: зберіть розподіл розміру IO, ops/bandwidth пула і поведінку sync.
  2. Окресліть зміни: підтвердіть типи датасетів і спадкування, щоб випадково не «оптимізувати» логи в регресію.
  3. Оберіть найменш ризиковий шлях: policy-only для churn-даних, цілеспрямовані переписи для гарячих піднаборів, розділення датасетів для змішаних навантажень.
  4. Підтвердіть доказами: zdb для розмірів блоків, p99 латентність додатка для впливу на користувача і zpool iostat для поведінки пула.
  5. Запишіть, що зробили і чому: майбутній ви буде втомленим, а втомлені заслуговують на хорошу документацію.
← Попередня
PCIe 3/4/5/6: що змінюється для реальних користувачів
Наступна →
Ubuntu 24.04: монтування CIFS повертає «Permission denied» — точні опції для виправлення

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