Héritage des propriétés ZFS : la surprise qui modifie les datasets enfants

Cet article vous a aidé ?

Les incidents ZFS les plus coûteux ne commencent pas souvent par des pannes de disque. Ils commencent par de la confiance.
Quelqu’un modifie un dataset « juste pour corriger ce truc », et une semaine plus tard la moitié de la flotte est
plus lente, les backups explosent, ou un point de montage manque au démarrage. Rien ne semble « cassé » de façon
évidente. ZFS est toujours sain. Le pool est ONLINE. Et pourtant le système se comporte différemment.

Le coupable est souvent l’héritage des propriétés ZFS : la fonctionnalité que vous adorez quand vous scalez proprement —
et la fonctionnalité qui fait qu’un dataset enfant change silencieusement quand vous ne le vouliez pas.
Si vous gérez de la production, traitez l’héritage comme des tables de routage : puissant, d’impact global,
et pas quelque chose à « essayer vite fait ».

L’héritage est la fonctionnalité et le piège

Les datasets ZFS (systèmes de fichiers et volumes) vivent dans un arbre. Les propriétés vivent sur des nœuds de cet arbre.
Certaines propriétés sont héritées le long de l’arbre à moins d’être surchargées localement. C’est volontaire : cela vous
permet de définir une politique sur un parent et d’avoir tous les enfants qui la suivent.

Le piège est humain : nous pensons en « ce dataset » et ZFS pense en « cette sous-arborescence ». Vous définissez une
propriété sur tank/apps pour résoudre un souci d’appli, et vous venez de changer le comportement de
tank/apps/postgres, tank/apps/redis, et du tank/apps/tmp oublié que
le runner CI écrase depuis deux ans.

L’héritage n’est pas qu’une question de « bons défauts ». Il peut changer le comportement de montage, les performances, la visibilité des snapshots,
la sémantique d’accès, la gestion des clés de chiffrement, et les garanties d’espace. Cela signifie que l’héritage peut :

  • corriger rapidement quand il est utilisé délibérément,
  • causer des incidents « personne n’a touché ce dataset » quand il est utilisé à la légère,
  • créer une dérive de configuration qui résiste au débogage (« c’est hérité d’où ? »).

Une règle opérationnelle que j’aime : si vous ne pouvez pas nommer les datasets parents qui seront impactés par votre changement,
vous n’êtes pas prêt à exécuter zfs set.

Blague #1 : l’héritage ZFS, c’est comme le Wi‑Fi du bureau — tout le monde en profite jusqu’à ce que quelqu’un « optimise » et que tout l’étage
commence à charger en boucle.

Le seul modèle mental qui scale : local vs hérité vs défaut

Quand vous interrogez une propriété, ZFS peut rapporter la valeur et la source. C’est la source qui vous sauve :

  • local : définie explicitement sur ce dataset
  • inherited from <dataset> : héritée d’un ancêtre
  • default : valeur par défaut compilée pour votre implémentation/version de ZFS
  • temporary : définie à l’exécution (moins courant ; pensez aux options de montage dans certains stacks)

Voilà pourquoi cela importe en production : deux datasets peuvent afficher la même valeur mais se comporter différemment
opérationnellement parce que la source vous dit ce qui se passera au prochain changement de fenêtre.
Une valeur par défaut reste stable jusqu’à ce que vous la changiez. Une valeur héritée peut changer quand quelqu’un modifie le parent.
Une valeur locale est « collante » mais peut devenir obsolète par rapport à la politique.

Règles d’héritage qui comptent en vrai

  • La plupart des propriétés sont héritables ; pas toutes. ZFS a des propriétés en lecture seule ou
    intrinsèquement locales (par exemple, used, available).
  • Les enfants héritent sauf surcharge locale. Si un enfant a une valeur locale, les changements du parent
    ne l’affecteront pas.
  • zfs inherit supprime la surcharge locale. Cela ne « remet pas à la valeur par défaut ». Cela supprime
    le réglage local et la valeur devient héritée (ou défaut, s’il n’y a rien à hériter).
  • Les clones compliquent l’histoire. Les clones naissent d’un snapshot d’origine ; ils existent toujours
    dans l’arbre de datasets et héritent donc des parents comme n’importe quel autre dataset.
  • Les propriétés liées au montage peuvent échouer d’une façon qui ressemble à des problèmes systemd. Ce ne sont pas des problèmes systemd. C’est ZFS, refusant poliment de monter ce que vous lui avez dit de monter.

