Sémantique de synchronisation ZFS NFS : pourquoi les clients modifient la sécurité de vos écritures

Cet article vous a aidé ?

Vous avez construit un serveur ZFS. Vous avez défini sync=standard comme une personne responsable. Vous avez même acheté des SSD « entreprise » pour le SLOG.
Puis une équipe base de données monte l’export, lance une charge, et soudain votre graphe de latence d’écriture ressemble à un sismographe. Ou pire : vous recevez un
ticket post-crash qui commence par « nous avons perdu des transactions engagées. »

La chute est cruelle : sur NFS, le client peut changer ce que signifie « écriture sûre » côté serveur. Pas par malice — par les sémantiques du protocole, les options de montage
et le comportement des applications. ZFS est déterministe ; les clients NFS sont… créatifs.

L’idée centrale : ZFS ne peut honorer que ce que le client demande réellement

ZFS vous donne un contrat clair : si une opération est synchrone, ZFS n’enverra pas d’accusé de réception tant qu’elle n’est pas sûre selon ses règles — c’est-à-dire consignées dans le journal d’intention de ZFS (ZIL) et conformes à l’ordre d’écriture du pool. Si elle est asynchrone, ZFS peut mettre en tampon.

NFS complique cela parce que le serveur n’est pas maître du moment où une application considère une écriture comme « commise ». Le client décide quand émettre des écritures stables, quand envoyer un COMMIT, et s’il doit (par inadvertance) mentir via la politique de cache ou des options de montage. Beaucoup d’applications n’appellent pas fsync() aussi souvent qu’on l’imagine ; de nombreuses bibliothèques « regroupent la durabilité » jusqu’aux points de contrôle ; et beaucoup de clients NFS tentent des astuces de performance parfaitement légales dans le protocole mais surprenantes pour les ingénieurs stockage.

Voici la vérité opérationnelle inconfortable : vous pouvez exécuter la même configuration serveur ZFS et obtenir des résultats de durabilité sensiblement différents
selon l’OS client, la version NFS, les options de montage et le comportement de flush de l’application. Deux clients peuvent monter le même export et avoir
des profils de perte après crash différents.

Si vous gérez du stockage partagé, vous devez considérer les clients NFS comme faisant partie du chemin d’écriture. Ils ne sont pas « que des consommateurs. » Ils
participent à votre protocole de durabilité — souvent sans savoir qu’ils s’y sont engagés.

Faits intéressants et historique qui comptent encore

  • NFS a commencé comme « sans état » par conception (ère v2/v3). Le serveur ne conservait pas d’état par client, ce qui facilitait la récupération et l’échelle, mais déplaçait la complexité de durabilité vers les clients.
  • NFSv3 a introduit la procédure COMMIT. Elle existe parce que les réponses WRITE peuvent représenter un stockage « instable » ; COMMIT demande au serveur de pousser ces octets vers un stockage stable.
  • NFSv4 a évolué vers un fonctionnement avec état. Il a ajouté verrous, délégations et un modèle plus intégré — pourtant « quand est-ce sur stockage stable ?» reste une réalité négociée.
  • ZFS découple intentionnellement « accuser » de « committer dans le TXG sur disque ». Les écritures synchrones peuvent être satisfaites par le ZIL sans attendre le prochain sync de transaction group (TXG).
  • Le ZIL n’est pas un cache d’écriture pour tout. Il n’enregistre que ce qui est nécessaire pour rejouer les opérations synchrones après un crash ; il s’agit de conformité, pas d’accélération par défaut.
  • Un périphérique SLOG séparé n’est que « ZIL sur un média stable plus rapide ». Il ne stocke pas vos données à long terme ; il conserve des enregistrements d’intention jusqu’à ce que le TXG committe.
  • « sync=disabled » existe parce que des gens en ont demandé. Il existe aussi parce que parfois on préfère la vitesse à la vérité. Votre futur rapport d’incident dira si c’était judicieux.
  • Linux et divers UNIX diffèrent sur la fréquence d’émission de COMMIT. Certains bufferisent et regroupent volontiers, d’autres forcent la stabilité plus souvent selon les options de montage et la charge.
  • L’ordre d’écriture et les barrières comptent même avec un bon SLOG. Si le périphérique ment sur les flushes, vous obtenez des accusés pour des écritures qui n’ont jamais été réellement stables.

Sémantique d’écriture NFS en pratique : instable, stable et « je vous jure que c’est bon »

La promesse NFS n’est pas « chaque écriture est durable »

NFS n’est pas un protocole de dispositif bloc. C’est un protocole de fichiers avec un contrat qui dépend des opérations demandées par le client et des sémantiques
qu’il attend.

