Семантика синхронізації ZFS через NFS: чому клієнти змінюють надійність записів

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

Ви налаштували ZFS-файлсервер. Ви встановили sync=standard, як відповідальна людина. Навіть купили «корпоративні» SSD для SLOG.
Потім команда бази даних монтує експорт, запускає навантаження, і раптом графік латентності записів нагадує сейсмограф. Або ще гірше: після аварії приходить
тікет із «ми втратили підтверджені транзакції».

Підсумок жорстокий: по NFS клієнт може змінити значення «безпечного запису» на сервері. Не зі зловмисності — через семантику протоколу, опції монтування
та поведінку додатків. ZFS детермінований; NFS-клієнти… креативні.

Головна ідея: ZFS може виконати лише те, що фактично попросив клієнт

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

NFS ускладнює ситуацію, бо сервер не контролює, коли додаток вважає запис «зафіксованим». Клієнт вирішує, коли надсилати стабільні записи, коли робити COMMIT і чи «бреше» (ненавмисно) через політику кешування або опції монтування. Багато додатків не викликають fsync() так часто, як ви вважаєте; багато бібліотек «групують стійкість» до контрольних точок; і багато NFS-клієнтів застосовують прийоми для підвищення продуктивності, які юридично дозволені протоколом, але дивують інженерів збереження.

Ось неприємна оперативна істина: ви можете використовувати ту саму конфігурацію сервера ZFS і отримувати суттєво різні результати по надійності залежно від ОС клієнта, версії NFS, опцій монтування і поведінки додатка щодо скидання на диск. Два клієнти можуть змонтувати той самий експорт і отримати різні профілі втрат після аварії.

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

Цікаві факти та історія, що досі важливі

  • NFS починався як «безстанний» за задумом (епоха v2/v3). Сервер не зберігав стану по кожному клієнту, що спрощувало відновлення та масштабування, але перекладало складність гарантій на клієнтів.
  • NFSv3 додав процедуру COMMIT. Вона з’явилася тому, що відповіді на WRITE могли означати «нестабільне» збереження; COMMIT просить сервер записати ці байти в стабільне сховище.
  • NFSv4 змістився в бік станового оперування. Додав блокування, делегації та інтегрованішу модель — але питання «коли це на стабільному диску?» і досі лишається узгодженим питанням.
  • ZFS навмисно розділяє «підтвердження» і «коміт TXG на диск». Синхронні записи можуть бути задоволені ZIL без очікування наступного синка TXG.
  • ZIL не є кешем записів для всього. Він зберігає лише те, що потрібно для відтворення синхронних операцій після аварії; це про коректність, а не про прискорення за замовчуванням.
  • Окремий пристрій SLOG — це просто «ZIL на швидшому стабільному носії». Він не зберігає ваші дані довгостроково; він зберігає записи наміру до коміту TXG.
  • sync=disabled існує, бо люди постійно просили його. Він також існує, бо іноді вам потрібна швидкість більше, ніж правда. Ваш майбутній інцидентний звіт вирішить, чи це було розумно.
  • Linux та різні UNIX відрізняються в тому, наскільки активно вони викликають COMMIT. Деякі охоче буферизують і групують, інші частіше змушують стабільність залежно від опцій монтування та навантаження.
  • Порядок записів і бар’єри важливі навіть із хорошим SLOG. Якщо пристрій бреше про flush, ви отримаєте підтвердження записів, які насправді не були стабільними.

Семантика записів NFS на практиці: нестабільні, стабільні та «я ж казав, що все гаразд»

Обіцянка NFS — не «кожен запис стійкий»

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

У NFSv3 клієнт може надсилати WRITE-операції, які сервер може буферизувати. Відповідь на WRITE може вказувати, чи вважаються дані стабільними (FILE_SYNC) або ні (UNSTABLE). Якщо вони нестабільні, клієнт має надіслати COMMIT, щоб зробити їх стабільними перед тим, як вважати їх надійними.

У NFSv4 модель еволюціонує, але те саме фундаментальне питання лишається: що саме обіцяє сервер у момент відповіді? Клієнт може використовувати складені операції та інші правила кешування, але саме він вирішує, коли вимагати стабільності.

«Стабільність» у NFS — це переговори, а не відчуття

«Стабільне» у NFS — не філософський термін. Воно конкретне: стабільне означає, що сервер помістив запис у невипаровуване сховище так, щоб воно пережило аварію згідно з правилами сервера. Це може бути батарейно-захищена RAM, NVRAM, диск із коректним скиданням кешу або — у випадку ZFS — шлях ZIL/SLOG для синхронних семантик.