Une citation simple que vous pouvez utiliser avec votre équipe stockage

W. Edwards Deming (idée paraphrasée) : « Un système est parfaitement conçu pour obtenir les résultats qu’il obtient. »
Les surprises d’héritage sont votre système qui fonctionne exactement comme conçu.

Propriétés qui mordent fort (et pourquoi)

1) mountpoint, canmount, et readonly : le trio « où est passé mon filesystem ? »

Si vous exploitez des serveurs, vous avez déjà diagnostiqué « ça montait hier » au moins une fois. Le comportement de montage ZFS est piloté par les propriétés.
Un dataset parent avec canmount=off peut être une frontière administrative propre — jusqu’à ce qu’il soit défini sur le mauvais
nœud et que les enfants ne montent plus au démarrage.

mountpoint est hérité par défaut, ce qui est pratique et dangereux. Si vous définissez un
mountpoint parent sur /srv, vous pourriez vous attendre à ce que seul le parent bouge.
À la place, les enfants se montent dessous à moins qu’ils aient des mountpoints locaux. Super quand c’est planifié ; chaos sinon.

2) compression : petit interrupteur, large rayon d’explosion

La compression est héritée. C’est une fonctionnalité : vous pouvez activer compression=lz4 sur un parent et
en bénéficier largement. La surprise : vous pouvez aussi l’éteindre pour une sous-arborescence et faire discrètement gonfler la capacité.
Pire, vous pouvez la changer pour un dataset qui contient à la fois « base de données chaude » et « logs froids » et désormais votre tuning
est un compromis que vous n’aviez pas prévu.

3) recordsize : du tuning qui devient une régression de performance

recordsize est héritable sur les systèmes de fichiers. Le définir incorrectement sur un parent, et vos enfants héritent
d’une taille d’enregistrement qui peut être catastrophique pour leur charge. Un répertoire de données PostgreSQL préfère typiquement
de plus petits blocs pour les IO aléatoires ; une archive média aime les gros blocs. Votre arbre de datasets ne devrait pas les forcer
à porter la même taille.

4) atime : mort par un million de petites écritures

Les mises à jour du temps d’accès (atime=on) peuvent générer une écriture lors des lectures. Sur des datasets très sollicités et orientés lecture
(spools mail, caches web, arbres riches en métadonnées), un atime=on hérité peut transformer « les lectures sont peu coûteuses »
en « les lectures sont aussi des écritures », ce qui affecte latence et usure.

5) snapdir et visibilité des snapshots

snapdir=visible fait apparaître .zfs/snapshot. C’est parfois pratique pour des restaurations self-service,
et parfois un risque opérationnel ou de conformité quand des applications le traversent. L’héritage signifie que vous pouvez exposer
des snapshots là où vous ne le vouliez pas. Ou les cacher là où vous en dépendiez opérationnellement.

6) Quotas, reservations, refreservations : « l’espace » est une politique

Les quotas et réservations peuvent être hérités selon le type de propriété et votre conception. Même lorsqu’ils ne sont pas hérités,
leur interaction avec l’allocation parent/enfant est l’endroit où vit la surprise. La gestion d’espace n’est pas intuitive sous pression.
Vous pouvez vous retrouver avec de l’espace libre au niveau du pool mais « plus d’espace » pour un dataset à cause de réservations,
ou avec un dataset enfant qui grandit sans limite parce que vous supposiez qu’un quota parent le couvrirait.

7) Propriétés de chiffrement : héritage à bords tranchants