Avec NFSv3, un client peut envoyer des opérations WRITE que le serveur peut mettre en tampon. La réponse WRITE peut indiquer si les données sont considérées comme
stables (FILE_SYNC) ou non (UNSTABLE). Si c’est instable, le client est censé envoyer un COMMIT pour les rendre stables avant de les considérer comme durables.

Avec NFSv4, le modèle évolue mais la même question fondamentale demeure : que promet le serveur au moment où il répond ? Le client peut utiliser des opérations composées et
des règles de cache différentes, mais il décide toujours quand exiger la stabilité.

« Stable » est une négociation, pas une impression

« Stable » en NFS n’est pas un concept philosophique. C’est concret : stable signifie que le serveur a placé l’écriture sur un support non volatile de sorte qu’elle
survive à un crash conformément aux règles du serveur. Cela peut être de la RAM protégée par batterie, de la NVRAM, un disque avec flushs correctement honorés, ou — sur ZFS — typiquement
le chemin ZIL/SLOG pour les sémantiques synchrones.

Mais le client ne demande pas toujours la stabilité. Il peut émettre des écritures instables et envoyer COMMIT plus tard. Ou il peut compter sur la sémantique close-to-open et le cache d’attributs,
pas sur la durabilité. Ou l’app peut « commettre » au niveau base de données tout en laissant l’OS bufferiser.

Deux mots précis qui déclenchent des mondes très différents : fsync et O_DSYNC

Les applications expriment leurs besoins de durabilité via des appels système comme fsync(), fdatasync(), et des flags comme O_SYNC /
O_DSYNC. Sur un système de fichiers local, cela correspond assez directement à « ne me mens pas ».

Sur NFS, ces appels se transforment en opérations au niveau protocole qui peuvent inclure COMMIT ou écritures stables — selon la configuration du client. Si le client
choisit de regrouper ou de retarder COMMIT, votre serveur peut être « correct » mais les attentes de l’application violées après un crash.

Blague n°1 : la durabilité NFS, c’est comme la politique de bureau — tout le monde est d’accord en réunion, et les vraies décisions se prennent ensuite en privé.

Côté ZFS : sync, ZIL, SLOG, txg, et où réside la vérité

Transaction groups : le grand rythme sous-jacent

ZFS regroupe les changements en mémoire et les committe périodiquement sur disque en groupes de transactions (TXG). C’est une stratégie de performance centrale : elle transforme
de petites écritures aléatoires en schémas d’E/S plus séquentiels et permet à ZFS d’optimiser l’allocation.

Le cadence des TXG est typiquement de l’ordre de quelques secondes. C’est acceptable pour les écritures asynchrones : l’application reçoit un ACK rapidement, et ZFS committe plus tard. Mais pour
les écritures synchrones, attendre le prochain commit TXG est trop lent. C’est là qu’intervient le ZIL.

ZIL : journalisation d’intention pour les opérations synchrones

Le ZIL (ZFS Intent Log) enregistre suffisamment d’informations sur les opérations synchrones pour pouvoir les rejouer après un crash. Ce n’est pas un journal complet
de tous les changements. C’est un filet de sécurité spécifiquement pour les opérations qui avaient été accusées de « terminées » avant que le TXG ne les rende permanentes sur disque.

Sans périphérique séparé, le ZIL vit sur le pool principal. Avec un périphérique de journal séparé (SLOG), ZFS peut placer ces enregistrements d’intention sur un stockage plus rapide et à faible
latence, réduisant le coût des ACK synchrones.

Opérationnellement : le ZIL est l’endroit où vous payez pour l’honnêteté. Si vous exigez des sémantiques synchrones, vous demandez à ZFS d’effectuer du travail supplémentaire maintenant plutôt que plus tard.

La propriété dataset sync : le levier souvent mal utilisé

ZFS expose sync comme propriété de dataset :
standard, always, et disabled.

  • sync=standard : honorer les requêtes sync. Si le client/l’appli demande du sync, le faire ; sinon tamponner.
  • sync=always : traiter toutes les écritures comme synchrone, même si le client ne l’a pas demandé. C’est le mode « je ne vous fais pas confiance ».
  • sync=disabled : mentir. Accuser réception des requêtes sync sans réellement garantir le stockage stable.

En territoire NFS, sync=standard n’est pas synonyme de « sécurisé ». C’est « sécurisé si le client demande la sécurité. »
Et les clients ont plusieurs façons de ne pas la demander.

Ce que « sûr » signifie dépend de l’ensemble de la chaîne

Un ACK synchrone n’est honnête que jusqu’au maillon le plus faible qui peut prétendre que les données sont stables alors qu’elles ne le sont pas :

  • Paramètres de cache des disques et comportement des flush
  • Cache du contrôleur et protection batterie/flash
  • Protection contre la perte d’alimentation (PLP) et ordre d’écriture du périphérique SLOG
  • Stack de stockage hyperviseur si vous êtes virtualisé
  • Cache côté client et options de montage NFS