Але клієнт не завжди просить стабільність. Він може послати нестабільні записи і надіслати COMMIT пізніше. Або покладатися на семантику close-to-open та кешування атрибутів, а не на гарантії стійкості. Або додаток «фіксує» на рівні бази даних, дозволяючи ОС буферизувати дані.

Два точні слова, що відкривають різні світи: fsync і O_DSYNC

Додатки виражають потреби у стійкості через виклики системних функцій, як-от fsync(), fdatasync(), та прапори O_SYNC / O_DSYNC. На локальній файловій системі вони досить прямо відповідають «не бреши мені».

Через NFS ці виклики перетворюються на операції на рівні протоколу, які можуть включати COMMIT або стабільні записи — залежно від налаштувань клієнта. Якщо клієнт вирішить кооперувати або відкладати COMMIT, ваш сервер може бути «коректним», але очікування додатка щодо збереження можуть бути порушені після аварії.

Жарт №1: стійкість NFS схожа на корпоративну політику — на зборах усі погоджуються, а справжні рішення приймаються поза увагою.

З боку ZFS: sync, ZIL, SLOG, TXG і де живе правда

Групи транзакцій: великий ритм під усім

ZFS групує зміни в пам’яті й періодично комітить їх на диск у групах транзакцій (TXG). Це ключова стратегія продуктивності: вона перетворює випадкові дрібні записи на більш послідовні операції вводу/виводу і дозволяє ZFS оптимізувати розподіл.

Каденс TXG зазвичай становить кілька секунд. Це нормально для асинхронних записів: додаток отримує підтвердження швидко, а ZFS комітить пізніше. Але для синхронних записів чекати наступного коміту TXG занадто повільно. Тут і з’являється ZIL.

ZIL: журнал намірів для синхронних операцій

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

Без окремого пристрою ZIL живе на основному пулі. З окремим лог-пристроєм (SLOG) ZFS може поміщати ці записи наміру на швидший, низьколатентний носій, зменшуючи вартість підтвердження синхронних записів.

Оперативно: за чесність ви платите в ZIL. Якщо ви вимагаєте синхронних семантик, ви просите ZFS робити додаткову роботу зараз, а не пізніше.

Властивість dataset sync: важіль, який люди часто неправильно використовують

ZFS надає властивість sync для dataset:
standard, always і disabled.

  • sync=standard: поважати запити sync. Якщо клієнт/додаток просить sync — виконайте; інакше — буферизуйте.
  • sync=always: трактувати всі записи як sync, навіть якщо клієнт цього не просив. Це режим «я вам не довіряю».
  • sync=disabled: брехати. Підтверджувати запити sync без фактичного забезпечення стабільного збереження.

У світі NFS sync=standard — це не те саме, що «безпечний». Це «безпечний, якщо клієнт попросив безпеки».
А клієнти мають кілька способів не просити.

Що означає «безпечний» залежить від усього ланцюжка

Синхронне підтвердження чесне лише настільки, наскільки слабкий компонент може зобразити дані як стабільні, коли це не так:

  • Налаштування кешу диска і поведінка flush
  • Кеш контролера і батарейно/флеш-захист
  • Захист від втрати живлення (PLP) та порядок записів на SLOG
  • Стек зберігання гіпервізора, якщо ви віртуалізовані
  • Клієнтське кешування та опції монтування NFS

Ви можете зробити все правильно на ZFS і все одно постраждати від клієнта, який налаштований трактувати close() як «достатньо добре»
без примусу стабільних записів, або від «швидкого» SSD, який підтверджує flush, наче читає казку на ніч.

Одна цитата, що справджується: «Надія — не стратегія.» — парафраз ідеї, яку часто приписують інженерам з експлуатації/надійності.

Чому клієнт змінює безпеку: монтування, кеші та патерни додатків

Опції монтування NFS можуть тихо міняти стійкість на користь пропускної здатності

На багатьох клієнтах різниця між «sync» і «async» — це не один перемикач; це сукупність поведінок:

  • Кешування при монтуванні. Кешування атрибутів, кешування записів директорій і клієнтський page cache можуть змінити, коли записи будуть витіснені на сервер.
  • Групування записів. Клієнти можуть збирати дрібні записи в більші RPC, зменшуючи оверхед, але затримуючи стабільність.
  • Поводження з COMMIT. Клієнт може надсилати COMMIT лінькувато, або лише при fsync/close залежно від політики.
  • Hard vs soft монти. Безпосередньо не контролює стійкість, але змінює поведінку при збої (зависає проти помилки), що змінює поведінку додатків під навантаженням.