Le chiffrement natif ZFS introduit des propriétés héritables comme encryption, keylocation,
keyformat, et d’autres. Le comportement central : les enfants peuvent hériter des réglages de chiffrement du parent lors de leur création.
Mais changer le chiffrement n’est pas comme changer la compression ; vous ne pouvez pas simplement l’activer pour un dataset existant sans
migration send/receive. Les gens essaient quand même. La « surprise » ici est souvent un échec de processus : s’attendre à ce que l’héritage
sécurise rétroactivement les données.

Blague #2 : Changer recordsize sur le dataset parent, c’est comme acheter la même chaise pour tout le monde dans l’entreprise — le dos de quelqu’un va finir par ouvrir un ticket.

Faits intéressants et contexte historique

  • ZFS a été conçu pour remplacer une pile, pas seulement un système de fichiers. Il a fusionné la gestion de volumes et la sémantique des systèmes de fichiers, si bien que les « propriétés » sont devenues des boutons de politique système.
  • Les propriétés ont été conçues pour la délégation. Les premières déploiements de ZFS avaient besoin d’un moyen pour laisser les équipes s’auto-gérer des datasets en toute sécurité ; l’héritage était le mécanisme d’échelle.
  • Les choix par défaut de compression ont évolué. De nombreux stacks modernes recommandent lz4 de façon large ; les conseils plus anciens étaient plus prudents car le CPU était plus rare et les algorithmes différents.
  • recordsize vient d’un monde d’IO séquentiel massif. ZFS s’est optimisé pour les flux ; les bases de données ont forcé la communauté à se discipliner sur le tuning par dataset.
  • Les réglages par défaut d’atime reflètent la tradition Unix, pas la réalité des performances. Les temps d’accès étaient utiles pour des outils et politiques, mais les systèmes modernes paient souvent trop cher pour ça.
  • Le comportement de montage était autrefois plus « magique ». Différentes intégrations OS (héritage Solaris vs ZFS sur Linux) ont façonné la façon dont les propriétés interagissent avec le boot et les services de montage.
  • La visibilité des snapshots est une guerre culturelle. Les admins aiment .zfs/snapshot pour les restaurations ; les propriétaires d’applis détestent les répertoires surprises. La propriété existe parce que les deux ont raison.
  • Les sources de propriété sont un cadeau pour le débogage ajouté tôt. « local vs inherited vs default » est l’une des meilleures décisions UX orientées opérateur dans les outils de stockage.

Tâches pratiques (commandes, sorties, décisions)

Voici les vérifications que j’exécute quand quelqu’un dit « ZFS a changé quelque chose ». Chaque tâche inclut : la commande,
ce que signifie la sortie, et la décision suivante.

Tâche 1 : Voir la valeur de la propriété et d’où elle vient

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

Signification : Le dataset Postgres est compressé parce que son parent l’est. Si vous changez tank/apps,
vous changez Postgres. Décision : si Postgres a besoin d’un comportement spécial, définissez une surcharge locale sur
tank/apps/postgres et ne comptez plus sur le parent pour cette propriété.

Tâche 2 : Auditer une sous-arborescence pour une propriété (attraper la dérive vite)

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

Signification : Un seul réglage local sur tank/apps dicte toute la sous-arborescence.
Décision : soit acceptez cela comme politique, soit séparez les datasets spéciaux avec des recordsize locaux
(par ex. Postgres en 16K ou 8K selon votre stockage et workload).

Tâche 3 : Trouver tout ce qui hérite d’un parent spécifique (rayon d’impact)

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

Signification : Ces datasets se déplaceront si vous changez le mountpoint de tank/apps.
Décision : ne touchez pas au mountpoint du parent sauf si vous avez une fenêtre de maintenance
et un plan de rollback.

Tâche 4 : Vérifier ce qui montera et ce qui ne montera pas

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

Signification : Tous les datasets sont éligibles au montage et sont montés.
Décision : si quelque chose manque, cherchez canmount=off ou mountpoint=none
et tracez d’où c’est hérité.

Tâche 5 : Attraper « nous avons changé le mountpoint parent » avant que le reboot n’intervienne

cr0x@server:~$ zfs set mountpoint=/srv tank/apps
cr0x@server:~$ zfs mount -a
cannot mount 'tank/apps/postgres': mountpoint or dataset is busy