Vous pouvez tout faire correctement sur ZFS et être brûlé par un client configuré pour considérer close() comme « suffisant »
sans forcer les écritures stables, ou par un SSD « rapide » qui accuse réception des flushs comme s’il lisait une histoire pour s’endormir.

Une citation qui tient : « L’espoir n’est pas une stratégie. » — idée souvent paraphrasée attribuée aux ingénieurs ops/fiabilité.

Pourquoi le client change la sécurité : montages, caches et habitudes applicatives

Les options de montage NFS peuvent échanger silencieusement durabilité contre débit

Sur de nombreux clients, la différence entre « sync » et « async » n’est pas un simple interrupteur ; c’est une combinaison de comportements :

  • Mise en cache au montage. Le cache d’attributs, le cache d’entrées de répertoire et le cache de pages côté client peuvent changer le moment où les écritures sont poussées.
  • Rassemblement d’écritures. Les clients peuvent regrouper de petites écritures en RPC plus grands, réduisant l’overhead mais retardant la stabilité.
  • Comportement de commit. Le client peut envoyer des COMMIT paresseusement, ou seulement sur fsync/close selon la politique.
  • Montages hard vs soft. Pas un contrôle direct de durabilité, mais cela change la façon dont les erreurs se manifestent (blocage vs erreur), ce qui modifie le comportement applicatif sous stress.

Vous ne pouvez pas supposer les valeurs par défaut du client. Elles varient selon la version d’OS, la distribution, le noyau, et même les baselines de sécurité.

Les applications ne sont pas cohérentes sur les flushs

Les bases de données sont les coupables évidents, mais beaucoup de systèmes « ennuyeux » font des choses dangereuses :

  • Queues de messages qui groupent fsync toutes les N messages.
  • Loggers qui appellent fsync uniquement lors de la rotation.
  • Systèmes de build qui « s’en fichent » jusqu’à ce qu’ils en aient besoin (dépôts d’artefacts, n’importe qui ?).
  • ETL qui supposent que rename est atomique et durable partout.

Sur des systèmes de fichiers locaux, ces patterns peuvent être acceptables. Sur NFS, ils peuvent devenir de longues fenêtres de données reconnues mais non stables,
surtout si le client utilise des écritures instables et retarde COMMIT.

Exports serveur NFS : ce que vous autorisez compte

Les paramètres d’export côté serveur ne disent généralement pas « mentir sur la durabilité », mais ils influencent le comportement du client : modes de sécurité, subtree checking,
stabilité de FSID, et comportements de délégation (sur NFSv4) peuvent changer le caching et les patterns de retry.

Sur les serveurs Linux (y compris de nombreuses installations ZFS-on-Linux), exportfs et les threads nfsd peuvent devenir des goulets d’étranglement
qui ressemblent à de la « latence disque ». Si vous ne mesurez pas les deux, vous accuserez la mauvaise couche et « résoudrez » en achetant des SSD.

Réalités du SLOG : quand il aide, quand il nuit et quand c’est cosmétique

Quand un SLOG aide

Un SLOG aide lorsque vous avez de nombreuses écritures synchrones et que la latence du pool est supérieure à celle d’un périphérique dédié à faible latence. C’est
fréquent avec :

  • Charges NFS où les clients émettent fréquemment fsync / écritures stables (bases de données, images VM sur NFS, certains systèmes de mail)
  • Petites écritures synchrones aléatoires où les vdevs du pool sont des HDD ou chargés
  • Applications sensibles à la latence où chaque écriture sync bloque un thread

Le SLOG concerne la latence d’ACK, pas le débit. Si vous n’êtes pas fortement orienté sync, il ne fera pas beaucoup de différence.

Quand un SLOG ne sert à rien

Si la plupart des écritures sont asynchrones, le chemin ZIL/SLOG n’est pas votre goulet. Votre goulet est le débit de commit TXG, les limites de données sales, ou l’amplification de lecture.

Aussi : si vos clients effectuent des écritures instables et retardent COMMIT, un SLOG rapide ne sert que lors du COMMIT. Jusque-là, vous ne payez pas le coût sync — ce qui signifie que vous pourriez accumuler du risque au lieu de résoudre la latence.

Quand un SLOG nuit

Un mauvais périphérique SLOG peut détruire la latence. ZFS envoie des enregistrements d’intention sync selon un schéma qui nécessite des écritures à faible latence et des flushs honnêtes. Les SSD grand public ont souvent :

  • une latence imprévisible sous écritures sync soutenues,
  • pas de protection contre la perte d’alimentation,
  • des accusés de flushs optimistes,
  • un effondrement quand le cache SLC est épuisé.