Ви не можете припускати стандартні налаштування клієнта. Вони різняться за версією ОС, дистрибутивом, ядром і навіть базовими політиками безпеки.

Додатки непослідовні щодо скидань на диск

Очевидні винуватці — бази даних, але й багато «нудних» систем роблять небезпечні речі:

  • Черги повідомлень, що роблять fsync кожні N повідомлень.
  • Логери, що викликають fsync лише при ротації логів.
  • Системи збірки, яким «побічно не важливо» аж поки це не стане важливим (хто відповідає за артефакти?).
  • ETL-пайплайни, що вважають rename атомарним і стійким скрізь.

На локальних файлових системах ці патерни можуть бути прийнятними. Через NFS вони можуть перетворитися на довгі вікна підтверджених-але-нестабільних даних,
особливо якщо клієнт використовує нестабільні записи і відтерміновує COMMIT.

Експорти NFS на сервері: що ви дозволяєте — має значення

Налаштування експорту на сервері зазвичай не говорять «бреши щодо стійкості», але вони впливають на поведінку клієнтів: режими безпеки, перевірку піддерев,
стабільність FSID і делегації (в NFSv4) можуть змінювати кешування та патерни повторних спроб.

На Linux-серверах (включно з багатьма розгортаннями ZFS-on-Linux) exportfs і потоки nfsd можуть стати вузьким місцем, що виглядає
як «затримка диска». Якщо ви не вимірюєте обидва шари, ви звинуватите невірний рівень і «виправите» покупкою SSD.

Реалії SLOG: коли допомагає, коли шкодить і коли це театр

Коли SLOG допомагає

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

  • Навантаженнях NFS, де клієнти часто роблять fsync / стабільні записи (бази даних, образи віртуальних машин на NFS, деякі поштові системи)
  • Дрібних випадкових синхронних записах, коли vdev пулу — HDD або зайняті
  • Чутливих до латентності додатках, де кожен синхронний запис блокує потік

SLOG — про латентність підтвердження, не про пропускну здатність. Якщо ви не маєте багато синків, він мало що змінить.

Коли SLOG нічого не дає

Якщо більшість записів асинхронні, шлях ZIL/SLOG не є вашим вузьким місцем. Ваше вузьке місце — пропускна здатність коміту TXG, ліміти брудних даних або читальне посилення.

Також: якщо ваші клієнти роблять нестабільні записи і відтерміновують COMMIT, швидкий SLOG може допомогти лише в момент COMMIT. До того часу ви накопичуєте ризик, а не вирішуєте латентність.

Коли SLOG шкодить

Поганий пристрій SLOG може знищити латентність. ZFS надсилає записи наміру у патерні, що очікує постійно низьку латентність з коректними flush-семантиками. Побутові SSD часто:

  • мають непередбачувану латентність під тривалими синхронними записами,
  • не мають захисту від втрати живлення,
  • оптимістично підтверджують flush,
  • різко падають, коли кеш SLC вичерпується.

Ось як ви отримуєте класичний графік: p99 латентності записів в нормі… доки раптом не починає бути погано, і тоді так і залишається.

Жарт №2: «Швидкий» SSD без захисту від втрати живлення — як резюме з «командним гравцем» у навичках: технічно можливо, але вам треба перевіряти.

sync=always: ядерна опція, що іноді правильна

Якщо ви не довіряєте клієнтам у правильності запитів стабільності, sync=always — грубий інструмент, який змушує ZFS трактувати кожен запис як синхронний. Це звужує простір для клієнтської креативності.

Воно також підвищує латентність і висвітлює кожне слабке місце: SLOG, пул, контролер і мережу. Використовуйте його вибірково: на рівні dataset, на рівні експорту, для навантажень, які дійсно цього потребують.

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

Найшвидший спосіб відлагодити проблеми синхронізації NFS на ZFS — припинити філософію й по черзі відповісти на три питання:
(1) чи ми виконуємо синхронні записи, (2) де латентність, і (3) чи система бреше щодо поведінки flush?

Перш за все: підтвердьте, чи навантаження справді синхронне

  • Перевірте властивість dataset sync ZFS і підтвердіть очікування.
  • Перевірте опції монтування NFS на клієнті і чи додатки викликають fsync.
  • Спостерігайте активність ZIL/SLOG і частоту NFS COMMIT.

По-друге: знайдіть домен вузького місця (CPU, мережа, SLOG, пул)

  • Якщо потоки NFS сервера насичені, це не «затримка диска», а вузьке місце сервера.
  • Якщо латентність SLOG стрибає — синхронні записи будуть стрибати, навіть якщо пул у порядку.
  • Якщо vdev пулу зайняті, коміти TXG будуть тягнутися, і асинхронні навантаження також загальмують.