Signification : Le nouveau layout de montage entre en conflit avec des montages existants ou des processus en cours.
Décision : rétablissez immédiatement le mountpoint du parent, ou arrêtez proprement les services et remontez
dans une fenêtre contrôlée. Ne « forcez » pas en plein vol.

Tâche 6 : Identifier les surcharges locales (les endroits où la politique n’atteint pas)

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

Signification : Seuls deux datasets ont des réglages de compression explicites ; tout le reste est hérité ou par défaut.
Décision : confirmez que ces surcharges locales sont intentionnelles. Les surcharges locales sont l’endroit où les « standards »
cessent silencieusement de s’appliquer.

Tâche 7 : Réinitialiser un dataset enfant pour qu’il hérite à nouveau (annulation contrôlée des personnalisations)

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

Signification : Vous avez supprimé la surcharge locale, et maintenant Postgres suit le parent.
Décision : faites cela seulement si vous êtes sûr que la politique du parent est correcte pour la charge. « Inherit »
n’est pas un bouton gratuit de nettoyage.

Tâche 8 : Voir quelles propriétés sont définies localement sur un dataset (profil rapide)

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

Signification : Ce sont des déviations délibérées par rapport à la politique héritée/par défaut.
Décision : si vous ne les attendiez pas, vous avez de la dérive de configuration. Remontez qui les a définies et pourquoi.
Si vous les attendiez, documentez-les car quelqu’un va vouloir « les nettoyer » plus tard.

Tâche 9 : Utiliser zfs diff pour comprendre les changements visibles par snapshot (vérif réalité ops)

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

Signification : C’est le suivi au niveau fichier entre snapshots, pas des changements de propriétés.
Décision : si les performances ont changé mais pas les données, vous regardez probablement l’héritage des propriétés ou
le comportement du kernel/module — pas « quelqu’un a édité des fichiers ».

Tâche 10 : Confirmer la disposition des datasets et repérer des enfants qui ne devraient pas exister

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

Signification : Un dataset tmp volumineux dans la sous-arborescence apps est un classique danger d’héritage.
Décision : déplacez les workloads éphémères vers leur propre domaine de politique parent (ou définissez des surcharges locales),
surtout pour recordsize, sync, et atime.

Tâche 11 : Vérifier si des quotas ou réservations contraignent silencieusement des enfants

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

Signification : Vous avez réservé 100G ; le dataset n’a que 20G « disponibles » même si le pool a plus.
Décision : si Postgres approche 100G et que vous n’aviez pas prévu de pression d’espace stricte, ajustez ou retirez
la réservation. Si c’était intentionnel, alertez — les réservations sont de la politique, pas des détails triviaux.

Tâche 12 : Valider la compression et l’espace logique (est-ce utile ou nuisible ?)

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  -

Signification : La compression est activée et efficace. Si compressratio est ~1.00x, vous dépensez
du CPU pour peu de bénéfice. Décision : conservez la compression pour la plupart des workloads mixtes ; désactivez-la seulement sur preuve, pas par intuition.

Tâche 13 : Repérer les collisions de mountpoint (mode d’échec courant d’héritage)

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

Signification : Deux datasets partagent le même mountpoint. ZFS ne montera pas les deux joyeusement.
Décision : corrigez les mountpoints avant le jour du reboot. Les collisions se manifestent souvent par des « montages aléatoires manquants »
selon l’ordre des montages.

Tâche 14 : Confirmer quelles propriétés sont héritables (savoir à quel jeu vous jouez)

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

Signification : Certaines propriétés affichent une source significative ; d’autres sont dynamiques et non pertinentes.
Décision : concentrez les audits d’héritage sur les propriétés comportementales (compression, recordsize, atime,
mountpoint, canmount, snapdir, sync, logbias, primarycache/secondarycache, xattr, acltype, propriétés liées au chiffrement).

Plan de diagnostic rapide

C’est le flux de triage « c’est lent / ça ne monte pas / plus d’espace » qui vous amène rapidement au coupable héritage.
Exécutez-le comme une checklist. La rapidité compte ; la justesse compte plus.