C’est ainsi que vous obtenez le graphe classique : p99 de latence d’écriture correct… jusqu’à ce que soudainement ce ne soit plus le cas, et que cela reste dégradé.

Blague n°2 : un SSD « rapide » sans protection contre la perte d’alimentation, c’est comme un CV qui met « team player » — techniquement possible, mais il faut vérifier.

sync=always : l’option nucléaire qui est parfois correcte

Si vous ne pouvez pas faire confiance aux clients pour demander correctement la stabilité, sync=always est l’outil brut qui force ZFS à traiter chaque écriture comme synchrone. Il réduit l’espace de créativité côté client.

Il augmente aussi la latence, et exposera chaque maillon faible : SLOG, pool, contrôleur et réseau. Utilisez-le de façon chirurgicale : par dataset, par export, pour les charges qui en ont réellement besoin.

Playbook de diagnostic rapide

La manière la plus rapide de déboguer une douleur sync NFS-sur-ZFS est d’éviter de débattre la philosophie et de répondre plutôt à trois questions dans l’ordre :
(1) faisons-nous des écritures sync, (2) où est la latence, et (3) le système ment-il sur la durabilité des flushs ?

Première étape : confirmer si la charge est réellement synchrone

  • Vérifier la propriété sync du dataset ZFS et confirmer les attentes.
  • Vérifier les options de montage NFS côté client et si les applis appellent fsync.
  • Observer l’activité ZIL/SLOG et les taux de COMMIT NFS.

Deuxième étape : localiser le domaine du goulet (CPU, réseau, SLOG, pool)

  • Si les threads du serveur NFS sont saturés, ce n’est pas « latence disque », c’est un goulet serveur.
  • Si la latence du SLOG pique, les écritures sync piqueront même si le pool va bien.
  • Si les vdevs du pool sont occupés, les commits TXG traîneront et les charges asynchrones se bloqueront.

Troisième étape : valider le comportement du stockage stable de bout en bout

  • Confirmer que le périphérique SLOG a PLP et n’est pas virtualisé derrière des caches en writeback.
  • Confirmer la politique de cache d’écriture des disques et que les flushs sont honorés.
  • Confirmer que vous n’exécutez pas sync=disabled quelque part « temporairement ».

Tâches pratiques : commandes, sorties et décisions

Voici les vérifications que j’exécute réellement quand quelqu’un dit « NFS est lent » ou « nous avons perdu des écritures ». Chaque élément inclut : commande, sortie exemple, ce que cela signifie,
et la décision à prendre.

Tâche 1 : Identifier le dataset et sa politique sync

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

Ce que cela signifie : ZFS honorera les demandes sync, mais ne les forcera pas.

Décision : Si la charge exige une durabilité stricte indépendamment du comportement client, envisagez sync=always (sur ce dataset), pas à l’échelle du pool.

Tâche 2 : Chercher la mauvaise surprise classique : 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

Ce que cela signifie : Une sous-arborescence ment sur les écritures sync.

Décision : Traitez cela comme un risque en production. Si c’est intentionnel, documentez le périmètre et les attentes de perte en cas de crash ; sinon corrigez-le immédiatement.

Tâche 3 : Confirmer s’il y a (ou non) un 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

Ce que cela signifie : Les écritures sync peuvent être accusées via le périphérique de log.

Décision : Si vous avez beaucoup d’écritures sync sur NFS et pas de SLOG, attendez-vous à une latence plus élevée. Si vous avez un SLOG, validez qu’il est réellement fiable.

Tâche 4 : Vérifier la latence du SLOG sous charge (indicateur rapide)

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

Ce que cela signifie : ~1.2 ms d’attente d’écriture à un taux d’écriture élevé est acceptable ; si vous voyez des dizaines de ms, votre SLOG est problématique.

Décision : Si w_await pique durant des « sync storms », remplacez le SLOG par un périphérique PLP grade ou retirez-le s’il aggrave les choses.

Tâche 5 : Vérifier que recordsize du dataset correspond à la charge (latence vs débit)

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

Ce que cela signifie : logbias=latency incite ZFS à privilégier le log pour les écritures sync ; recordsize influence l’amplification lecture/écriture pour les bases de données.

Décision : Pour I/O aléatoire de type DB, envisagez un recordsize plus petit (par ex. 16K) après mesure. Conservez logbias=latency pour les datasets sync-intensifs.

Tâche 6 : Vérifier la pression TXG et les stalls liés aux « dirty data »

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

Ce que cela signifie : Si dirty_data frôle dirty_data_max, ZFS throttlera les écrivains — les clients verront des stalls sans lien direct avec la sémantique NFS.

Décision : Si vous throttlez, examinez le débit de commit du pool, la saturation des vdevs, et si un « fast SLOG » masque un pool lent.