По-третє: перевірте поведінку стабільного сховища end-to-end

  • Підтвердьте, що SLOG має PLP і не віртуалізований за writeback кешами.
  • Підтвердьте політику кешу запису на диску і що flushи виконуються.
  • Підтвердьте, що ви не запускаєте sync=disabled десь «тимчасово».

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

Ось перевірки, які я реально виконую, коли хтось каже «NFS повільний» або «ми втратили записи». Кожна включає: команду, приклад виводу, що це означає,
і яке рішення прийняти.

Завдання 1: Визначте dataset і його політику sync

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

Що це означає: ZFS буде поважати запити sync, але не примушуватиме їх.

Рішення: Якщо навантаження потребує суворої стійкості незалежно від поведінки клієнта, розгляньте sync=always (для цього dataset), а не на рівні всього пулу.

Завдання 2: Шукаємо класичну пастку: sync disabled

cr0x@server:~$ zfs get -r -o name,property,value,source sync tank/nfs
NAME        PROPERTY  VALUE     SOURCE
tank/nfs    sync      standard  default
tank/nfs/db sync      standard  local
tank/nfs/ci sync      disabled  local

Що це означає: Одна підгілка бреше про sync-запити.

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

Завдання 3: Підтвердіть наявність (або відсутність) SLOG

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

        NAME                         STATE     READ WRITE CKSUM
        tank                         ONLINE       0     0     0
          mirror-0                   ONLINE       0     0     0
            ata-HDD_A                ONLINE       0     0     0
            ata-HDD_B                ONLINE       0     0     0
        logs
          nvme-SLOG0                 ONLINE       0     0     0

errors: No known data errors

Що це означає: Синхронні записи можуть підтверджуватися через лог-пристрій.

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

Завдання 4: Перевірка латентності SLOG під навантаженням (швидкий індикатор)

cr0x@server:~$ iostat -x 1 3 /dev/nvme0n1
Linux 6.5.0 (server)   12/26/2025

avg-cpu:  %user %nice %system %iowait  %steal %idle
           3.2   0.0    5.4    1.1     0.0   90.3

Device            r/s     w/s   rkB/s   wkB/s  avgrq-sz avgqu-sz await r_await w_await  svctm  %util
nvme0n1           0.0  8200.0     0.0  65600.0     16.0     9.1   1.2    0.0    1.2    0.1   82.0

Що це означає: ~1.2 ms write await при великому обсязі записів — нормально; якщо бачите десятки мс — ваш SLOG проблема.

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

Завдання 5: Перевірте відповідність recordsize та навантаження (латентність vs пропускна спроможність)

cr0x@server:~$ zfs get -o name,property,value recordsize,logbias,primarycache tank/nfs/db
NAME         PROPERTY      VALUE     SOURCE
tank/nfs/db  recordsize    128K      local
tank/nfs/db  logbias       latency   local
tank/nfs/db  primarycache  all       default

Що це означає: logbias=latency підштовхує ZFS використовувати лог для синхронних записів; recordsize впливає на читально-записне підсилення для баз даних.

Рішення: Для DB-подібного випадкового вводу/виводу розгляньте менший recordsize (наприклад, 16K) після вимірювань. Залишайте logbias=latency для синк-важких dataset.

Завдання 6: Перевірте тиск TXG і «брудні дані» що викликають затримки

cr0x@server:~$ cat /proc/spl/kstat/zfs/arcstats | egrep 'dirty_data|txg_sync|txg_quiesce'
txg_sync_delay                     5
txg_quiesce_delay                  1
dirty_data_max                  8589934592
dirty_data                      2147483648

Що це означає: Якщо dirty_data підходить до dirty_data_max, ZFS буде обмежувати записувачів — клієнти бачитимуть затримки, не пов’язані з семантикою NFS.

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

Завдання 7: Спостерігайте насичення потоків NFS сервера (Linux nfsd)

cr0x@server:~$ ps -eLo pid,comm,psr,pcpu,stat | awk '$2=="nfsd" {sum+=$4} END {print "total_nfsd_cpu="sum"%"}'
total_nfsd_cpu=380%

Що це означає: Потоки nfsd спалюють CPU. З клієнтського боку це може виглядати як «сховище повільне».

Рішення: Якщо CPU високий, налаштуйте кількість потоків nfsd, перевірте накладні витрати шифрування/аутентифікації та профілюйте суміш RPC до зміни ZFS.

Завдання 8: Підтвердіть версії протоколу та опції монтування на клієнті