Premier : définir la portée (un dataset ou une sous-arborescence ?)

  • Identifiez le(s) dataset(s) supportant le chemin : vérifiez le mapping des mountpoints et zfs list.
  • Déterminez si des siblings sont également affectés (l’héritage frappe souvent les siblings).
  • Demandez « quel parent unifierait ces datasets ? » C’est là que le changement a probablement eu lieu.

Deuxième : vérifier les sources de propriété pour les principaux suspects

  • Problèmes de montage : mountpoint, canmount, readonly, overlay (si applicable), et collisions.
  • Régression de performance : recordsize, compression, atime, sync, logbias, primarycache, secondarycache.
  • Surprises d’espace : quota, refquota, reservation, refreservation.

Troisième : confirmer que le changement est une politique, pas du hardware

  • Santé du pool : zpool status doit être propre ; sinon, vous déboguez le mauvais problème.
  • Diff de l’arbre de datasets : comparez les sources de propriété entre datasets « bons » et « mauvais ».
  • Cherchez l’activité admin récente : historique shell, notes de gestion de changement, commits d’automation.

Quatrième : corriger avec le plus petit rayon d’impact

  • Si un seul enfant a besoin d’un comportement différent, définissez une surcharge locale sur l’enfant.
  • Si la politique est erronée pour beaucoup d’enfants, changez le parent — mais seulement après audit de la sous-arborescence.
  • Si vous ne pouvez pas décider en sécurité, restaurez les valeurs précédentes (rollback du changement de propriété) et revenez avec des données.

Trois mini-récits d’entreprise issus des mines de l’héritage

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

Une entreprise SaaS de taille moyenne avait une disposition ZFS ordonnée : tank/apps pour l’état des applications, avec des enfants
pour Postgres, Redis, et un blob store. Un nouvel SRE a reçu pour mission de « rendre les backups plus faciles à trouver » sur un hôte
utilisé par plusieurs équipes. Il a remarqué que le dataset parent était monté sur /srv/apps et voulait tout mettre
sous /srv pour la « cohérence ».

Il a exécuté une seule commande : définir mountpoint=/srv sur tank/apps. Le système n’a pas explosé
immédiatement parce que la plupart des montages étaient déjà actifs. Le changement est resté là comme une peau de banane.
Cette nuit-là, un reboot de routine pour patch kernel a déclenché des remounts. Certains datasets n’ont pas réussi à monter à cause
de cibles « busy », d’autres ont collisionné avec des répertoires existants, et des unités systemd ont couru en supposant que les chemins existaient.

Le ticket d’incident lisait comme une anthologie d’horreur : « Postgres ne démarre pas », « Redis : données manquantes », « l’application
ne trouve pas les uploads ». Le pool était sain. Le IO disque semblait OK. Le problème était que les chemins n’étaient pas là où
les services les attendaient.

La correction fut simple mais humiliante : revenir sur le mountpoint parent, définir un mountpoint local seulement là où nécessaire,
et ajouter une vérification pré-reboot qui compare les mountpoints actuels contre un inventaire approuvé. La vraie leçon n’était pas « ne touchez pas mountpoint ».
C’était « ne supposez pas que les propriétés des datasets se comportent comme des réglages par répertoire ».

La ligne la plus percutante du postmortem : personne n’a revu le rayon d’impact. La commande était correcte. L’hypothèse ne l’était pas.

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

Une autre organisation stockait des artefacts de build et des couches de conteneur sur ZFS. Ils luttaient contre la capacité : le pool grimpait
vers un taux inconfortable. Quelqu’un a proposé d’activer la compression au sommet de l’arbre :
zfs set compression=lz4 tank. C’est une recommandation courante. Ce n’est pas gratuit.

Le déploiement a été fait via de l’automation. En quelques heures, la supervision montrait une augmentation CPU sur un sous-ensemble de nœuds.
La latence d’une API chargée a aussi augmenté. L’équipe a supposé que le changement d’API n’était pas lié — jusqu’à ce qu’ils corrèlent les nœuds
avec la disposition des datasets. L’API utilisait aussi un dataset sous tank, mais personne ne pensait qu’elle « partageait »
quoi que ce soit avec les artefacts de build.