Tâche 7 : Surveiller la saturation des threads du serveur NFS (nfsd sous Linux)

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

Ce que cela signifie : Les threads nfsd consomment du CPU. Cela peut ressembler à une « latence stockage » depuis le côté client.

Décision : Si le CPU est élevé, ajustez le nombre de threads nfsd, vérifiez le coût du chiffrement/auth, et profilez le mix RPC avant de toucher à ZFS.

Tâche 8 : Confirmer les versions du protocole NFS et les options de montage sur un client

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

Ce que cela signifie : NFSv3 avec grand rsize/wsize ; montage hard. Aucune option explicite sync/async affichée ici, mais la version compte pour le comportement COMMIT.

Décision : Si les attentes de durabilité sont strictes, évaluez le comportement de NFSv4.1+ et les options client ; alignez un profil de montage supporté par classe de charge.

Tâche 9 : Observer le taux de COMMIT sur le serveur (le client force-t-il la stabilité ?)

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

Ce que cela signifie : Beaucoup d’écritures mais relativement peu de commits suggèrent des écritures instables regroupées, ou des charges qui n’appellent pas fsync fréquemment.

Décision : Si la charge prétend que « chaque transaction est durable », ce décalage est votre piste. Auditez la politique fsync de l’application et le cache client.

Tâche 10 : Confirmer les options d’export servies

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)

Ce que cela signifie : Export standard ; wdelay peut modifier le comportement de regroupement d’écritures côté serveur.

Décision : Ne touchez pas au hasard aux options d’export en espérant la durabilité. Utilisez les exports pour contrôler l’accès et la correction ; utilisez ZFS et la politique client pour les sémantiques sync.

Tâche 11 : Valider que le périphérique SLOG n’encoie pas d’erreurs ou n’est pas contourné

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
--------------------------  -----  -----  -----  -----  -----  -----

Ce que cela signifie : Le périphérique de log reçoit de l’activité d’écriture. S’il est toujours à zéro pendant une charge « sync », soit la charge n’est pas sync, soit votre config n’est pas celle que vous croyez.

Décision : Corrélez avec le comportement de l’application et COMMIT NFS. N’achetez pas de matériel avant de prouver que des écritures sync ont lieu.

Tâche 12 : Vérifier si la latence perçue par le client est réseau ou I/O serveur

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

Ce que cela signifie : Le RTT est sub-millisecondes ; le réseau n’est pas la source principale de latence sur ce snapshot.

Décision : Concentrez-vous sur le CPU serveur et la latence stockage. Si le RTT est de plusieurs ms ou si les retransmissions augmentent, réglez le réseau d’abord.

Tâche 13 : Vérifier que ZFS ne souffre pas d’erreurs de checksum ou de périphériques (tueurs de latence silencieux)

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

Ce que cela signifie : Pas de défauts connus. Si ce n’est pas propre, arrêtez les tentatives « tuning » et lancez la réponse d’incident.

Décision : Remplacez les périphériques défaillants, lancez un scrub, et réévaluez. Un pool dégradé peut ressembler exactement à « NFS sync est lent ».

Tâche 14 : Confirmer que sync=always est appliqué là où vous le pensez

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

Ce que cela signifie : ZFS traitera chaque écriture comme sync pour ce dataset, indépendamment des demandes client.

Décision : Attendez-vous à une latence plus élevée ; assurez-vous que le SLOG est solide et surveillez p95/p99. Utilisez ceci pour les données qui doivent survivre aux crashs sans ambiguïté.

Trois mini-récits d’entreprise tirés du terrain

Mini-récit 1 : L’incident causé par une mauvaise hypothèse

Une entreprise de taille moyenne exploitait un cluster NFS basé sur ZFS pour des services internes. Une équipe hébergeait le stockage persistant d’une file de travail sur un export NFS.
Les équipes stockage avaient fait leurs devoirs : pool en miroir, SLOG avec un « NVMe rapide », sync=standard, et des scrubs réguliers.

L’équipe de la file a migré d’une distribution Linux à une autre lors d’une mise à jour de plateforme. Même version d’application, mêmes données, même cible de montage.
Après un incident d’alimentation (pas même spectaculaire ; le genre qui devient dramatique après coup), un lot d’éléments reconnus a réapparu comme « jamais traités ».
Pire : une petite portion a disparu.

Le premier tour de debug fut prévisible. Les gens se sont disputés sur « ZFS est copy-on-write donc il ne peut pas perdre de données » et « NFS est fiable sur TCP »
comme si ces affirmations clôturaient l’incident. L’équipe stockage a lancé zpool status — sain. L’équipe réseau a montré peu de retransmissions.
L’équipe de la file insistait sur le fait que leur appli « fsyncs sur commit ».