cr0x@server:~$ nfsstat -m
/mnt/db from server:/tank/nfs/db
 Flags: rw,relatime,vers=3,rsize=1048576,wsize=1048576,hard,proto=tcp,timeo=600,retrans=2,sec=sys,mountaddr=10.0.0.10,mountvers=3,mountproto=tcp,local_lock=none

Що це означає: NFSv3 з великими rsize/wsize; hard-монт. Явної опції sync/async тут не видно, але версія впливає на поведінку COMMIT.

Рішення: Якщо очікування щодо стійкості суворі, оцініть поведінку NFSv4.1+ і опції клієнта; узгодьте єдиний профіль монтування для класу навантаження.

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

cr0x@server:~$ nfsstat -s | egrep 'commit|write'
write        182938
commit        12480

Що це означає: Багато write-операцій, але відносно мало commit-ів — вказує на нестабільні записи, що групуються, або на те, що додатки рідко викликають fsync.

Рішення: Якщо навантаження стверджує, що «кожна транзакція стійка», ця невідповідність — ваш провідний слід. Аудитуйте політику fsync додатка та налаштування кешування клієнта.

Завдання 10: Підтвердіть опції експорту, які віддаються

cr0x@server:~$ exportfs -v | sed -n '/tank\/nfs\/db/,+2p'
/tank/nfs/db   10.0.0.0/24(rw,wdelay,root_squash,sec=sys,no_subtree_check,fsid=120)

Що це означає: Стандартний експорт; wdelay може змінювати поведінку агрегування записів на боці сервера.

Рішення: Не чіпайте налаштування експорту випадково в надії на стійкість. Використовуйте експорти для контролю доступу й коректності; семантику sync вирішуйте на боці ZFS і клієнта.

Завдання 11: Перевірте, чи SLOG-пристрій не помиляється і не обходиться

cr0x@server:~$ zpool iostat -v tank 1 2
                              capacity     operations     bandwidth
pool                        alloc   free   read  write   read  write
--------------------------  -----  -----  -----  -----  -----  -----
tank                        2.10T  5.10T      0   2200      0  45.0M
  mirror-0                  2.10T  5.10T      0   2100      0  44.0M
    ata-HDD_A                   -      -      0   1050      0  22.0M
    ata-HDD_B                   -      -      0   1050      0  22.0M
logs                            -      -      0    800      0  12.5M
  nvme-SLOG0                    -      -      0    800      0  12.5M
--------------------------  -----  -----  -----  -----  -----  -----

Що це означає: Лог-пристрій бачить активність запису. Якщо під час «sync»-навантаження він завжди нульовий — або навантаження не синхронне, або конфігурація відрізняється від очікуваної.

Рішення: Корелюйте з поведінкою додатка і COMMIT NFS. Не купуйте обладнання, поки не доведете, що синхронні записи відбуваються.

Завдання 12: Перевірте, чи клієнт бачить латентність через мережу чи серверний I/O

cr0x@server:~$ ss -tin sport = :2049 | head -n 12
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
ESTAB 0      0      10.0.0.10:2049     10.0.0.51:49822
	 cubic wscale:7,7 rto:204 rtt:0.289/0.032 ato:40 mss:1448 pmtu:1500 rcvmss:1448 advmss:1448 cwnd:10 bytes_sent:23849342 bytes_acked:23849210 bytes_received:14920345 segs_out:22110 segs_in:21540 data_segs_out:18900 data_segs_in:17620 send 400.8Mbps lastsnd:8 lastrcv:8 lastack:8 pacing_rate 801.6Mbps unacked:1

Що це означає: RTT субмілісекундний; мережа наразі не є основним джерелом латентності в цьому знімку.

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

Завдання 13: Перевірте, чи ZFS не має помилок контролю чи пристроїв (тихі вбивці продуктивності)

cr0x@server:~$ zpool status -x
all pools are healthy

Що це означає: Нема відомих помилок. Якщо тут не чисто — припиніть «тюнінг продуктивності» і почніть incident response.

Рішення: Замініть несправні пристрої, виконайте scrub і перевірте знову. Деградований пул може виглядати точно як «NFS sync повільний».

Завдання 14: Підтвердіть, що sync=always застосовано там, де ви думаєте

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

Що це означає: ZFS буде трактувати кожен запис як sync для цього dataset, незалежно від запитів клієнта.

Рішення: Очікуйте вищої латентності; переконайтеся, що SLOG надійний і моніторьте p95/p99. Використовуйте це для даних, які мають виживати після аварії з мінімальною неоднозначністю.

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

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

