Tout va bien jusqu’à ce que ça ne va plus : un job de sauvegarde démarre, une base de données marque un temps d’arrêt, et soudain les hôtes VM paraissent « lents » d’une manière que personne ne sait quantifier.
Vous regardez des graphiques, débattez de la « latence du stockage », et quelqu’un propose de redémarrer « pour vider le bazar ».
La solution est rarement héroïque. C’est une structure ennuyeuse : un jeu de données ZFS par charge de travail, avec des propriétés explicites, des règles de snapshot explicites et des limites explicites.
C’est ainsi que l’on évite que le pool de stockage ne devienne une cuisine partagée où tout le monde prépare du curry en même temps et personne ne trouve une poêle propre.
L’astuce : les jeux de données sont des frontières de gestion
ZFS n’est pas seulement un système de fichiers. C’est un moteur de politique soudé à un allocateur, avec la vérification d’intégrité et le copy-on-write comme liant.
Ce moteur de politique est principalement contrôlé par les propriétés des jeux de données. Et ces propriétés ne servent que si vous utilisez les jeux de données comme frontières.
La stratégie « un jeu de données par charge de travail » signifie que vous arrêtez de traiter votre pool comme un grand arbre de répertoires où tout hérite de ce que le dernier administrateur avait envie de poser.
À la place, vous créez des jeux de données qui correspondent à des charges réelles : une base de données, un magasin d’images VM, un cache d’objets, un dépôt d’artefacts de build, des répertoires home,
des couches de conteneurs, une zone de réception de sauvegarde. Chacun reçoit des réglages délibérés.
Le but n’est pas « plus de datasets parce que ZFS le permet ». Le but est le contrôle opérationnel :
- Isolation des performances : recordsize, logbias, atime, comportement sync, special_small_blocks peuvent être ajustés par charge.
- Isolation de sécurité : cadence de snapshots, rétention, holds et réplication peuvent être définis par dataset.
- Discipline de capacité : quotas/reservations empêchent une charge d’engloutir votre pool en la qualifiant de « temporaire ».
- Contrôle du rayon d’impact : vous pouvez revenir en arrière ou envoyer/recevoir une charge sans toucher aux voisines.
- Diagnostique : zfs get, zfs list et les statistiques par dataset deviennent significatifs au lieu d’être un magma indistinct.
Si vous ne retenez qu’une chose : votre pool ZFS est le budget matériel ; les jeux de données sont vos contrats.
Sans contrats, vous recevez des factures surprises.
Une petite plaisanterie pour la route : Si votre stockage est « un grand dataset », ce n’est pas de l’architecture — c’est une émotion.
Faits intéressants et contexte historique (parce que les cicatrices ont une histoire)
- ZFS est né chez Sun Microsystems au début/milieu des années 2000, conçu pour remplacer les gestionnaires de volumes et systèmes de fichiers traditionnels par un système intégré.
- Le modèle copy-on-write était une réponse directe à la corruption silencieuse et à la classe de problèmes « write hole » qui hantaient les piles RAID + filesystem plus anciennes.
- Les snapshots ZFS ont été conçus pour être bon marché car ce ne sont que des références à des blocs existants — jusqu’à ce que vous les conserviez indéfiniment et vous demandiez pourquoi les suppressions ne libèrent pas d’espace.
- L’héritage des propriétés de dataset est intentionnel : il permet des arbres de politique, mais il permet aussi des héritages accidentels — comme une base de données de production héritant d’un planning de snapshots « test ».
- La compression est devenue une pratique courante dans ZFS parce qu’elle améliore souvent les performances en réduisant les E/S, surtout sur disques rotatifs et matrices saturées.
- recordsize existe parce que « une taille de bloc pour tous » échoue : les fichiers séquentiels volumineux et les I/O aléatoires petits veulent des compromis différents.
- Les ZVOLs sont des périphériques bloc gérés par ZFS ; ils se comportent différemment des datasets et apportent leurs propres réglages comme volblocksize.
- L’ARC n’est pas « juste un cache » ; c’est un consommateur de mémoire avec un comportement d’éviction qui peut faire ou défaire les performances, surtout sous charges mixtes.
- OpenZFS est la continuation multiplateforme après l’ère Sun, et les fonctionnalités modernes (comme les special vdevs et dRAID) sont le fruit d’années de retours opérationnels.
Tout cela mène à la même vérité opérationnelle : ZFS veut que vous exprimiez une intention. Les jeux de données sont le moyen de le faire.
Carte des charges : ce qui mérite son propre jeu de données
« Par charge de travail » n’est pas « par répertoire ». C’est « par comportement ». Vous séparez quand une charge a des besoins différents en latence, débit, sécurité ou rétention.
Vous gardez ensemble ce qui partage un cycle de vie et une politique.
Bonnes frontières de dataset (pragmatique, pas académique)
- Bases de données : le répertoire de données Postgres/MySQL, WAL/binlog et les sauvegardes méritent souvent des jeux de données séparés.
- Stockage VM : un jeu de données pour les images VM, éventuellement par cluster ou par locataire. Si vous utilisez des ZVOLs, traitez-les aussi comme des charges.
- Conteneurs : couches d’images vs volumes inscriptibles vs logs. Ces éléments ont des schémas d’écriture très différents.
- Caches d’artifacts/build : forte rotation, faible valeur, beaucoup de suppressions — parfait pour des règles de snapshot/reten tion distinctes.
- Répertoires personnels : quotas, rétention différente et restaurations suite à des suppressions accidentelles.
- Zones de réception de sauvegarde : réceptions, longue rétention et « ne pas snapshotter accidentellement toutes les 5 minutes ».
- Logs : se compressent souvent bien, mais n’ont pas toujours besoin de snapshots ; ils peuvent aussi être énormes et spikés.
Où les gens sur-segmentent
Ne créez pas un dataset pour chaque sous-répertoire d’une application parce que vous êtes enthousiasmé par les réglages.
Si votre planning de snapshots est identique, la rétention identique et les caractéristiques de performance identiques, gardez-les ensemble.
Gérer 400 datasets sans convention de nommage, c’est se construire un musée d’intentions oubliées.
Nommage : faites ennuyeux et recherchable
Utilisez une hiérarchie prévisible. Exemple :
tank/prod/db/postgres, tank/prod/db/postgres-wal, tank/prod/vm, tank/prod/containers,
tank/shared/homes, tank/backup/recv.
Mettez l’environnement et la fonction en début de nom. Vous ferez des grep à 02:00. Ne faites pas de la poésie pour le futur vous.
Propriétés importantes (et ce qu’elles font vraiment)
recordsize : le levier de performance silencieux
recordsize contrôle la taille maximale de bloc pour les fichiers d’un dataset. Plus grand, c’est mieux pour les lectures/écritures séquentielles volumineuses (médias, sauvegardes).
Plus petit peut réduire l’amplification read-modify-write pour les I/O aléatoires (bases de données).
Pour beaucoup de bases sur datasets (pas ZVOLs), recordsize=16K ou recordsize=8K est un point de départ courant.
Pour les images VM stockées comme fichiers, recordsize=128K marche souvent bien. Pour les flux de sauvegarde, recordsize=1M peut être approprié.
Mais ne suivez pas aveuglément : mesurez.
volblocksize : pour les ZVOLs, pas pour les fichiers
Les ZVOLs utilisent volblocksize. Vous le définissez à la création et le changer ensuite est non trivial.
Adaptez-le à la taille de bloc de la machine invitée/système de fichiers et au type d’I/O attendu : p.ex. 8K ou 16K pour I/O aléatoire type DB ; plus grand pour séquentiel.
compression : une fonctionnalité de performance déguisée en efficacité de stockage
Utilisez compression=zstd pour la plupart des datasets sauf raison claire contraire.
C’est généralement plus rapide que vos disques et réduit les écritures. Cela réduit aussi les drames du type « pourquoi mon pool est plein ».
atime : écritures de métadonnées dont vous n’avez probablement pas besoin
atime=off est un réglage standard pour la plupart des charges serveur. Si vous avez vraiment besoin des mises à jour d’heure d’accès, faites-en un dataset explicite et petit.
sync et logbias : où l’honnêteté rencontre la latence
sync=standard est la valeur par défaut et la plus sûre. sync=disabled peut rendre les benchmarks beaux et la production catastrophique.
logbias=latency vs throughput influence l’intention pour les écritures synchrones (et si le SLOG aide).
Si vous changez le comportement sync, traitez-le comme un changement de RPO : notez-le, obtenez une approbation et assumez que la réalité vous auditer a.
primarycache / secondarycache : choisissez ce que vous mettez en cache
Pour les datasets qui polluent l’ARC (comme de grandes lectures séquentielles de sauvegarde), envisagez primarycache=metadata.
Il ne s’agit pas d’économiser de la RAM ; il s’agit de garder l’ARC utile pour les charges sensibles à la latence.
special_small_blocks et special vdevs : métadonnées rapides, petits I/O rapides
Si vous avez un special vdev (typiquement des SSD) pour métadonnées/petits blocs, special_small_blocks peut améliorer considérablement les charges de petits fichiers.
Cela peut aussi provoquer des regrets spectaculaires si votre special vdev est sous-dimensionné. C’est une de ces fonctionnalités où il faut planifier la capacité comme si c’était de la production, parce que ça l’est.
quotas, reservations, refreservation : la capacité comme politique
quota limite la croissance. reservation garantit de l’espace. refreservation peut réserver de l’espace égal aux données référencées d’un dataset,
souvent utilisé pour les zvols. Ce sont vos garde-fous contre la taxe du voisin bruyant sur le stockage.
snapshots : votre levier de rollback, votre unité de réplication, votre piège spatio-temporel
Les snapshots sont bon marché jusqu’à ce qu’ils ne le soient plus. Un dataset à forte rotation et à longue rétention de snapshots retiendra des blocs supprimés et gonflera l’espace utilisé.
Un dataset avec des snapshots fréquents et une rotation rapide peut remplir un pool alors que tout le monde jure que rien n’a changé.
Une citation, parce qu’elle reste vraie
L’espoir n’est pas une stratégie.
— Général H. Norman Schwarzkopf
Tâches pratiques : commandes + ce que la sortie signifie + la décision à prendre
Ce ne sont pas des commandes « jouets ». Ce sont celles que vous exécutez quand vous êtes fatigué, la production est chaude, et votre pool de stockage se fait reprocher de tout.
Chaque tâche inclut : commande, sortie d’exemple, interprétation et une décision.
Task 1: Lister les datasets et voir qui utilise réellement l’espace
cr0x@server:~$ zfs list -o name,used,avail,refer,mountpoint -r tank/prod
NAME USED AVAIL REFER MOUNTPOINT
tank/prod 3.21T 5.87T 192K /tank/prod
tank/prod/db 1.44T 5.87T 192K /tank/prod/db
tank/prod/db/postgres 812G 5.87T 812G /var/lib/postgresql
tank/prod/db/postgres-wal 221G 5.87T 221G /var/lib/postgresql-wal
tank/prod/vm 1.55T 5.87T 1.55T /tank/prod/vm
tank/prod/containers 218G 5.87T 218G /var/lib/containers
Ce que cela signifie : USED inclut les snapshots/descendants ; REFER est les données vivantes de ce dataset.
Décision : Si USED est bien plus grand que REFER, les snapshots retiennent de l’espace. Inspectez la rétention des snapshots pour ce dataset.
Task 2: Trouver le bloat des snapshots par dataset
cr0x@server:~$ zfs list -t snapshot -o name,used,refer -s used -r tank/prod/db/postgres | tail -n 5
tank/prod/db/postgres@hourly-2025-12-25-00 18.4G 812G
tank/prod/db/postgres@hourly-2025-12-24-23 18.2G 812G
tank/prod/db/postgres@hourly-2025-12-24-22 17.9G 812G
tank/prod/db/postgres@hourly-2025-12-24-21 17.6G 812G
tank/prod/db/postgres@hourly-2025-12-24-20 17.4G 812G
Ce que cela signifie : USED du snapshot est ici les blocs uniques détenus par ce snapshot.
Décision : Si les snapshots sont volumineux et fréquents, raccourcissez la rétention ou snapshottez moins souvent sur les datasets à forte rotation (WAL, caches, artefacts de build).
Task 3: Vérifier l’héritage des propriétés pour repérer des valeurs par défaut accidentelles
cr0x@server:~$ zfs get -r -o name,property,value,source recordsize,compression,atime,sync,logbias tank/prod/db/postgres
NAME PROPERTY VALUE SOURCE
tank/prod/db/postgres recordsize 128K inherited from tank/prod
tank/prod/db/postgres compression zstd inherited from tank/prod
tank/prod/db/postgres atime on default
tank/prod/db/postgres sync standard default
tank/prod/db/postgres logbias latency local
Ce que cela signifie : Cette base de données hérite de recordsize=128K et a atime=on.
Décision : Définissez recordsize explicitement pour les datasets DB et désactivez atime à moins d’en avoir besoin.
Task 4: Corriger recordsize et atime pour un dataset de base de données
cr0x@server:~$ sudo zfs set recordsize=16K atime=off tank/prod/db/postgres
Ce que cela signifie : Les écritures futures utiliseront des records de 16K ; les blocs existants ne seront pas réécrits tant que les données ne changent pas.
Décision : Appliquez pendant une fenêtre de maintenance si vous prévoyez de réécrire de larges portions ; sinon acceptez une amélioration progressive.
Task 5: Vérifier compressratio pour voir si la compression aide
cr0x@server:~$ zfs get -o name,property,value compressratio -r tank/prod | head
NAME PROPERTY VALUE
tank/prod compressratio 1.42x
tank/prod/db compressratio 1.12x
tank/prod/db/postgres compressratio 1.06x
tank/prod/vm compressratio 1.68x
Ce que cela signifie : Les images VM se compressent bien ; la base de données se compresse mal (commun pour pages déjà compressées ou données chiffrées).
Décision : Gardez la compression activée sauf si le CPU est contraint. Pour la DB, la compression n’économisera pas beaucoup d’espace mais peut réduire légèrement les écritures.
Task 6: Mettre un plafond ferme sur une charge « temporaire »
cr0x@server:~$ sudo zfs set quota=500G tank/prod/containers
cr0x@server:~$ zfs get -o name,property,value quota tank/prod/containers
NAME PROPERTY VALUE
tank/prod/containers quota 500G
Ce que cela signifie : Le dataset containers ne peut pas dépasser 500G.
Décision : Utilisez des quotas pour les caches, artefacts de build et files de logs. Si on atteint le quota, vous obtenez une défaillance contrôlée plutôt qu’une panne du pool entier.
Task 7: Réserver de l’espace pour la base de données afin qu’elle puisse respirer
cr0x@server:~$ sudo zfs set reservation=1T tank/prod/db
cr0x@server:~$ zfs get -o name,property,value reservation tank/prod/db
NAME PROPERTY VALUE
tank/prod/db reservation 1T
Ce que cela signifie : Vous garantissez de l’espace pour les datasets DB sous tank/prod/db.
Décision : Utilisez des reservations quand le coût métier d’échecs d’écriture DB est supérieur au coût de « gaspiller » de la capacité réservée.
Task 8: Identifier si le pool souffre de pression de fragmentation
cr0x@server:~$ zpool list -o name,size,alloc,free,frag,capacity,health tank
NAME SIZE ALLOC FREE FRAG CAPACITY HEALTH
tank 9.08T 7.24T 1.84T 63% 79% ONLINE
Ce que cela signifie : 79% plein et 63% de fragmentation est un signe d’alerte, pas un crime. ZFS peut fonctionner ainsi, mais la latence augmente souvent.
Décision : Planifiez la capacité : gardez les pools sous ~80% pour des charges aléatoires mixtes. Déplacez les datasets à forte rotation ou ajoutez des vdevs avant la phase de « latence mystère ».
Task 9: Surveiller la latence d’E/S au niveau du pool
cr0x@server:~$ zpool iostat -v tank 2 3
capacity operations bandwidth
pool alloc free read write read write
---------- ----- ----- ----- ----- ----- -----
tank 7.24T 1.84T 640 1200 78.2M 94.6M
raidz2-0 7.24T 1.84T 640 1200 78.2M 94.6M
sda - - 80 150 9.70M 11.8M
sdb - - 79 151 9.66M 11.7M
sdc - - 81 148 9.74M 11.6M
sdd - - 80 149 9.69M 11.7M
Ce que cela signifie : Vous voyez la distribution de charge. Cette vue seule ne montre pas la latence, mais elle indique si vous saturez des périphériques.
Décision : Si les écritures sont constamment élevées et la charge sync-intensive, évaluez SLOG et les réglages sync par dataset (pas globalement).
Task 10: Inspecter la comptabilité logique d’espace par dataset (et repérer les pièges de refreservation)
cr0x@server:~$ zfs list -o name,used,refer,logicalused,logicalrefer,compressratio -r tank/prod/vm | head
NAME USED REFER LUSED LREFER RATIO
tank/prod/vm 1.55T 1.55T 2.31T 2.31T 1.68x
Ce que cela signifie : L’utilisation logique est plus grande que l’utilisation physique grâce à la compression (et possiblement aux images sparse).
Décision : Si logical used est énorme, assurez-vous que votre monitoring alerte sur la capacité physique du pool, pas seulement la capacité déclarée par les invités.
Task 11: Voir ce qui malmène l’ARC et si vous devriez limiter le cache pour un dataset
cr0x@server:~$ arcstat 1 3
time read miss miss% dmis dm% pmis pm% mmis mm% size c
12:00:01 712 144 20 112 16 32 4 0 0 46.3G 48.0G
12:00:02 690 171 24 139 20 32 5 0 0 46.1G 48.0G
12:00:03 705 162 23 131 19 31 4 0 0 46.0G 48.0G
Ce que cela signifie : Un taux de miss de 20–24% sous charge peut être acceptable ou catastrophique selon les objectifs de latence. Cela suggère que l’ensemble de travail peut ne pas tenir.
Décision : Si de gros jobs séquentiels thrashent l’ARC, définissez primarycache=metadata sur ces datasets (p.ex. receive de sauvegardes ou archives médias).
Task 12: Changer la politique de cache pour un dataset de sauvegarde afin de protéger les charges sensibles à la latence
cr0x@server:~$ sudo zfs set primarycache=metadata tank/backup/recv
cr0x@server:~$ zfs get -o name,property,value,source primarycache tank/backup/recv
NAME PROPERTY VALUE SOURCE
tank/backup/recv primarycache metadata local
Ce que cela signifie : ZFS mettra en cache les métadonnées, mais pas les données de fichier, pour ce dataset dans l’ARC.
Décision : Utilisez ceci pour les datasets « en streaming » qui ne bénéficient pas du cache et nuisent activement aux autres.
Task 13: Créer un dataset avec des défauts adaptés à la charge (le bon type d’ennui)
cr0x@server:~$ sudo zfs create -o compression=zstd -o atime=off -o recordsize=16K tank/prod/db/mysql
cr0x@server:~$ zfs get -o name,property,value,source compression,atime,recordsize tank/prod/db/mysql
NAME PROPERTY VALUE SOURCE
tank/prod/db/mysql compression zstd local
tank/prod/db/mysql atime off local
tank/prod/db/mysql recordsize 16K local
Ce que cela signifie : Vous avez rendu l’intention explicite à la création, évitant les surprises d’héritage accidentelles.
Décision : Standardisez la création de datasets via un runbook ou de l’automatisation. Les humains sont excellents ; la cohérence l’est davantage.
Task 14: Vérifier la dérive de la politique de snapshot (snapshottez-vous les mauvaises choses ?)
cr0x@server:~$ zfs get -r -o name,property,value,source com.sun:auto-snapshot tank/prod | head -n 8
NAME PROPERTY VALUE SOURCE
tank/prod com.sun:auto-snapshot true inherited from tank
tank/prod/db com.sun:auto-snapshot true inherited from tank
tank/prod/db/postgres com.sun:auto-snapshot true inherited from tank
tank/prod/db/postgres-wal com.sun:auto-snapshot true inherited from tank
tank/prod/vm com.sun:auto-snapshot true inherited from tank
tank/prod/containers com.sun:auto-snapshot true inherited from tank
tank/prod/logs com.sun:auto-snapshot true inherited from tank
Ce que cela signifie : Tout est snapshoté, y compris WAL et logs, qui peuvent être à forte rotation et de faible valeur.
Décision : Désactivez les auto-snapshots sur les datasets à forte rotation (WAL/logs/caches) ou donnez-leur une classe de rétention différente.
Task 15: Désactiver les snapshots sur un dataset à forte rotation (exemple : WAL)
cr0x@server:~$ sudo zfs set com.sun:auto-snapshot=false tank/prod/db/postgres-wal
cr0x@server:~$ zfs get -o name,property,value,source com.sun:auto-snapshot tank/prod/db/postgres-wal
NAME PROPERTY VALUE SOURCE
tank/prod/db/postgres-wal com.sun:auto-snapshot false local
Ce que cela signifie : L’automatisation des snapshots qui respecte cette propriété ignorera WAL.
Décision : Protégez WAL via des sauvegardes natives à la base de données/archivage, pas via des snapshots système toutes les 15 minutes qui retiennent la rotation pour toujours.
Task 16: Confirmer ashift et la disposition des vdevs (parce qu’on ne règle pas une mauvaise géométrie)
cr0x@server:~$ zdb -C tank | egrep "ashift|vdev_tree|type:|path:"
ashift: 12
type: 'raidz'
type: 'disk'
path: '/dev/disk/by-id/ata-SAMSUNG_SSD_...'
Ce que cela signifie : ashift: 12 implique un alignement sur 4K, généralement correct pour disques/SSDs modernes.
Décision : Si ashift est incorrect (trop petit), les performances et l’amplification d’écriture peuvent être durablement affectées. Le corriger signifie généralement reconstruire le pool.
Deuxième petite plaisanterie, vous l’avez méritée : la belle chose avec les datasets « temporaires », c’est qu’ils durent pour toujours — comme des tatouages, mais avec de pires politiques de rétention.
Carnet de diagnostic rapide : trouver le goulot sans une semaine de réunions
Voici l’ordre de triage qui marche dans la vraie vie : commencez large, puis zoomez. Ne commencez pas par changer recordsize.
Ne commencez pas par accuser ZFS. Et surtout ne commencez pas par « désactiver sync » parce que quelqu’un a lu un post forum en 2014.
Premier : le pool manque-t-il d’air ?
- Vérifiez la capacité et la fragmentation du pool :
zpool list - Vérifiez le bloat des snapshots :
zfs list -t snapshot - Vérifiez si un dataset unique explose :
zfs list -r
Interprétation : Les pools proches du plein tendent à devenir des machines à latence.
Forte rotation + beaucoup de snapshots + haut niveau de remplissage est une recette classique pour les incidents « ça devient lent ».
Second : est-ce IOPS/latence, débit ou CPU ?
- Regardez la saturation et la distribution des périphériques :
zpool iostat -v 1 - Regardez le CPU système steal/iowait :
mpstat -P ALL 1,iostat -x 1 - Vérifiez le coût CPU de la compression si vous utilisez des algos lourds :
zfs get compressionet métriques CPU système
Interprétation : Un goulot de latence d’écriture aléatoire donne l’impression que « tout est lent ». Un goulot de débit ressemble à « les gros jobs prennent une éternité ».
Les goulots CPU donnent l’impression que le stockage est lent parce que la pile de stockage attend le CPU pour checksum, compresser et gérer les métadonnées.
Troisième : quel dataset est le voisin bruyant ?
- Identifiez les plus gros lecteurs/écrivains par processus :
iotop -ooupidstat -d 1 - Corrélez avec les points de montage/datasets :
zfs list -o name,mountpoint - Confirmez les propriétés du dataset :
zfs get -o name,property,value,source -r ...
Interprétation : Si un dataset effectue des écritures séquentielles avec snapshots et cache activé, il peut évincer l’ARC utile et provoquer des misses pour tout le monde.
Si un dataset est sync-intensive et manque d’intentions/configuration de log appropriée, il peut dominer la latence.
Quatrième : vous battez-vous contre vos propres politiques ?
- Portée de l’automatisation des snapshots : vérifiez
com.sun:auto-snapshotou vos flags d’outillage local - Réplication en retard et réceptions :
zfs list -t snapshotet vérifiez les vieux snapshots « coincés » gardés pour send/receive - Quotas/reservations :
zfs get quota,reservation,refreservation
Interprétation : Beaucoup de tickets « ZFS mange de l’espace » sont en réalité « les snapshots font exactement ce qu’on leur a demandé, et on l’a mal demandé ».
Trois mini-récits d’entreprise depuis les tranchées du stockage
1) L’incident causé par une mauvaise hypothèse : « les snapshots sont gratuits »
Une entreprise de taille moyenne utilisait un pool ZFS pour un parc mixte : VMs, conteneurs et quelques bases de données. Quelqu’un a activé une politique d’auto-snapshot
à la racine du pool. Snapshots horaires pour tout. Rétention quotidienne pendant un mois. C’était présenté comme « un filet de sécurité ».
L’hypothèse erronée n’était pas malveillante ; elle était optimiste. Les snapshots sont bon marché à créer, donc l’équipe a supposé qu’ils étaient bon marché à conserver.
Pendant ce temps, le dataset des conteneurs avait une rotation agressive : pulls d’images, suppressions de couches, jobs CI, rotation des logs, l’entropie habituelle.
Les suppressions ne libéraient pas l’espace. Le pool s’est rempli lentement, puis soudainement.
L’incident a commencé par un symptôme base de données : les commits ont ralenti. Puis le cluster VM a commencé à subir des timeouts invités.
Les opérations ont regardé les graphiques et ont vu l’utilisation disque monter et ne jamais redescendre. Quelqu’un a tenté de supprimer de vieilles images conteneur — pas de libération immédiate d’espace.
L’astreinte s’est retrouvée dans le même trou que beaucoup d’entre nous connaissent : « pourquoi rien ne libère d’espace ? »
La correction post-incident n’a pas été un réglage exotique. Ils ont séparé les datasets par charge, désactivé les snapshots sur les caches et datasets à forte rotation,
et attribué à chaque dataset une politique de rétention correspondant à sa valeur. La base de données a eu des snapshots fréquents avec rétention courte ; le magasin VM a eu des snapshots nocturnes ;
les conteneurs n’en ont pas eu, les sauvegardes étant gérées au niveau du registre d’artefacts.
La leçon : les snapshots ne sont pas gratuits ; ce sont des coûts différés. Une frontière de dataset est l’endroit où vous décidez quel coût vous êtes prêt à payer.
2) L’optimisation qui a mal tourné : « sync=disabled pour la vitesse »
Un autre site avait un problème de performance : leur charge Postgres subissait une latence de commit élevée après une migration vers un nouveau stockage.
Un ingénieur de bonne volonté a lancé un benchmark, a vu de mauvais chiffres, et a appliqué le « classic fix » : sync=disabled sur le dataset de la DB.
Les graphiques de benchmark se sont instantanément améliorés. Les gens ont célébré. Le ticket a été clôturé avec un commentaire confiant sur le « overhead ZFS ».
Des semaines plus tard, ils ont eu un incident lié à l’alimentation. Ce n’était pas un incendie de datacenter — juste le genre de problème électrique en amont qui vous fait apprendre
combien votre maintenance UPS est fiable. Les systèmes sont revenus. Postgres est revenu aussi, mais pas proprement. La base a nécessité une récupération et a montré des signes
de corruption sur une partie des transactions récentes. L’incident est devenu un exercice inter-équipes sur les sauvegardes, les archives WAL et des conversations désagréables.
L’échec opérationnel n’était pas seulement le changement de propriété ; c’était l’absence de frontières de dataset et de documentation autour de l’intention.
Le dataset DB héritait d’autres valeurs par défaut d’un arbre à usage général, et personne ne savait quelles datasets « mentaient » sur sync.
Quand il a fallu auditer le risque, l’équipe ne savait pas quoi chercher.
La correction a été conservatrice : restaurer sync=standard, valider l’adéquation du SLOG (ou accepter la latence), et séparer le WAL dans son propre dataset
avec des propriétés délibérées. Ils ont aussi ajouté une vérification de conformité simple : zfs get -r sync sur les pools prod, signalée dans le monitoring.
La leçon : les hacks de performance qui changent la correction ne sont pas du tuning ; ce sont des changements de politique. Utilisez des frontières de dataset pour rendre ces politiques explicites,
auditable et rares.
3) La pratique ennuyeuse mais correcte qui a sauvé la mise : quotas + reservations + ownership clair des datasets
Une entreprise exécutant une plateforme interne avait un pool partagé par plusieurs équipes produit. L’équipe plateforme a appliqué une règle :
chaque équipe avait un dataset, avec un quota, et les bases de production avaient des reservations. Tout le monde a râlé, car on râle toujours quand on ajoute des garde-fous.
Un trimestre, une charge data-science a commencé à déverser des données intermédiaires dans son dataset — parfaitement raisonnable isolément, catastrophiquement grand en agrégat.
La charge a atteint son quota. Les jobs ont échoué. L’équipe a appelé la plateforme avec le message habituel « le stockage est en panne ».
Mais la production est restée saine. Les bases ont continué à committer. Les images VM ont continué à tourner. Personne d’autre ne l’a remarqué.
L’astreinte de l’équipe plateforme n’a pas eu besoin d’héroïsme. Ils avaient un domaine de défaillance clair : un dataset, un quota, un propriétaire.
Ils ont travaillé avec l’équipe pour soit augmenter le quota avec un plan soit déplacer la charge vers un pool différent conçu pour les données temporaires.
L’impact sur la capacité a été discuté avant d’être infligé.
La leçon : la fonctionnalité ZFS la plus efficace pour la sanité multi-tenant n’est pas un cache sophistiqué. C’est la politique en tant que propriétés, appliquée via les datasets,
avec des propriétaires qu’on peut contacter quand leur dataset se comporte mal.
Erreurs courantes : symptômes → cause racine → correction
1) Symptom : « Nous avons supprimé des téraoctets mais le pool est toujours plein »
Cause racine : Les snapshots retiennent les blocs supprimés ; les suppressions ne libèrent de l’espace que lorsqu’aucun snapshot ne référence les blocs.
Correction : Identifiez les utilisateurs d’espace des snapshots ; raccourcissez la rétention ; excluez les datasets à forte rotation des snapshots fréquents ; détruisez les snapshots délibérément.
2) Symptom : « La latence DB monte pendant les sauvegardes/réplication »
Cause racine : Les lectures du dataset de sauvegarde polluent l’ARC, expulsant le working set DB ; ou les écritures de sauvegarde concurrencent les IOPS.
Correction : Mettez les sauvegardes dans leur propre dataset ; définissez primarycache=metadata pour les datasets en streaming ; planifiez les jobs lourds ; envisagez un pool séparé pour les sauvegardes.
3) Symptom : « Les VMs bégayent toutes les heures pile »
Cause racine : Jobs de snapshot/réplication sur le dataset VM provoquant des rafales d’activité métadonnées, ou scrubs/resilvers coïncidant avec les pics.
Correction : Ajustez la cadence des snapshots par dataset ; étalez les plannings ; assurez le dimensionnement du special vdev si utilisé ; lancez les scrubs hors-peak et surveillez l’impact.
4) Symptom : « Tout est lent une fois que le pool atteint ~85% d’utilisation »
Cause racine : L’allocateur a moins de marge ; la fragmentation augmente ; l’amplification d’écriture augmente, surtout en RAIDZ avec écritures aléatoires.
Correction : Gardez de la réserve ; ajoutez des vdevs ; déplacez les workloads à forte rotation vers un pool séparé ; taillez les snapshots ; appliquez des quotas.
5) Symptom : « Après activation de la compression, le CPU monte et la latence empire »
Cause racine : Utilisation d’un niveau/algorithme de compression coûteux sur un système CPU-limit ? ou compression de données déjà compressées/chiffrées.
Correction : Utilisez zstd à un niveau raisonnable (par défaut plateforme) ; validez la marge CPU ; envisagez de laisser la compression activée mais évitez les niveaux extrêmes.
6) Symptom : « Les écritures applicatives échouent avec ENOSPC alors que zpool montre de l’espace libre »
Cause racine : Quota du dataset atteint, ou des reservations ailleurs consomment l’espace disponible.
Correction : Vérifiez zfs get quota,reservation ; ajustez quotas/reservations ; communiquez la propriété — c’est la politique qui fonctionne, pas ZFS qui ment.
7) Symptom : « Les petits fichiers sont douloureusement lents »
Cause racine : Métadonnées et petits blocs sur disques lents ; pas de special vdev ; recordsize n’est pas le facteur principal — IOPS l’est.
Correction : Utilisez un special vdev avec assez de capacité ; configurez special_small_blocks correctement ; assurez-vous qu’atime est off ; séparez la charge petits-fichiers dans son propre dataset.
8) Symptom : « La réplication ne peut pas supprimer de vieux snapshots »
Cause racine : Holds de snapshots ou chaînes incrémentales dépendantes qui les nécessitent.
Correction : Vérifiez les holds ; validez l’outillage de réplication ; cassez et redémarrez la réplication avec précaution ; ne détruisez pas aveuglément des snapshots requis.
Listes de contrôle / plan étape par étape
Étape par étape : migration d’« un grand dataset » vers des datasets par charge
-
Inventoriez les charges et les points de montage.
Décidez des frontières selon le comportement : DB, WAL, images VM, conteneurs, logs, sauvegardes, homes. -
Décidez de l’ensemble minimal de politiques.
Pour chaque charge : cadence/reten tion des snapshots, compression, recordsize/volblocksize, atime, quotas/reservations, politique de cache. -
Créez des datasets avec des propriétés explicites.
Ne comptez pas sur l’héritage pour des réglages critiques ; l’héritage est bien pour des valeurs par défaut, pas pour des contrats. -
Déplacez les données avec un plan.
Pour les services en production, utilisez rsync avec fenêtre d’indisponibilité ou migration aware-application ; pour les datasets volumineux, envisagez send/receive de snapshots. -
Mettez à jour fstab/unités systemd de montage.
Rendez les montages explicites et cohérents ; évitez les surprises de mountpoint. -
Implémentez l’outillage de snapshot par classe de dataset.
Un planning pour les données DB, un autre pour les images VM, aucun pour les caches sauf si prouvé utile. -
Appliquez des quotas là où résident les données « temporaires ».
Conteneurs, CI, caches, logs. Si ça peut être régénéré, ça doit être plafonné. -
Réservez pour les charges qui ne doivent pas échouer.
Bases de données et magasins d’état critiques. -
Ajoutez des contrôles de monitoring pour la dérive.
Alertez sur unsync=disabledinattendu, sur le nombre de snapshots, sur la capacité du pool qui fond. -
Faites un game day.
Entraînez-vous à restaurer un fichier depuis un snapshot ; à rollback d’un dataset cloné ; à recevoir une réplication dans le dataset correct.
Checklist opérationnelle : défauts par charge (kit de départ)
- Données DB (fichiers) :
recordsize=16K,compression=zstd,atime=off, snapshots fréquents mais courte rétention. - DB WAL/binlog :
recordsize=16K(ou spécifique à la charge),atime=off, snapshots généralement désactivés ou rétention minimale. - Images VM (fichiers) :
recordsize=128K,compression=zstd, snapshots quotidiens/hebdomadaires selon RPO/RTO. - ZVOL pour VM : définir
volblocksizeà la création ; documentez-le ; considérez les changements comme une migration. - Conteneurs : quotas obligatoires ; snapshots optionnels et généralement courts ; contrôles de cache si forte rotation.
- Sauvegardes/receive :
recordsize=1Msouvent sensé,primarycache=metadata, longue rétention mais réplication contrôlée. - Logs : compression activée, snapshots rarement nécessaires, quotas/rotation essentiels.
Checklist de gouvernance : empêcher le chaos de revenir
- Chaque dataset a un propriétaire et un objectif dans son nom.
- Chaque dataset prod a une politique de snapshot explicite documentée en code/runbooks.
- Des quotas existent pour tout ce qui n’est pas critique ou régénérable.
- Des reservations existent pour les états critiques où ENOSPC est inacceptable.
- La dérive de propriétés est détectable : rapports programmés sur
sync,recordsize,compression, nombre de snapshots.
FAQ
1) « Un dataset par charge » signifie-t-il un dataset par application ?
Pas forcément. Cela signifie un dataset par comportement et politique. Une application peut nécessiter plusieurs datasets (données DB vs WAL vs uploads).
Dix apps peuvent partager un dataset si elles partagent réellement le cycle de vie et la politique (rare, mais possible).
2) Combien de datasets est trop ?
Quand les humains ne peuvent plus répondre à « à quoi sert ce dataset ? » sans faire de l’archéologie. Opérationnellement, ZFS gère beaucoup de datasets sans problème.
Ce sont vos conventions de nommage, ownership et automatisation qui posent la limite.
3) Dois-je mettre recordsize à 8K pour chaque base de données ?
Ne généralisez pas. Beaucoup de moteurs DB écrivent en pages 8K/16K, mais la charge importe. Commencez par 16K pour les datasets DB, mesurez, et ajustez.
Si vous utilisez des ZVOLs, recordsize n’est pas le bon réglage — volblocksize l’est.
4) La compression est-elle sûre pour les bases de données ?
En général oui, et souvent bénéfique. Le risque est la charge CPU sur des systèmes déjà CPU-saturés.
La solution n’est pas « désactiver la compression partout » ; c’est « utiliser une compression sensée et surveiller le CPU ».
5) Dois-je jamais utiliser sync=disabled ?
Seulement si vous acceptez explicitement de perdre les écritures synchrones récentes en cas de crash/perte d’alimentation — typiquement pour des données scratch où la correction n’est pas requise.
Si c’est de l’état de production, traitez sync=disabled comme retirer la ceinture de sécurité pour améliorer le trajet.
6) Pourquoi la suppression d’un répertoire volumineux n’a-t-elle pas libéré d’espace immédiatement ?
Les snapshots et clones peuvent garder des blocs référencés. Vérifiez l’utilisation des snapshots sur ce dataset. L’espace revient quand la dernière référence est supprimée.
7) Les quotas nuisent-ils aux performances ?
Pas de manière significative dans des environnements typiques. Les quotas font mal aux sentiments, car ils forcent la planification.
Utilisez les quotas pour prévenir des pannes du pool ; c’est un bon compromis.
8) Si je définis recordsize maintenant, vais-je réécrire les données existantes ?
Non. Cela affecte les nouvelles écritures. Les blocs existants gardent leur taille jusqu’à réécriture par la charge normale ou via une réécriture/migration explicite.
9) Puis-je utiliser un seul planning de snapshot pour tout et appeler ça réglé ?
Vous pouvez, comme vous pouvez utiliser un seul mot de passe pour tout. Les dégâts apparaissent plus tard, et toujours au pire moment.
Les plannings de snapshot doivent correspondre à la valeur et à la rotation.
10) Quel est le gain le plus rapide si nous sommes déjà en chaos ?
Identifiez les trois principaux datasets par USED et utilisation de snapshots, puis appliquez quotas et corrections de politique de snapshot.
Cela réduit immédiatement le rayon d’impact, avant même un tuning approfondi.
Conclusion : prochaines étapes qui fonctionnent vraiment
Le chaos ZFS est rarement une défaillance technologique. C’est généralement un échec de gestion exprimé en problème de stockage.
L’approche « jeu de données par charge de travail » vous donne des points de contrôle : intention de performance, intention de sécurité et intention de capacité.
Et elle vous donne une carte que vous pouvez déboguer sous pression.
Étapes pratiques :
- Tracez votre carte des charges et marquez où les politiques diffèrent. Ce sont vos frontières de dataset.
- Créez ou refactorisez des datasets afin que chaque frontière ait des propriétés explicites : recordsize/volblocksize, compression, atime, snapshots, quotas/reservations.
- Implémentez une routine de triage rapide (capacité → saturation → voisin bruyant → dérive de politique) et répétez-la.
- Ajoutez une détection de dérive : alerte si un dataset prod passe à
sync=disabled, si le nombre de snapshots explose, ou si la réserve de capacité du pool passe sous votre seuil.
Faites cela, et votre prochaine page « le stockage est lent » cessera d’être un jeu de devinettes pour devenir une checklist. Voilà toute l’astuce.