La percée est venue de l’examen du comportement protocolaire, pas des croyances. Sur le client, nfsstat -m montrait une version NFS différente et des valeurs de cache différentes.
Côté serveur, les appels COMMIT étaient bien inférieurs aux attentes sous charge. L’application appelait fsync — mais le pattern d’I/O était tamponné et la politique client retardait la frontière de stabilité plus que l’équipe ne le pensait.

La correction fut ennuyeuse : standardiser le profil de montage client pour cette charge, et basculer le dataset en sync=always jusqu’à ce que chacun puisse prouver le contrat de durabilité de l’application. Cela a coûté en latence. Cela a apporté de la clarté. C’est un compromis que vous pouvez expliquer dans un postmortem sans transpirer.

Mini-récit 2 : L’optimisation qui s’est retournée contre eux

Une autre organisation servait des répertoires utilisateurs et des artefacts de build sur NFS. Ils voulaient accélérer les CI. Quelqu’un a trouvé le bouton magique :
sync=disabled sur le dataset servant le cache de build. C’était présenté comme « suffisamment sûr parce que les artefacts peuvent être reconstruits. »

Pendant un temps, tout allait bien. Les pipelines CI se sont accélérés. La latence stockage est tombée. Tout le monde a félicité le ticket de changement pour « améliorer l’utilisation ».
Puis une dépendance non évidente les a mordus : un pipeline de release écrivait aussi des métadonnées signées dans le même arbre. Les métadonnées étaient « petites et rapides » ,
donc personne n’y pensait. Elles étaient aussi difficiles à reconstruire sans refaire toute la cérémonie de sécurité.

Un crash hôte est survenu en pleine release. ZFS avait accusé réception des requêtes sync sans stockage stable. Le fichier de métadonnées existait, mais son contenu était plus ancien.
Quelques builds ont été livrés avec des métadonnées désaccordées. Pas catastrophique, mais l’équipe conformité s’en est mêlée, et soudain « artefacts reconstruisables »
ne semblait plus une bonne affirmation générale.

Le retour de bâton n’était pas dû à ZFS qui se montrait capricieux. C’était l’organisation qui avait été imprécise sur les classes de données sur des exports partagés. Ils ont optimisé une charge et
entraîné accidentellement une charge plus stricte dans la même politique de durabilité.

La correction fut structurelle : séparer les datasets selon les exigences de durabilité, appliquer sync=standard ou sync=always en conséquence, et garder
les « astuces rapides et dangereuses » derrière des points de montage et contrôles d’accès explicites. Aussi : supprimez « temporaire » des tickets de changement ; c’est le mot le plus permanent en ops.

Mini-récit 3 : La pratique ennuyeuse mais correcte qui a sauvé la mise

Une société de services financiers disposait d’une plateforme ZFS NFS hébergeant un mix de services, y compris une application stateful dont le fournisseur était très explicite :
« doit avoir des écritures stables sur commit. » Les ingénieurs stockage n’ont pas deviné. Ils ont créé un dataset et un export séparés juste pour cette appli.
sync=always, un SLOG PLP validé, et une norme de montage client écrite. Pas d’exceptions.

Ce n’était pas populaire. L’équipe applicative se plaignait de la latence comparée à leur SSD local précédent. L’équipe stockage ne s’est pas laissée émouvoir.
Ils ont montré un test simple : avec sync=standard et le montage actuel de l’appli, la fréquence d’appel fsync était plus faible que prévu et le comportement COMMIT était en rafales. Avec sync=always, la courbe de latence était stable et la fenêtre de perte en cas de crash devenait simple à raisonner.

Des mois plus tard, un incident moche s’est produit ailleurs : un bug de firmware causait des redémarrages de certains serveurs sous des patterns d’I/O spécifiques.
De nombreux systèmes avaient des anomalies après redémarrage. Cette appli stateful non. Pas de corruption mystérieuse, pas de « rejouer des jobs », pas de rollback silencieux.

La raison n’était pas de l’héroïsme. C’était que quelqu’un avait écrit l’exigence de durabilité, l’avait appliquée côté serveur, et avait refusé de la co-héberger avec
des charges « rapides mais négligentes ». C’est le type d’ennui que vous voulez sur votre CV.

Erreurs courantes : symptômes → cause racine → correction

1) « Nous avons mis sync=standard, donc nous sommes protégés. »

Symptômes : Perte de données après crash malgré « sync activé », ou durabilité incohérente selon les clients.

Cause racine : Les clients n’ont pas demandé des écritures stables de façon cohérente (écritures instables + COMMIT retardé, politique de cache, appli qui ne fait pas fsync).

Correction : Pour des charges strictes, utilisez sync=always par dataset/export ; standardisez les options de montage client ; vérifiez le comportement COMMIT/fsync avec des métriques.

2) « Un SLOG rendra tout plus rapide. »