Середня компанія мала ZFS-NFS кластер для внутрішніх сервісів. Одна команда зберігала стан черги завдань у NFS-експорті.
Сторона зберігання зробила домашнє завдання: дзеркальний пул, SLOG на «швидкому NVMe», sync=standard і рутинні scrub-и.

Команда черги мігрувала з одного дистрибутива Linux на інший під час оновлення платформи. Та сама версія додатка, ті ж дані, той самий цільовий сервер.
Після події з відключенням живлення (не надто драматичної; така, що драматичною стала пізніше), частина підтверджених завдань з’явилася як «ніколи не оброблені».
І ще частина зникла.

Перші раунди дебагу були передбачувано непродуктивні. Люди сперечалися про «ZFS — копію при записі, отже не може втратити дані» і «NFS на TCP надійний»,
ніби ці твердження завершують розслідування. Команда зберігання викликала zpool status — все здорово. Мережа показала низькі повторні передачі.
Команда черги наполягала, що додаток «викликає fsync при коміті».

Прорив прийшов, коли звернулися до поведінки протоколу, а не до вірувань. На клієнті nfsstat -m показав іншу версію NFS і інші
значення кешування за замовчуванням. На сервері COMMIT-викликів було значно менше, ніж очікували під навантаженням. Додаток дійсно викликав fsync — але патерн I/O був
буферизований, і політика клієнта відкладала межу стійкості більше, ніж команда уявляла.

Виправлення було прозаїчним: стандартизувати профіль монтування клієнта для цього навантаження і переключити dataset на sync=always, поки всі не доведуть
контракт стійкості додатка. Це коштувало латентності. Це дало ясність. Це торг, який ви можете пояснити в постмортемі без паніки.

Міні-історія 2: Оптимізація, що обернулася проти

Інша організація обслуговувала домашні теки та артефакти збірок по NFS. Хотіли пришвидшити CI. Хтось знайшов магічний регулятор: sync=disabled на dataset, що підтримував кеш збірок. Це подали як «достатньо безпечно, бо артефакти можна відновити».

Довго виглядало чудово. CI прискорився. Латентність знизилася. Всім сподобалося. А потім одна неочевидна залежність дала про себе знати: pipeline релізу також писав підписані метадані в ту саму директорію. Метадані були «дрібними і швидкими»,
тож про них ніхто не думав. Вони також були тією річчю, яку не дуже просто відтворити без заново проходження процедури підпису.

Відбувся краш хосту під час релізу. ZFS підтвердив sync-запити без стабільного запису. Файл метаданих існував, але мав старіший вміст.
Декілька збірок вийшли з невідповідними метаданими. Не катастрофа, але команда відповідності втрутилася, і «перебудовувані артефакти» перестали здаватися безумовним виправданням.

Повернення не було через фіксу в ZFS. Це була організаційна неточність у класифікації даних на спільних експортax. Вони оптимізували одне навантаження і випадково втягли критичніше навантаження в ту саму політику.

Виправлення було структурним: розділити dataset за вимогами стійкості, застосувати sync=standard або sync=always відповідно, і тримати «небезпечні хакі швидкості» за окремими точками монтування й доступу. Також: видаліть слово «тимчасово» з заявок на зміни; це найпостійніше слово в експлуатації.

Міні-історія 3: Скучно, але правильно — і це врятувало день

Фінансова компанія мала платформу ZFS NFS, що хостила різні сервіси, включно зі станним додатком, чий постачальник однозначно вказував: «потрібні стабільні записи при коміті». Інженери зі зберігання не гадали. Вони створили окремий dataset і експорт саме для цього додатка.
sync=always, перевірений PLP SLOG і письмова стандартизація монтувань клієнта. Без винятків.

Це було непопулярно. Команда додатка скаржилась на латентність порівняно з локальним SSD. Інженери зі зберігання не сперечалися емоційно.
Вони показали простий тест: з sync=standard і поточним монтуванням додатка частота fsync була нижчою, ніж очікувалося, і COMMITи приходили вибухами. З sync=always крива латентності була стабільною, і вікно втрат після аварії стало простим для розрахунку.

Через кілька місяців сталася неприємна пригода в іншому місці: баг прошивки спричинив рестарт частини серверів під конкретними I/O-патернами.
Багато систем мали дивні стани після перезавантаження. Той станний додаток — ні. Жодної корупції, жодних повторних запусків завдань, жодних тихих відкатів.

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

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

1) «Ми встановили sync=standard, отже ми в безпеці.»

Симптоми: Втрата даних після аварії попри «увімкнений sync», або непослідовна стійкість між клієнтами.

Корінь: Клієнти не запитували стабільні записи послідовно (нестабільні записи + відкладений COMMIT, політика кешування, додаток не викликає fsync).