La compression n’était pas le méchant ; c’était l’interaction. Cette API traitait beaucoup de petits payloads déjà compressés.
Les ratios de compression flottaient autour de 1.00x. Pendant ce temps, les cycles CPU supplémentaires concurrençaient les threads applicatifs lors des pics.
Sur le papier, c’était « un overhead mineur ». En réalité, c’était un overhead au mauvais endroit au mauvais moment.

Ils ont récupéré en mettant compression=off localement pour le dataset de l’API et en laissant la compression ailleurs.
Le retour de flamme n’était pas que la compression soit mauvaise. Le retour de flamme venait de traiter l’arbre de datasets comme un classeur statique
au lieu d’un domaine de performance partagé.

L’amélioration à long terme fut une politique : héritage de haut niveau seulement pour les propriétés sûres et largement bénéfiques,
et des « datasets d’exception » explicites documentés et testés. La compression est restée activée pour la majorité. L’API a eu ses propres règles.

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

Une société liée à la finance utilisait ZFS pour des services multi‑tenant internes. Leur lead stockage insistait sur un rituel ennuyeux :
chaque trimestre, exécuter un audit récursif des propriétés et committer la sortie dans le repo d’infra en tant qu’artéfact.
Pas en diagramme. La sortie brute de zfs get -r. Tout le monde se plaignait que c’était bureaucratique.

Puis un nouveau déploiement de plateforme a introduit un changement subtil : un parent de dataset a eu atime=on localement à cause
d’un « restore defaults » mal avisé. Personne ne l’a remarqué en dev car les workloads de test étaient petits.
En production, les services orientés lecture ont commencé à générer des écritures supplémentaires. La latence a augmenté. L’amplification d’écriture NVMe
a grimpé. Rien de catastrophique, juste une lente et coûteuse usure.

L’ingénieur on-call a fait les vérifications habituelles. Pool sain. Pas d’erreurs. Les patterns IO semblaient « chargés » mais pas en échec.
Puis il a récupéré le dernier snapshot de propriétés trimestriel et l’a diffé avec l’état courant. La différence a sauté aux yeux :
la source d’atime avait changé sur un parent. Cela a immédiatement réduit la portée à une sous-arborescence et une seule propriété.

Correctif : restaurer atime=off sur le parent approprié, confirmer que les enfants l’héritent, et ajouter un garde-fou dans l’automation
pour éviter une réactivation accidentelle. Downtime : nul. Temps de debug : court. La pratique ennuyeuse s’est payée en un incident.

La morale n’est pas sexy : les inventaires de propriétés ne sont pas de la paperasse ; ce sont des machines à remonter le temps. Quand vous déboguez,
une baseline connue et saine est une arme.

Erreurs courantes : symptômes → cause racine → correctif

1) « Le dataset enfant a changé et personne ne l’a touché »

Symptômes : changements de comportement (performance, chemins de montage, visibilité des snapshots), mais le dataset enfant ne montre aucune action admin récente.

Cause racine : propriété héritée d’un parent qui a été modifié.

Correctif : exécutez zfs get -o name,property,value,source sur l’enfant et remontez la source. Soit rétablissez le changement du parent soit définissez une surcharge locale sur l’enfant.

2) « Après reboot, certains datasets manquent »

Symptômes : services échouent au démarrage, mountpoints vides, datasets ZFS montrent mounted=no.

Cause racine : changement hérité de mountpoint, collisions de mountpoint, ou canmount=off défini sur un parent.

Correctif : vérifiez zfs get -r canmount,mountpoint,mounted. Supprimez les collisions ; définissez des mountpoints locaux sur les enfants qui nécessitent des chemins uniques ; assurez-vous que les parents utilisés comme conteneurs ont explicitement canmount=off seulement si c’est voulu.

3) « Régression de performance après un tuning “inoffensif” »

Symptômes : latence plus élevée, IO en hausse, CPU en hausse ; pas d’erreurs de pool.

Cause racine : recordsize, atime, ou compression hérités appliqués à des workloads qui les détestent.