Symptômes : Pas d’amélioration, ou pire p99 après ajout d’un SLOG.

Cause racine : La charge est majoritairement asynchrone ; ou le périphérique SLOG a une mauvaise latence/honnêteté de flush.

Correction : Mesurez d’abord le taux COMMIT/écritures sync. Utilisez un SLOG PLP grade ou retirez le mauvais. Ne faites pas de cargo-cult de SLOGs.

3) « sync=disabled est acceptable pour les données non critiques. »

Symptômes : Fichiers aléatoirement obsolètes, métadonnées tronquées, « il existe mais il est plus ancien », réconciliation post-crash douloureuse.

Cause racine : Les données non critiques partageaient un dataset/export avec des données critiques, ou « non critique » s’est avéré critique lors d’un incident.

Correction : Séparez les datasets par classe de durabilité. Verrouillez les exports dangereux. Rendez le risque explicite et traçable.

4) « NFS est lent ; les disques doivent être lents. »

Symptômes : Forte latence côté client mais pool semble correct ; pics CPU sur le serveur.

Cause racine : Saturation des threads nfsd, overhead d’auth, mix RPC riche en métadonnées, ou effets de sérialisation d’un client unique.

Correction : Mesurez le CPU serveur, le mix d’opérations nfsstat, le nombre de threads, et le RTT réseau. Scalez les threads nfsd et tunez les exports avant d’acheter des disques.

5) « Nous avons perdu des écritures, donc ZFS est corrompu. »

Symptômes : Perte au niveau application sans erreurs de pool ; pas d’erreurs de checksum.

Cause racine : Fenêtre de perte due au comportement async ou flushs mensongers (sync disabled, mauvais SLOG/cache disque).

Correction : Auditez les propriétés sync et la sémantique de flush du matériel ; appliquez des sémantiques sync côté serveur pour les données critiques.

6) « Nous sommes passés à NFSv4 donc c’est réglé. »

Symptômes : Même confusion sur la durabilité, messages d’erreur différents.

Cause racine : La version seule n’oblige pas les applis à appeler fsync ni les clients à exiger un stockage stable au bon moment.

Correction : Continuez à vous concentrer sur : comportement de flush applicatif, politique de cache client, propriété sync du dataset, et stockage stable honnête.

Listes de vérification / plan étape par étape

Plan A : Vous voulez une durabilité stricte pour une charge NFS

  1. Créer un dataset dédié. Ne le partagez pas avec des charges « rapides et négligentes ».
  2. Définir les sémantiques sync côté serveur. Utilisez sync=always si vous ne pouvez pas contrôler entièrement le comportement de flush client.
  3. Utiliser un SLOG validé si la latence sync compte. PLP requis ; SLOG en miroir si vous ne pouvez pas tolérer le risque de perte du périphérique de log pour la disponibilité.
  4. Standardiser les montages client. Documenter la version NFS et les options de montage supportées. Considérez les déviations comme non supportées.
  5. Tester le comportement au crash. Pas des « benchmarks », mais des simulations de reboot/perte d’alimentation en staging qui ressemblent à la production.
  6. Surveiller les signaux du protocole. Suivre les taux write/commit, la latence p95, et la saturation CPU serveur.

Plan B : Vous voulez la performance et acceptez une certaine perte au crash

  1. Rendre explicite la perte acceptable. « Un peu » n’est pas un nombre ; définissez une méthode de récupération et une fenêtre attendue.
  2. Conserver sync=standard et éviter sync=disabled sauf isolation. Si vous devez désactiver sync, faites-le sur un dataset séparé et rendez-le visible.
  3. Optimiser pour le débit du pool. Concentrez-vous sur le layout des vdevs, la stratégie ARC/L2ARC, et le comportement TXG plutôt que sur le SLOG.
  4. Réduire le rayon d’impact. Séparez les exports par classe de charge pour que le choix risqué n’infecte pas tout.

Plan C : Vous avez hérité d’une plateforme NFS/ZFS mystère et personne ne sait ce qui est sûr

  1. Inventorier les datasets et propriétés sync. Trouvez tout sync=disabled immédiatement.
  2. Cartographier les exports vers les datasets. Assurez-vous que les charges critiques ne reposent pas sur un export « performance ».
  3. Échantillonner les montages client. Collecter des nfsstat -m de clients représentatifs.
  4. Mesurer le taux COMMIT et l’activité SLOG. Déterminer si les clients exigent réellement des écritures stables.
  5. Choisir une posture de durabilité par défaut. Ma préférence : sûr par défaut pour les charges stateful ; les hacks perf doivent être opt-in et isolés.

FAQ

1) Si je définis sync=standard, les écritures NFS sont-elles durables ?