Виправлення: Для суворих навантажень використовуйте sync=always на рівні dataset/export; стандартизуйте опції монтування клієнтів; перевіряйте COMMIT/fsync поведінку метриками.

2) «SLOG зробить усе швидшим.»

Симптоми: Жодного покращення або гірший p99 після додавання SLOG.

Корінь: Навантаження переважно асинхронне; або SLOG-пристрій має погану латентність/нечесний flush.

Виправлення: Виміряйте COMMIT/швидкість синхронних записів перш ніж ставити SLOG. Використовуйте пристрій класу PLP або приберіть поганий SLOG. Не застосовуйте SLOG механічно.

3) «sync=disabled підходить для неважливих даних.»

Симптоми: Випадково застарілі файли, усічена метадані, «файл є, але старий», болюча реконциляція після аварії.

Корінь: Неважливі дані поділяли dataset/export із критичними, або «неважливе» виявилось критичним під час інциденту.

Виправлення: Розділіть dataset за класом стійкості. Заблокуйте небезпечні експорти. Зробіть ризик явним і підзвітним.

4) «NFS повільний; диски мають бути повільні.»

Симптоми: Велика латентність на клієнті, але пул виглядає в порядку; сплески CPU на сервері.

Корінь: Насичення потоків nfsd, накладні витрати аутентифікації, важкий RPC-мікс або серіалізація від одного клієнта.

Виправлення: Виміряйте CPU сервера, композицію операцій nfsstat, кількість потоків і RTT мережі. Масштабуйте потоки nfsd і налаштуйте експорти перед покупкою дисків.

5) «Ми втратили записи, отже ZFS пошкоджений.»

Симптоми: Втрата на рівні додатка без помилок пулу; немає помилок контрольних сум.

Корінь: Вікно втрати через асинхронність або брехню про flushи (sync disabled, поганий SLOG/драйвер кешу диска).

Виправлення: Аудитуйте властивості sync і семантику flush апаратури; примушуйте sync-семантику на боці сервера для критичних даних.

6) «Ми перейшли на NFSv4, отже все виправилось.»

Симптоми: Та сама плутанина зі стійкістю, інші повідомлення про помилки.

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

Виправлення: Зосередьтеся на: поведінці скидання додатка, політиці кешування клієнта, властивості dataset sync і чесному стабільному сховищі.

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

План A: Потрібна сувора стійкість для одного NFS-навантаження

  1. Створіть виділений dataset. Не спільно використовуйте його з «швидкими і вільними» навантаженнями.
  2. Встановіть семантику sync на стороні сервера. Використовуйте sync=always, якщо не можете повністю контролювати поведінку клієнтів щодо flush.
  3. Використовуйте перевірений SLOG, якщо латентність sync важлива. Потрібен PLP; дзеркальний SLOG, якщо ви не терпите ризику відмови лог-пристрою для доступності.
  4. Стандартизуйте монтування клієнтів. Задокументуйте підтримувану версію NFS і опції монтування. Розглядайте відхилення як неподтримувані.
  5. Тестуйте поведінку при аваріях. Не «бенчмарки», а реальні симуляції перезавантаження/відключення живлення у стейджингу, що відповідає продакшну.
  6. Моніторьте сигнали протоколу. Відстежуйте частоти write/commit, p95 латентності і насичення CPU сервера.

План B: Потрібна продуктивність і допустимі деякі втрати при аварії

  1. Ясно визначте допустимі втрати. «Трохи» — не число; визначте метод відновлення й очікуване вікно.
  2. Тримайте sync=standard і уникайте sync=disabled, якщо не ізольовані. Якщо мусите відключити sync, робіть це в окремому dataset і явно позначайте.
  3. Оптимізуйте пропускну здатність пулу. Зосередьтеся на розкладці vdev, стратегії ARC/L2ARC і поведінці TXG, а не на SLOG.
  4. Мінімізуйте зону ураження. Розділяйте експорти за класами навантажень, щоб ризиковий вибір не впливав на всі сервіси.

План C: Ви успадкували загадкову платформу NFS/ZFS і ніхто не знає, що безпечно

  1. Інвентаризуйте dataset і властивості sync. Негайно знайдіть будь-яке sync=disabled.
  2. Зіставте експорти з dataset. Переконайтеся, що критичні навантаження не сидять на «продуктивному» експорті.
  3. Зберіть приклади монтувань клієнтів. Зберіть nfsstat -m з репрезентативних клієнтів.
  4. Виміряйте COMMIT-частоту і активність SLOG. Визначте, чи клієнти реально вимагають стабільних записів.
  5. Виберіть стандартну позицію щодо стійкості. Моя рекомендація: за замовчуванням безпечно для станних навантажень; оптимізації повинні бути опціональними і ізольованими.

