Найдорожчі інциденти з ZFS рідко починаються з відмов дисків. Вони починаються з впевненості.
Хтось підправляє один датасет «щоб виправити цю одну річ», а через тиждень половина флоту
працює повільніше, завдання резервного копіювання вибухають, або при завантаженні зникає монтування.
Зовні нічого явно «зламаного» немає. ZFS все ще здоровий. Пул ONLINE. І все ж система поводиться інакше.
Причина часто — успадкування властивостей ZFS: функція, яку ви любите при чистому масштабуванні —
і та сама функція, яка змінює дочірній датасет мовчки, коли ви цього не мали на увазі.
Якщо ви керуєте продакшном, ставтеся до успадкування як до таблиць маршрутизації: потужне, глобально впливаюче,
і не те, що «просто спробувати».
Успадкування — і можливість, і пастка
Датасети ZFS (файлові системи та томи) живуть у дереві. Властивості існують на вузлах цього дерева.
Деякі властивості успадковуються вниз по дереву, якщо їх не переоприділити локально. Це навмисно: так ви можете
встановити політику на батьківському датасеті, і всі діти їй підпорядкуються.
Пастка — людська: ми мислимо «цей датасет», а ZFS мислить «цей піддерево». Ви встановлюєте властивість на tank/apps,
щоб вирішити проблему додатка, і водночас змінюєте поведінку tank/apps/postgres, tank/apps/redis
і випадковий забутий tank/apps/tmp, який CI runner навантажує вже два роки.
Успадкування — не лише про «приємні значення за замовчуванням». Воно може змінити поведінку монтування, продуктивність, видимість
снапшотів, семантику доступу, обробку ключів шифрування та гарантії простору. Тобто успадкування може:
- швидко виправляти речі при свідомому використанні,
- викликати інциденти «ніхто не чіпав цей датасет» при випадковому застосуванні,
- створювати конфігураційний дрейф, який важко відлагодити («він успадкований звідки?»).
Одне операційне правило, яке мені подобається: якщо ви не можете назвати батьківські датасети, які постраждають від вашої зміни,
ви не готові виконувати zfs set.
Жарт №1: успадкування ZFS — як офісний Wi‑Fi: усі виграють, поки хтось «оптимізує» його і весь поверх починає буферизувати.
Єдина модель мислення, що масштабується: local vs inherited vs default
Коли ви запитуєте властивість, ZFS може показати значення і джерело. Саме джерело вас рятує:
- local: явне встановлення на цьому датасеті
- inherited from <dataset>: успадковано від предка
- default: вбудований за замовчуванням для вашої реалізації/версії ZFS
- temporary: встановлено під час виконання (рідше; думайте про опції монтування в деяких стеках)
Ось чому це важливо в продакшені: два датасети можуть показувати однакове значення, але поводитися по-різному
операційно, бо джерело підказує, що трапиться при наступному вікні змін.
Значення за замовчуванням залишається стабільним, поки ви його не зміните. Успадковане значення може змінитися, коли хтось змінить батька.
Локальне значення «липке», але може стати застарілою політикою.
Правила успадкування, що важливі в реальному житті
- Більшість властивостей успадковувані; не всі. У ZFS є властивості лише для читання або властивості, що за своєю природою локальні (наприклад,
used,available). - Діти успадковують, якщо не переоприділено локально. Якщо у дитини є локальне значення, зміни батька її не зачеплять.
zfs inheritвидаляє локальний оверрайд. Воно не «встановлює значення за замовчуванням». Воно видаляє локальне налаштування, і значення стає успадкованим (або default, якщо нема, від кого успадкувати).- Клони ускладнюють картину. Клони народжуються як діти origin snapshot; вони все одно існують у дереві датасетів і тому успадковують від батьків, як і всі інші.
- Властивості, пов’язані з монтуванням, можуть фейлити так, що схоже на проблеми systemd. Це не systemd. Це ZFS, який ввічливо відмовляється монтувати те, що ви попросили монтувати.
Цитата, яку можна використати в роботі зі своєю командою зберігання
В. Едвардс Демінг (парафразована ідея): «Система ідеально спроектована для отримання тих результатів, які вона дає.»
Сюрпризи від успадкування — це ваша система, що працює саме так, як задумано.
Властивості, що боляче кусають (і чому)
1) mountpoint, canmount і readonly: тріо «куди поділася моя файловa система?»
Якщо ви керуєте серверами, ви хоч раз дебагили «він монтувався вчора». Поведінка монтування ZFS керується властивостями.
Батьківський датасет з canmount=off може бути чистою адміністративною межею — поки він не встановлений не там,
і діти перестають монтуватися при завантаженні.
mountpoint за замовчуванням успадковується, що зручно і небезпечно. Якщо ви встановите батьку
mountpoint в /srv, ви можете очікувати, що переміститься лише батько. Натомість діти змонтуються
під ним, якщо у них немає локальних mountpoint. Чудово, коли це сплановано; хаос, коли ні.
2) compression: невеликий перемикач, великий радіус ураження
Стиснення успадковується. Це корисно: можна ввімкнути compression=lz4 на батьку і отримати переваги широко.
Сюрприз: ви також можете вимкнути його для піддерева і тихо роздути використання ємності.
Гірше, ви можете змінити його для датасету, що містить і «гарячу базу даних», і «холодні логи», і тоді ваше налаштування стане компромісом, якого ви не бажали.
3) recordsize: тюнінг продуктивності, що перетворюється на деградацію
recordsize успадковується для файлових систем. Неправильна установка на батьку призведе до того, що діти успадкують розмір запису,
який може бути жахливим для їхнього навантаження. Наприклад, каталог даних PostgreSQL зазвичай віддає перевагу меншим блокам для випадкових I/O;
медіатека любить великі блоки. Дерево датасетів не має змушувати їх носити однаковий «розмір одягу».
4) atime: смерть від мільйона маленьких записів
Оновлення часу доступу (atime=on) може генерувати запис при читанні. На завантажених датасетах із великою часткою читань
(поштові скриньки, веб-кеші, дерева з великою кількістю метаданих) успадкований atime=on може перетворити «читання дешеве»
на «читання — це також запис», що впливає на затримки та знос.
5) snapdir і видимість снапшотів
snapdir=visible робить .zfs/snapshot видимим. Це іноді корисно для самостійних відновлень,
а інколи створює ризики відповідності або операційні проблеми, коли програми обходять ці каталоги.
Успадкування означає, що ви можете показати снапшоти там, де не хотіли, або приховати там, де розраховували на їхню наявність.
6) Квоти, reservation, refreservation: «простір» — це політика
Квоти та резервації можуть успадковуватися залежно від типу властивості та вашого дизайну. Навіть коли вони не успадковуються,
їхня взаємодія з алокацією батька/дитини — джерело сюрпризів. Керування простором не інтуїтивне під тиском.
Ви можете опинитися з вільним місцем на рівні пулу, але «немає місця» для датасету через резервації,
або з дитячим датасетом, що росте без обмежень, бо ви припускали, що квота на батьку захистить його.
7) Властивості шифрування: успадкування з гострими краями
Вбудоване шифрування ZFS вводить успадковувані властивості, як-от encryption, keylocation,
keyformat і інші. Суть поведінки: діти можуть успадковувати налаштування шифрування від батьків при створенні.
Але змінити шифрування не так просто, як стиснення; ви не можете просто увімкнути його для існуючого датасету без
міграції через send/receive. Люди все одно пробують. Сюрприз тут часто — процесова невідповідність: очікувати, що успадкування
ретроакторно захистить дані.
Жарт №2: змінити recordsize на батьківському датасеті — як купити всім співробітникам однакові крісла — хтось точно поскаржиться.
Цікаві факти та історичний контекст
- ZFS проектували, щоб замінити стек, а не лише файлову систему. Він поєднав управління томами та семантику файлової системи, тому «властивості» стали системними важелями політики.
- Властивості створювали для делегування. Ранні розгортання ZFS потребували способу дозволити командам самостійно керувати датасетами без ризику; успадкування стало механізмом масштабування.
- Вибір значень за замовчуванням для стиснення змінювався з часом. Багато сучасних стеків рекомендують
lz4загалом; старіші поради були обережніші через дорожчий CPU і відмінності алгоритмів. recordsizeпоходить зі світу великих послідовних I/O. ZFS оптимізували під потокові навантаження; бази даних змусили спільноту дисципліновано ставитися до налаштувань на рівні датасету.- За замовчуванням
atimeвідображає Unix-традицію, а не реальність продуктивності. Час доступу був корисним для інструментів і політик, але сучасні системи часто платять за нього занадто високу ціну. - Поведінка монтування колись була більш «магічною». Різні інтеграції ОС (спадщина Solaris vs Linux ZFS) впливали на те, як властивості взаємодіють із завантаженням і сервісами монтування.
- Видимість снапшотів — предмет культурної війни. Адміни люблять
.zfs/snapshotдля відновлень; власники додатків ненавидять несподівані каталоги. Властивість існує, бо обидві сторони праві. - Джерела властивостей — подарунок для дебагу, доданий на ранньому етапі. «local vs inherited vs default» — одне з найкращих UX-рішень для операторів у сфері зберігання.
Практичні завдання (команди, виводи, рішення)
Це перевірки, які я виконую, коли хтось каже «ZFS щось змінив». Кожне завдання містить: команду,
що означає її вивід, і яке рішення прийняти далі.
Завдання 1: Побачити значення властивості і звідки вона
cr0x@server:~$ zfs get -o name,property,value,source compression tank/apps/postgres
NAME PROPERTY VALUE SOURCE
tank/apps/postgres compression lz4 inherited from tank/apps
Значення: Датасет Postgres стиснутий, бо його батько — ні. Якщо ви зміните tank/apps,
ви зміните Postgres. Рішення: якщо Postgres потребує особливої поведінки, встановіть локальний оверрайд на
tank/apps/postgres і не покладайтеся на батька для цієї властивості.
Завдання 2: Аудит піддерева за властивістю (швидко виявити дрейф)
cr0x@server:~$ zfs get -r -o name,property,value,source recordsize tank/apps
NAME PROPERTY VALUE SOURCE
tank/apps recordsize 128K local
tank/apps/postgres recordsize 128K inherited from tank/apps
tank/apps/redis recordsize 128K inherited from tank/apps
tank/apps/artifacts recordsize 128K inherited from tank/apps
Значення: Одне локальне налаштування на tank/apps диктує всьому піддереву.
Рішення: або прийняти це як політику, або рознести спеціальні датасети з локальними recordsize
(наприклад, Postgres на 16K або 8K залежно від сховища та навантаження).
Завдання 3: Знайти все, що успадковує від конкретного батька (радіус ураження)
cr0x@server:~$ zfs get -r -H -o name,source mountpoint tank/apps | awk '$2 ~ /tank\/apps/ {print $1, $2}'
tank/apps/postgres inherited from tank/apps
tank/apps/redis inherited from tank/apps
tank/apps/artifacts inherited from tank/apps
Значення: Ці датасети перемістяться, якщо ви зміните mountpoint у tank/apps.
Рішення: не чіпайте mountpoint на батьку без вікна техобслуговування та плану відкату.
Завдання 4: Перевірити, що змонтується, а що ні
cr0x@server:~$ zfs get -r -o name,canmount,mountpoint,mounted tank/apps
NAME CANMOUNT MOUNTPOINT MOUNTED
tank/apps on /srv/apps yes
tank/apps/postgres on /srv/apps/postgres yes
tank/apps/redis on /srv/apps/redis yes
Значення: Усі датасети можуть монтуватися і змонтовані.
Рішення: якщо чогось не вистачає, шукайте canmount=off або mountpoint=none
і відстежуйте, звідки це успадковано.
Завдання 5: Виявити «ми змінили mountpoint батька» до перезавантаження
cr0x@server:~$ zfs set mountpoint=/srv tank/apps
cr0x@server:~$ zfs mount -a
cannot mount 'tank/apps/postgres': mountpoint or dataset is busy
Значення: Нова схема монтування конфліктує з існуючими монтуваннями або процесами.
Рішення: відкотіть mountpoint батька негайно, або зупиніть сервіси коректно і перемонтуйте
в контрольованому вікні. Не намагайтесь «форсувати» в польоті.
Завдання 6: Виявити локальні оверрайди (місця, куди політика не дістає)
cr0x@server:~$ zfs get -r -H -o name,property,value,source compression tank | awk '$4=="local"{print}'
tank/apps compression lz4 local
tank/backups compression off local
Значення: Лише два датасети мають явні налаштування стиснення; усе інше успадковано або за замовчуванням.
Рішення: підтвердьте, що ці локальні оверрайди навмисні. Локальні оверрайди — це місця, де «стандарти»
мовчки перестають діяти.
Завдання 7: Скинути дочірній датасет щоб знову успадковував (контрольоване «унду-кастомізація»)
cr0x@server:~$ zfs get -o name,property,value,source atime tank/apps/postgres
NAME PROPERTY VALUE SOURCE
tank/apps/postgres atime off local
cr0x@server:~$ zfs inherit atime tank/apps/postgres
cr0x@server:~$ zfs get -o name,property,value,source atime tank/apps/postgres
NAME PROPERTY VALUE SOURCE
tank/apps/postgres atime on inherited from tank/apps
Значення: Ви видалили локальний оверрайд, і тепер Postgres слідує за батьком.
Рішення: робіть це лише якщо впевнені, що політика батька підходить для навантаження. «Inherit»
— це не безкоштовна кнопка очищення.
Завдання 8: Побачити, які властивості встановлені локально на датасеті (швидкий профіль)
cr0x@server:~$ zfs get -s local all tank/apps/postgres | head
NAME PROPERTY VALUE SOURCE
tank/apps/postgres atime off local
tank/apps/postgres logbias latency local
tank/apps/postgres primarycache metadata local
tank/apps/postgres recordsize 16K local
Значення: Це свідомі відхилення від успадкованої/дефолтної політики.
Рішення: якщо ви не очікували цього, у вас конфігураційний дрейф. Простежте, хто їх встановив і чому.
Якщо очікували — задокументуйте, бо хтось пізніше «очистить» їх.
Завдання 9: Використати zfs diff щоб зрозуміти зміни видимі у снапшотах (перевірка операційної реальності)
cr0x@server:~$ zfs diff tank/apps/postgres@before-change tank/apps/postgres@after-change | head
M /srv/apps/postgres/postgresql.conf
+ /srv/apps/postgres/pg_wal/0000000100000000000000A1
- /srv/apps/postgres/tmp/old.sock
Значення: Це відстеження змін на рівні файлів між снапшотами, а не змін властивостей.
Рішення: якщо продуктивність змінилася, але дані не змінились, швидше за все ви дивитесь на успадкування властивостей або поведінку ядра/модуля — не на «хтось редагував файли».
Завдання 10: Підтвердити розклад датасетів і виявити дітей, яких не повинно бути
cr0x@server:~$ zfs list -r -o name,used,avail,refer,mountpoint tank/apps
NAME USED AVAIL REFER MOUNTPOINT
tank/apps 120G 2.1T 256K /srv/apps
tank/apps/postgres 80G 2.1T 80G /srv/apps/postgres
tank/apps/redis 2G 2.1T 2G /srv/apps/redis
tank/apps/tmp 35G 2.1T 35G /srv/apps/tmp
Значення: Важкий tmp датасет всередині піддерева apps — класична пастка успадкування.
Рішення: перемістіть епhemeral навантаження в окрему доменну політику (або встановіть локальні оверрайди),
особливо для recordsize, sync і atime.
Завдання 11: Перевірити, чи квоти або резервації тихо обмежують дітей
cr0x@server:~$ zfs get -r -o name,quota,reservation,refreservation,used,available tank/apps/postgres
NAME QUOTA RESERVATION REFRESERVATION USED AVAILABLE
tank/apps/postgres none 100G none 80G 20G
Значення: Ви зарезервували 100G; датасет має лише 20G «доступного» навіть якщо пул має більше.
Рішення: якщо Postgres наближається до 100G і ви не планували жорсткий тиск на простір, відкоригуйте або приберіть резервацію.
Якщо ви навмисне це зробили — налаштуйте оповіщення; резервації — це політика, а не дрібниця.
Завдання 12: Перевірити стиснення і логічний простір (допомагає чи заважає?)
cr0x@server:~$ zfs get -o name,compressratio,used,logicalused compression,compressratio tank/apps/artifacts
NAME PROPERTY VALUE SOURCE
tank/apps/artifacts compression lz4 inherited from tank/apps
tank/apps/artifacts compressratio 1.72x -
Значення: Стиснення увімкнено й ефективне. Якщо compressratio ≈1.00x, ви витрачаєте CPU заради малої вигоди.
Рішення: тримайте стиснення для більшості змішаних навантажень; вимикайте лише за доказами, а не на відчуття.
Завдання 13: Виявити колізії mountpoint (поширений режим відмови успадкування)
cr0x@server:~$ zfs get -r -H -o name,value mountpoint tank | sort -k2 | awk 'prev==$2{print "collision:", prev, "between", prevname, "and", $1} {prev=$2; prevname=$1}'
collision: /srv/apps between tank/apps and tank/apps/legacy
Значення: Два датасети мають однаковий mountpoint. ZFS не буде радо монтувати обидва.
Рішення: виправте mountpoint до дня перезавантаження. Колізії часто проявляються як «випадкові відсутні монтування»
залежно від порядку монтування.
Завдання 14: Підтвердити, які властивості успадковувані (знати правила гри)
cr0x@server:~$ zfs get -H -o property,values,source all tank/apps | head -n 8
type filesystem -
creation Wed Nov 13 10:12 2024 -
used 120G -
available 2.1T -
referenced 256K -
compressratio 1.23x -
mounted yes -
quota none default
Значення: Деякі властивості показують змістовне джерело; інші динамічні і не мають.
Рішення: зосередьте аудити успадкування на поведінкових властивостях (compression, recordsize, atime,
mountpoint, canmount, snapdir, sync, logbias, primarycache/secondarycache, xattr, acltype, властивості шифрування).
Швидкий план діагностики
Це потік триажу «повільно / не монтується / закінчився простір», який швидко приведе вас до підозри про успадкування.
Виконуйте його як чекліст. Швидкість важлива; коректність — ще важливіша.
Перше: визначте область (один датасет чи піддерево?)
- Знайдіть датасет(и), що підтримують шлях: перевірте відображення mountpoint і
zfs list. - Визначте, чи також постраждали сусіди (успадкування часто б’є по сусідах).
- Запитайте «який батько об’єднав би ці датасети?» Саме там, ймовірно, сталася зміна.
Друге: перевірте джерела властивостей для головних підозрюваних
- Проблеми монтування:
mountpoint,canmount,readonly,overlay(якщо застосовно), та колізії. - Регрес продуктивності:
recordsize,compression,atime,sync,logbias,primarycache,secondarycache. - Сюрпризи з простором:
quota,refquota,reservation,refreservation.
Третє: підтвердьте, що зміни політики, а не апаратури
- Стан пулу:
zpool statusмає бути чистим; якщо ні, ви дебагите не ту проблему. - Порівняння дерева датасетів: зіставте джерела властивостей між «хорошими» і «поганими» датасетами.
- Шукайте недавню активність адміністраторів: історія shell, нотатки change management, коміти автоматизації.
Четверте: виправлення з найменшим радіусом ураження
- Якщо окремий дитина потребує іншої поведінки — встановіть локальний оверрайд на дитині.
- Якщо політика неправильна для багатьох дітей — змініть батька, але лише після аудиту піддерева.
- Якщо не можете безпечно вирішити — відновіть попередні значення (відкат зміни властивості) і перегляньте з даними.
Три корпоративні міні-історії з шахт успадкування
Міні-історія 1: Інцидент через хибне припущення
Середня SaaS-компанія мала акуратний розклад ZFS: tank/apps для стану додатків, з дочірніми
для Postgres, Redis і блоб-стору. Нового SRE попросили «зробити резервні копії легше знаходити» на хості,
що використовується кількома командами. Він помітив, що батьківський датасет змонтований у /srv/apps і захотів, щоб усе
було під /srv для «послідовності».
Він виконав одну команду: встановив mountpoint=/srv на tank/apps. Система не вибухнула одразу,
бо більшість монтувань вже були активні. Зміна лежала там, як шкірка від банана.
Тієї ночі рутинна перезавантаження після патчу ядра спричинила перемонтування. Деякі датасети не змогли змонтуватися через
«busy» цілі, інші стикнулися з колізіями каталогів, і systemd-юніти побігли вперед, припускаючи, що шляхи існують.
Квиток інциденту читався як збірка жахів: «Postgres не стартує», «Redis не знайдено», «додаток не може знайти завантаження».
Пул був здоровий. Дискові I/O виглядали нормально. Проблема була в тому, що шляхи не там, де сервіси їх очікували.
Виправлення було простим, але принизливим: відкотити mountpoint батька, встановити локальні mountpoint лише там, де потрібно,
і додати перевірку перед перезавантаженням, що порівнює поточні mountpoint зі схваленим інвентарем. Суттєвий урок
не в «не чіпайте mountpoint». Він у тому, що не припускайте, що властивості датасету поводяться як прості директорії.
Найгостріший рядок постмортему: ніхто не переглянув радіус ураження. Команда була правильна. Припущення — ні.
Міні-історія 2: Оптимізація, що обернулася проти
Інша організація зберігала артефакти збірок і шари контейнерів на ZFS. Вони боролися з ємністю: пул поступово підходив до небажаного використання.
Хтось запропонував увімкнути стиснення на верхньому рівні дерева:
zfs set compression=lz4 tank. Це поширена рекомендація. Але не безкоштовна.
Розгортання виконувалося автоматизацією. Через кілька годин моніторинг показав зростання CPU на частині вузлів.
Затримки на навантаженому API також підвищилися. Команда думала, що зміни API не пов’язані — доки не зіставили вузли
з розкладом датасетів. API використовував датасет під tank також, але ніхто не думав, що він «ділить» щось із артефактами збірок.
Стиснення не було лиходієм; лиходій — взаємодія. API обробляв багато маленьких, вже стиснених payload-ів.
Коефіцієнти стиснення були близькі до 1.00x. Тим часом додаткові цикли CPU конкурували з потоками додатка в піковий час.
На папері — «невелике перевантаження». На практиці — перевантаження в невдалому місці й часі.
Вони відновили роботу, встановивши compression=off локально для датасету API і залишивши його ввімкненим в інших місцях.
Проблема не в тому, що стиснення погане. Проблема — у ставленні до дерева датасетів як до статичної шухлядки замість спільної доменної зони продуктивності.
Довготривале поліпшення — політика: успадкування верхнього рівня лише для властивостей, що безпечні й загальнокорисні,
і явні «датасети-винятки», задокументовані та протестовані. Стиснення залишилося для більшості. API отримав свої правила.
Міні-історія 3: Нудна, але правильна практика, що врятувала день
Компанія, близька до фінансів, використовувала ZFS для мультиорендних внутрішніх сервісів. Керівник зберігання наполягав на нудному ритуалі:
щоквартально запускати рекурсивний аудит властивостей і комітити вивід у репозиторій інфраструктури як артефакт.
Не як діаграму. Суворий вивід zfs get -r. Усі скаржилися, що це бюрократія.
Потім новий реліз платформи привніс тонку зміну: у батьківському датасеті локально з’явилось atime=on через
оманливе «відновити значення за замовчуванням». Ніхто не помітив у розробці, бо тестові навантаження були крихітні.
У продакшені сервіси з великою часткою читань почали генерувати додаткові записи. Затримки зросли. Пискове посилення NVMe
збільшилося. Нічого катастрофічного, просто повільне дороге горіння.
Інженер на чергуванні виконав звичні перевірки. Пул здоровий. Помилок немає. I/O-патерни виглядали «завантаженими», але не фейливими.
Потім він витягнув останній квартальний снапшот властивостей і зробив diff з поточним станом. Різниця проявилась:
джерело atime змінилося на батьку. Це одразу звело область пошуку до піддерева і однієї властивості.
Виправлення: відновити atime=off на правильному батьку, підтвердити, що діти його успадкували, і додати захисний механізм
в автоматизацію, щоб випадково не вмикати знову. Проста практика окупилася в одному інциденті.
Мораль незграбна: інвентар властивостей — не паперова робота; це машина часу. Коли ви дебагите, відома «хороша» база — ваше зброя.
Поширені помилки: симптом → корінь → виправлення
1) «Дочірній датасет змінився, і ніхто його не чіпав»
Симптоми: зміни поведінки (продуктивність, шляхи монтування, видимість снапшотів), але в дочірньому датасеті немає недавніх дій адміністратора.
Корінь: властивість успадкована від батька, який було змінено.
Виправлення: виконайте zfs get -o name,property,value,source на дочці і простежте джерело. Або відкотіть зміну батька, або встановіть локальний оверрайд на дитині.
2) «Після перезавантаження деякі датасети відсутні»
Симптоми: сервіси не стартують при завантаженні, mountpoint порожні, датасети ZFS показують mounted=no.
Корінь: успадкована зміна mountpoint, колізії mountpoint або canmount=off на батьку.
Виправлення: перевірте zfs get -r canmount,mountpoint,mounted. Усуньте колізії; встановіть локальні mountpoint на дітях, яким потрібні унікальні шляхи; переконайтесь, що батьки, які використовуються як контейнер, мають canmount=off лише коли це навмисно.
3) «Регрес продуктивності після «нешкідливого» тюнінгу»
Симптоми: вища затримка, більше I/O, зростання CPU; немає помилок пулу.
Корінь: успадкований recordsize, atime або compression, застосований до навантажень, що цього не люблять.
Виправлення: проведіть аудит джерел властивостей по піддереву. Застосуйте локальні оверрайди на рівні робочих навантажень. Уникайте тюнінгу на високорівневих батьках, якщо не можете обґрунтувати його для кожної дитини.
4) «Немає місця на пристрої, але пул має простір»
Симптоми: додаток бачить ENOSPC; zpool list показує вільне місце.
Корінь: квота/refquota/reservation/refreservation обмежують; іноді очікування щодо успадкування, а не властивості самі по собі.
Виправлення: перевірте zfs get quota,refquota,reservation,refreservation,available. Відкоригуйте обмеження і налаштуйте оповіщення по «available» на рівні датасету, а не лише по вільному місцю пулу.
5) «Снапшоти раптово видимі (або раптово зникли)»
Симптоми: програми обходять .zfs директорії, інструменти бекапу поводяться дивно, служби відповідності ставлять питання.
Корінь: успадкований snapdir змінився на батьку.
Виправлення: встановіть snapdir=hidden або snapdir=visible навмисно на правильній межі політики; перевірте рекурсивно з zfs get -r snapdir.
6) «Ми увімкнули шифрування на батьку; чому існуючі діти не зашифровані?»
Симптоми: нові датасети зашифровані, старі — ні; аудит це виявляє.
Корінь: властивості шифрування впливають на створення датасетів і успадкування, але ви не можете ретроактивно зашифрувати існуючі датасети на місці.
Виправлення: сплануйте send/receive міграцію до нового зашифрованого дерева датасетів; розглядайте успадкування як шаблон для нових датасетів, а не машину часу для старих даних.
Контрольні списки / покроковий план
План A: Безпечно змінити властивість на батьківському датасеті
-
Визначте піддерево. Перерахуйте дітей і монтування.
cr0x@server:~$ zfs list -r -o name,mountpoint tank/apps NAME MOUNTPOINT tank/apps /srv/apps tank/apps/postgres /srv/apps/postgres tank/apps/redis /srv/apps/redisРішення: якщо ви не впізнаєте кожен датасет, зупиніться. Невідомі діти — де ховаються сюрпризи.
-
Аудит поточних значень властивостей і джерел рекурсивно.
cr0x@server:~$ zfs get -r -o name,property,value,source compression,recordsize,atime,sync tank/apps NAME PROPERTY VALUE SOURCE tank/apps compression lz4 local tank/apps recordsize 128K local tank/apps atime on default tank/apps sync standard default tank/apps/postgres compression lz4 inherited from tank/apps tank/apps/postgres recordsize 16K local tank/apps/postgres atime on inherited from tank/apps tank/apps/postgres sync standard inherited from tank/appsРішення: якщо важливі діти мають локальні оверрайди, підтвердіть, що вони залишаться коректними після зміни батька.
-
Смоделюйте вплив. Перерахуйте датасети, що наразі успадковують цю властивість від батька.
cr0x@server:~$ zfs get -r -H -o name,source atime tank/apps | awk '$2 ~ /tank\/apps/ {print $1}' tank/apps/postgres tank/apps/redisРішення: якщо список містить «спеціальні робочі навантаження», розгляньте локальні оверрайди замість зміни батька.
-
Зробіть зміну в контрольованому вікні (або хоча б у контрольованих умовах).
cr0x@server:~$ zfs set atime=off tank/appsРішення: якщо це стосується монтування — плануйте простої; зміни mountpoint гучні операційно.
-
Підтвердіть рекурсивно і слідкуйте за аутлайєрами.
cr0x@server:~$ zfs get -r -o name,property,value,source atime tank/apps NAME PROPERTY VALUE SOURCE tank/apps atime off local tank/apps/postgres atime off inherited from tank/apps tank/apps/redis atime off inherited from tank/appsРішення: якщо якийсь датасет залишився з
atime=on, у нього локальний оверрайд; вирішіть, чи це правильно.
План B: Зробити дитину імунною до майбутніх змін батька
-
Визначте, які властивості потрібно прикріпити локально. Типові:
recordsize,logbias,primarycache,syncдля баз даних (обережно), абоmountpoint. -
Встановіть локальні оверрайди явно і задокументуйте причину.
cr0x@server:~$ zfs set recordsize=16K tank/apps/postgres cr0x@server:~$ zfs set primarycache=metadata tank/apps/postgres cr0x@server:~$ zfs get -o name,property,value,source recordsize,primarycache tank/apps/postgres NAME PROPERTY VALUE SOURCE tank/apps/postgres recordsize 16K local tank/apps/postgres primarycache metadata localРішення: локальні оверрайди — це контракт. Якщо ви не можете їх обґрунтувати, ви просто ускладнюєте майбутнє.
План C: Запобігати сюрпризам за допомогою бази властивостей
-
Збережіть рекурсивний снапшот властивостей для ключових датасетів.
cr0x@server:~$ zfs get -r -o name,property,value,source all tank/apps > /var/tmp/zfs-props-tank-apps.txt cr0x@server:~$ wc -l /var/tmp/zfs-props-tank-apps.txt 842 /var/tmp/zfs-props-tank-apps.txtРішення: збережіть його десь надійно (артефакт репозиторію, сховище конфігурації). Якщо файл живе лише на хості — він помре разом з хостом.
-
Робіть diff до і після змін.
cr0x@server:~$ diff -u /var/tmp/zfs-props-tank-apps-before.txt /var/tmp/zfs-props-tank-apps-after.txt | head --- /var/tmp/zfs-props-tank-apps-before.txt +++ /var/tmp/zfs-props-tank-apps-after.txt @@ -tank/apps atime on default +tank/apps atime off localРішення: якщо diff більший, ніж очікувалося — зупиніться і переоцініть. Великі diff-и — джерело відмов.
FAQ
1) Як дізнатися, чи властивість датасету успадкована?
Використовуйте zfs get і дивіться колонку source. Якщо там написано «inherited from …», це ваша відповідь.
2) Якщо я встановлю властивість на батьку, чи завжди це вплине на всіх дітей?
Воно вплине на тих дітей, які не мають локального оверрайду для цієї властивості, і лише для властивостей, що піддаються успадкуванню.
Діти з локальними значеннями ізольовані.
3) У чому різниця між zfs inherit та встановленням властивості в значення за замовчуванням?
zfs inherit видаляє локальне налаштування, змушуючи датасет успадкувати від батька (або перейти до default, якщо батьків нема).
Явне встановлення значення робить його локальним, навіть якщо воно співпадає з дефолтом.
4) Чому зміна mountpoint порушила речі, хоч це «просто шлях»?
Тому що шляхи — це контракти з сервісами, конфігураціями й порядком завантаження. Успадкування може змістити розклад монтувань усього піддерева.
Це не просто перейменування; це зміна топології.
5) Чи безпечно вмикати compression=lz4 на верхньому датасеті пулу?
Часто так, але «безпечно» залежить від навантаження і запасу CPU. Аудіть коефіцієнти стиснення та використання CPU.
Якщо датасет містить переважно вже стиснені дані, зробіть локальний виняток.
6) Чи варто тюнити recordsize на високорівневому батьку?
Зазвичай ні. Встановлюйте recordsize на батьку лише коли діти однорідні.
Для змішаних навантажень фіксуйте recordsize локально для датасета, що відповідає за конкретне навантаження.
7) Чи можу я ретроактивно зашифрувати існуючі датасети, встановивши властивості шифрування на батьку?
Ні. Зазвичай потрібна міграція send/receive до нового зашифрованого датасету.
Властивості батька визначають створення/успадкування для нових датасетів, а не конвертацію старих на місці.
8) Як знайти, на якому датасеті знаходиться каталог, коли монтування плутають?
Перевірте таблицю монтувань ZFS через zfs mount і порівняйте mountpoint, або скористайтеся інструментами монтувань ОС.
Потім запитайте властивості саме цього датасету.
9) Чому два датасети показують однакове значення властивості, але згодом поводяться по-різному?
Однакове значення, різне джерело. Значення з default не зміниться, поки ви його не зміните.
Значення, успадковане від батька, може змінюватися коли батько його змінює.
10) Як зберегти успадкування, але уникнути сюрпризів?
Розміщуйте межі політики там, де межі організації: один батько на клас навантаження.
Потім запускайте рекурсивні аудити і зберігайте базу, щоб довести, що зміни відбулися.
Висновок: наступні кроки, які можна реально зробити
Успадкування властивостей ZFS — це не академічна деталь. Це виробнича плоскость управління. Воно може врятувати вас від конфігураційного розростання,
і воно може повністю вивести з ладу систему, коли «маленька зміна» виявиться зміною політики для усього піддерева.
Зробіть наступне:
- Оберіть три критичні дерева датасетів (бази даних, бекапи та щось, що швидко заповнює диски) і запустіть рекурсивний аудит властивостей. Збережіть його.
- Позначте датасети робочих навантажень явно і встановіть локальні оверрайди там, де навантаження дійсно відрізняється (особливо
recordsize,atime, властивості монтування). - Впровадьте звичку перевірки радіусу ураження: перед зміною батька перераховуйте всіх дітей, що успадковують цю властивість, і приймайте свідоме рішення.
- Операціоналізуйте виявлення дрейфу: порівнюйте збережену базу та поточні властивості після кожного вікна змін.
Якщо запам’ятаєте лише одне: ніколи не дивіться на значення властивості ZFS, не подивившись на її джерело.
Саме там ховається сюрприз.