Correctif : auditez les sources de propriété sur la sous-arborescence. Appliquez des surcharges locales par workload. Évitez de tuner aux parents de haut niveau sauf si vous pouvez le justifier pour chaque enfant.

4) « Plus de place sur le device, mais le pool a de l’espace »

Symptômes : l’application voit ENOSPC ; zpool list affiche de l’espace libre.

Cause racine : limites de quota/refquota/reservation/refreservation ; parfois attentes héritées plutôt que propriétés héritées.

Correctif : inspectez zfs get quota,refquota,reservation,refreservation,available. Ajustez les limites, et alertez sur « available » au niveau dataset, pas seulement l’espace libre du pool.

5) « Les snapshots soudainement visibles (ou disparus) »

Symptômes : les applis traversent .zfs, les outils de backup se comportent bizarrement, la conformité pose des questions.

Cause racine : snapdir hérité modifié sur un parent.

Correctif : définissez snapdir=hidden ou snapdir=visible délibérément à la bonne frontière de politique ; vérifiez récursivement avec zfs get -r snapdir.

6) « Nous avons activé le chiffrement sur le parent ; pourquoi les enfants existants ne sont-ils pas chiffrés ? »

Symptômes : les nouveaux datasets sont chiffrés, les anciens ne le sont pas ; l’audit le signale.

Cause racine : les propriétés de chiffrement influencent la création et l’héritage des datasets, mais on ne peut pas chiffrer rétroactivement des datasets existants in-place.

Correctif : planifiez une migration send/receive vers un nouvel arbre de datasets chiffré ; traitez « l’héritage » comme un modèle pour les nouveaux datasets, pas comme une machine à remonter le temps pour les données anciennes.

Listes de contrôle / plan pas à pas

Plan A : Modifier en sécurité une propriété sur un dataset parent

  1. Identifier la sous-arborescence. Lister enfants et points de montage.

    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

    Décision : si vous ne reconnaissez pas chaque dataset, arrêtez. Les enfants inconnus sont là où se cachent les surprises.

  2. Auditer récursivement les valeurs et sources de propriété actuelles.

    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

    Décision : si des enfants importants ont des surcharges locales, confirmez qu’elles restent correctes après votre changement parent.

  3. Modéliser l’impact. Lister les datasets qui héritent actuellement cette propriété du parent.

    cr0x@server:~$ zfs get -r -H -o name,source atime tank/apps | awk '$2 ~ /tank\/apps/ {print $1}'
    tank/apps/postgres
    tank/apps/redis

    Décision : si la liste impactée contient des « workloads spéciaux », envisagez des surcharges locales plutôt que de changer le parent.

  4. Effectuer le changement dans une fenêtre contrôlée (ou au moins dans des conditions contrôlées).

    cr0x@server:~$ zfs set atime=off tank/apps

    Décision : si c’est lié au montage, planifiez une fenêtre ; les changements de mountpoint sont bruyants opérationnellement.

  5. Vérifier récursivement et surveiller les cas atypiques.

    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

    Décision : si un dataset reste atime=on, il a une surcharge locale ; décidez si c’est correct.

Plan B : Rendre un enfant immunisé aux futurs changements du parent

  1. Identifier quelles propriétés doivent être épinglées localement. Typiques : recordsize, logbias, primarycache, sync pour les bases (prudence), ou mountpoint.
  2. Définir des surcharges locales explicitement et documenter pourquoi.

    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

    Décision : les surcharges locales sont un contrat. Si vous ne pouvez pas les justifier, vous compliquez juste l’avenir.

Plan C : Prévenir les surprises avec une baseline de propriétés

  1. Capturer un snapshot récursif des propriétés pour les datasets clés.

    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

    Décision : stockez-le de manière durable (artefact repo, store config management). S’il vit seulement sur l’hôte, il mourra avec l’hôte.

  2. Diff avant/après changements.

    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

    Décision : si le diff est plus large que prévu, arrêtez et réévaluez. Les gros diffs sont là où surviennent les outages.

FAQ

1) Comment savoir si une propriété d’un dataset est héritée ?