Durables seulement lorsque le client/l’application demande les sémantiques synchrones (écriture stable / comportement COMMIT). Si le client bufferise et retarde la stabilité,
ZFS bufferisera aussi volontiers. Pour une durabilité stricte indépendante du comportement client, utilisez sync=always sur ce dataset.

2) Est-ce que hard vs soft change la durabilité NFS ?

Pas directement. Cela modifie le comportement en cas d’erreur. hard a tendance à bloquer et réessayer, ce qui est généralement correct pour l’intégrité des données ; soft
peut renvoyer des erreurs que les applis mal gèrent. Mais la durabilité concerne surtout les écritures stables/COMMIT et la politique sync côté serveur.

3) Ajouter un SLOG est-il toujours la bonne décision pour NFS ?

Seulement si vous avez un taux significatif d’écritures synchrones et que la latence du pool est le goulet. Si les clients n’émettent pas souvent des écritures stables/COMMIT,
un SLOG n’aidera pas. Un mauvais SLOG peut beaucoup nuire.

4) Quelle est la différence entre ZIL et SLOG déjà ?

ZIL est le mécanisme (journal d’intention pour les opérations synchrones). SLOG est un périphérique dédié où ZFS place ce journal pour réduire la latence. Sans SLOG,
le ZIL réside sur le pool principal.

5) Si nous utilisons sync=disabled, peut-on quand même être « grossièrement sûr » ?

Vous pouvez être « plutôt chanceux ». sync=disabled accuse réception des écritures sync sans les rendre stables. Après un crash, vous pouvez perdre des opérations récentes reconnues,
y compris des mises à jour de métadonnées. Utilisez-le seulement pour des datasets isolés où vous acceptez vraiment ce risque.

6) NFSv4 garantit-il une meilleure consistance au crash que NFSv3 ?

Pas de façon générale. NFSv4 ajoute des fonctionnalités stateful et peut améliorer certains comportements, mais la consistance au crash dépend toujours de la politique client,
de la discipline fsync de l’app et d’un stockage serveur honnête.

7) Pourquoi certains clients montrent-ils de fortes pointes de latence lors d’un checkpoint de base de données ?

Les checkpoints déclenchent souvent des rafales de fsync/commit. Si le dataset est sync=always ou si l’app utilise O_DSYNC, ZFS routtera cela via ZIL/SLOG. Si le SLOG est faible ou saturé,
la p99 explose précisément au moment du checkpoint.

8) Dois-je mirrorer le SLOG ?

Mirrorer un SLOG concerne la disponibilité, pas l’intégrité des données au sens usuel. Perdre un périphérique SLOG peut causer des problèmes de pool ou forcer des basculements dangereux selon la plateforme.
Pour des systèmes qui doivent rester en ligne, mirrorer le SLOG vaut souvent la peine.

9) Puis-je « forcer les clients à être sûrs » depuis le serveur ?

Vous ne pouvez pas obliger une appli à appeler fsync, mais vous pouvez forcer ZFS à traiter toutes les écritures comme sync avec sync=always. Cela réduit la dépendance
à la discipline de flush client. C’est l’outil d’application côté serveur le plus pratique.

10) Quelle est la méthode la plus fiable pour valider les affirmations de durabilité ?

Des tests de crash contrôlés avec le vrai système client, les vraies options de montage, et la vraie application. Mesurez ce que l’appli croit être commité, puis crash/reboot et vérifiez les invariants.
Les benchmarks ne disent pas la vérité ici ; les tests de crash le font.

Prochaines étapes réalisables cette semaine

  1. Inventorier les paramètres sync : exécutez zfs get -r sync sur vos arbres NFS et signalez tout sync=disabled.
  2. Choisir deux exports critiques et standardiser les profils de montage client ; collecter nfsstat -m de clients représentatifs.
  3. Mesurer les ratios COMMIT/write sur le serveur pendant les pics ; comparer avec ce que les applis revendiquent.
  4. Valider votre SLOG en mesurant la latence et en confirmant qu’il possède PLP ; si vous ne pouvez pas le prouver, traitez-le comme suspect.
  5. Séparer les datasets par classe de durabilité afin qu’un tweak perf d’une équipe ne modifie pas le modèle de risque d’une autre.
  6. Effectuer un test de crash en staging : une charge qui fait des « commits », un reboot forcé, et une étape de vérification. C’est étonnant comme les mythes tombent vite.

L’objectif n’est pas de rendre NFS « sûr » en abstraction. L’objectif est de rendre la sécurité explicable, mesurable et applicable — sans dépendre des valeurs par défaut des clients aujourd’hui.

← Précédent
Docker « Text file busy » lors du déploiement : la correction qui stoppe les redémarrages instables
Suivant →
ZFS Raw Send : Répliquer des données chiffrées sans partager les clés

Laisser un commentaire