Питання й відповіді (FAQ)

1) Якщо я встановлюю sync=standard, чи будуть NFS-записи стійкими?

Вони стійкі лише тоді, коли клієнт/додаток запитують синхронні семантики (стабільний запис / COMMIT-поведінка). Якщо клієнт буферизує і відкладає стабільність,
ZFS також може буферизувати. Для суворої стійкості незалежно від поведінки клієнта використовуйте sync=always для цього dataset.

2) Чи змінює hard vs soft на NFS стійкість?

Непрямо. Це змінює поведінку при збої. hard зазвичай чекає й повторює запити, що зазвичай правильно для цілісності даних; soft
може повертати помилки, які додатки неправильно обробляють. Але стійкість головним чином про стабільні записи/COMMIT і політику sync на сервері.

3) Чи правильне рішення — додати SLOG для NFS?

Лише якщо у вас є значуща частка синхронних записів і латентність пулу — це вузьке місце. Якщо клієнти рідко роблять стабільні записи/COMMIT, SLOG не допоможе. Поганий SLOG може завдати великої шкоди.

4) У чому різниця між ZIL і SLOG знову?

ZIL — це механізм (журнал намірів для синхронних операцій). SLOG — це виділений пристрій, куди ZFS кладе цей журнал, щоб зменшити латентність. Без SLOG ZIL живе на основному пулі.

5) Якщо ми використовуємо sync=disabled, чи можемо ми все ще бути «переважно в безпеці»?

Ви можете бути «переважно везучими». sync=disabled підтверджує sync-запити без їхнього фактичного забезпечення стабільності. Після аварії можна втратити нещодавні підтверджені операції, включно з оновленнями метаданих. Використовуйте його лише для ізольованих dataset, де ви дійсно погоджуєтесь на цей ризик.

6) Чи гарантує NFSv4 кращу консистентність після аварії, ніж NFSv3?

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

7) Чому деякі клієнти мають великі стрибки латентності під час контрольних точок бази даних?

Контрольні точки часто викликають сплески fsync/COMMIT. Якщо dataset має sync=always або додаток використовує O_DSYNC, ZFS прокладе ці операції через ZIL/SLOG. Якщо SLOG слабкий або насичений, p99 латентності вибухає саме під час контрольної точки.

8) Чи варто дзеркалити SLOG?

Дзеркалення SLOG про доступність, а не про цілісність у звичному розумінні. Втрата SLOG-пристрою може спричинити проблеми пулу або примусити небезпечні фолбек-и залежно від поведінки платформи. Для систем, які мають залишатися доступними, дзеркальний SLOG часто вартий витрат.

9) Чи можу я «змусити клієнтів бути безпечними» з боку сервера?

Ви не змусите додаток викликати fsync, але можете змусити ZFS трактувати всі записи як sync за допомогою sync=always. Це зменшує залежність від дисципліни клієнта щодо flush. Це найпрактичніший інструмент на стороні сервера.

10) Який найнадійніший спосіб перевірити твердження про стійкість?

Контрольоване краш-тестування з реальною клієнтською ОС, реальними опціями монтування і реальним додатком. Виміряйте те, що додаток вважає зафіксованим, потім аварійно перезавантажтесь і перевірте інваріанти. Бенчмарки не скажуть правди тут; краш-тести скажуть.

Наступні кроки, які можна зробити цього тижня

  1. Перевірка налаштувань sync: запустіть zfs get -r sync по ваших NFS-деревах і відмітьте будь-яке sync=disabled.
  2. Виберіть два критичних експорти і стандартизуйте профілі монтувань клієнтів; зберіть nfsstat -m з репрезентативних клієнтів.
  3. Виміряйте співвідношення COMMIT/write на сервері під час пікового навантаження; порівняйте з тим, що заявляють додатки.
  4. Підтвердіть ваш SLOG вимірюванням латентності і підтвердженням, що він має PLP; якщо не можете довести — вважайте його підозрілим.
  5. Розділіть dataset за класами стійкості, щоб оптимізація однієї команди не змінила модель ризику іншої.
  6. Запустіть один краш-тест у стейджингу: навантаження, що робить «коміти», форсований ребут і крок перевірки. Дивовижно, як швидко міфи падають.

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

← Попередня
Docker «Text file busy» під час розгортання: виправлення, яке зупиняє ненадійні перезапуски
Наступна →
ZFS Raw Send: реплікація зашифрованих даних без передачі ключів

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