Utilisez zfs get et regardez la colonne source. Si elle indique « inherited from … », c’est la réponse.

2) Si je définis une propriété sur un parent, cela affecte-t-il toujours tous les enfants ?

Cela affecte les enfants qui n’ont pas de surcharge locale pour cette propriété, et seulement pour les propriétés héritables.
Les enfants avec des valeurs locales sont isolés.

3) Quelle est la différence entre zfs inherit et définir une propriété sur sa valeur par défaut ?

zfs inherit supprime le réglage local, faisant hériter le dataset de son parent (ou revenir au défaut si aucun parent ne le définit).
Définir une valeur explicitement la rend locale, même si elle correspond au défaut.

4) Pourquoi changer mountpoint a cassé des choses alors que ce n’est « qu’un chemin » ?

Parce que les chemins sont des contrats avec des services, des configs et l’ordre de démarrage. L’héritage peut déplacer la disposition de montage d’une sous-arborescence entière.
Ce n’est pas juste un renommage ; c’est un changement de topologie.

5) Est‑ce sûr d’activer compression=lz4 au dataset racine du pool ?

Souvent oui, mais la « sécurité » dépend du workload et de la marge CPU. Auditez les ratios de compression et l’utilisation CPU.
Si un dataset stocke principalement des données déjà compressées, définissez une exception locale.

6) Dois‑je tuner recordsize sur un parent de haut niveau ?

Généralement non. Utilisez recordsize au niveau parent uniquement quand les enfants sont homogènes.
Pour des workloads mixtes, épinglez recordsize localement par dataset représentant un workload.

7) Puis-je chiffrer rétroactivement des datasets existants en définissant les propriétés de chiffrement sur le parent ?

Non. Vous devrez typiquement faire une migration send/receive vers un dataset chiffré.
Les propriétés de chiffrement du parent guident la création/héritage des nouveaux datasets, pas la conversion in-place des anciens.

8) Comment trouver sur quel dataset se trouve un répertoire quand les montages sont confus ?

Vérifiez la table de montage ZFS via zfs mount et comparez les mountpoints, ou utilisez les outils de montage de l’OS.
Puis interrogez les propriétés sur ce dataset spécifiquement.

9) Pourquoi deux datasets affichent la même valeur de propriété mais se comportent différemment ensuite ?

Même valeur, source différente. Une valeur issue du default ne changera pas sauf si vous la modifiez.
Une valeur héritée d’un parent peut changer dès que le parent change.

10) Comment conserver l’héritage tout en évitant les surprises ?

Placez des frontières de politique là où se situent les frontières organisationnelles : un parent par classe de workload.
Puis exécutez des audits récursifs et conservez une baseline pour pouvoir prouver ce qui a changé.

Conclusion : prochaines étapes réalisables

L’héritage des propriétés ZFS n’est pas un détail académique. C’est un plan de contrôle en production. Il peut vous sauver de l’explosion
de configurations, et il peut absolument vous mettre à genoux quand un « petit changement » s’avère être un changement de politique sur toute une sous-arborescence.

Faites ces actions ensuite :

  1. Choisissez trois arbres de datasets critiques (bases, backups, et ce qui remplit les disques) et exécutez un audit récursif des propriétés. Sauvegardez-le.
  2. Marquez explicitement les datasets de workload et définissez des surcharges locales là où le workload diffère vraiment (surtout recordsize, atime, propriétés de montage).
  3. Adoptez l’habitude du rayon d’impact : avant de changer un parent, listez tous les enfants qui héritent cette propriété et prenez une décision consciente.
  4. Opérationnalisez la détection de dérive : differez votre baseline sauvegardée contre les propriétés courantes après chaque fenêtre de changement.

Si vous ne retenez qu’une chose : ne regardez jamais une valeur de propriété ZFS sans regarder sa source.
C’est là que la surprise se cache.

← Précédent
ZFS ashift : le désalignement silencieux qui coupe les performances en deux
Suivant →
MySQL vs PostgreSQL : sauvegardes et restaurations — qui vous remet en ligne le plus vite

Laisser un commentaire