Dnodes et métadonnées ZFS : pourquoi les métadonnées peuvent être votre vrai goulot d’étranglement

Cet article vous a aidé ?

Quand ZFS « ralenti », les gens accusent d’abord les disques, puis le réseau, puis « ce jeu de données voisin bruyant ». Pendant ce temps, le véritable coupable est souvent les métadonnées : des IOs minuscules, des poursuites de pointeurs et des dnodes qui ne tiennent plus correctement en cache. Votre pool peut transférer des gigaoctets par seconde en lectures séquentielles volumineuses, et pourtant s’effondrer quand on vous demande de faire un git status d’un grand dépôt, d’extraire un arbre de noyau ou de parcourir des millions d’objets de 4–16 Ko.

C’est le type de problème de performance qui ruine les week-ends d’astreinte parce que ça ressemble à tout et à rien. Le CPU semble correct. La bande passante paraît correcte. Et pourtant la latence pique, les opérations de répertoire rampent, et l’application jure que « la base est tombée » alors que vos tableaux de bord indiquent que les disques ne sont qu’à moitié occupés.

Ce que signifie réellement « goulot d’étranglement des métadonnées » dans ZFS

Dans ZFS, « métadonnées » n’est pas un concept vague. Ce sont des blocs concrets et des structures sur disque qui décrivent tout le reste : ensembles d’objets, pointeurs de blocs, blocs indirects, dnodes, entrées de répertoire (ZAP), spacemaps, classes d’allocation, journaux d’intention, et plus encore. Les métadonnées sont aussi le profil de charge : beaucoup de lectures/écritures minuscules, beaucoup d’IO aléatoires, de nombreuses dépendances et des sémantiques synchrones fréquentes qui imposent un ordre.

Les goulots d’étranglement des métadonnées surviennent quand votre système passe plus de temps à déterminer où se trouvent les données et ce qu’elles sont plutôt qu’à déplacer les données elles-mêmes. C’est pourquoi un pool qui alimente des sauvegardes à débit maximal peut quand même flancher face à :

  • Jobs CI qui créent/suppriment des millions de fichiers par jour
  • couches d’images de conteneurs et extraction d’images
  • patterns de type maildir (beaucoup de petits fichiers, nombreuses renommages)
  • flottes de VM avec snapshots, clones et churn
  • tout ce qui parcourt des répertoires et fait des stat à grande échelle

La charge de métadonnées est d’abord un problème de latence. L’ennemi n’est pas « faible débit », c’est l’attente et le temps de blocage : une chaîne de lectures dépendantes où chaque miss dans l’ARC se transforme en une IO disque, trop petite pour amortir le surcoût de seek/traduction flash, qui bloque ensuite la recherche suivante.

Voici le changement de perspective : sur des charges métadonnées-intensives, votre « budget IOPS » et votre « budget de hits de cache » comptent plus que votre « budget MB/s ». Vous pouvez avoir beaucoup de bande passante et être pourtant complètement bloqué.

Dnodes 101 : la petite structure qui décide de votre sort

Qu’est-ce qu’un dnode ?

Un dnode est la structure de métadonnées par objet dans ZFS. Pour un fichier, le dnode décrit son type, la métadonnée de taille de bloc, les données bonus et comment trouver les blocs de données du fichier (via des pointeurs de blocs). Pour les répertoires et autres objets ZFS, il joue le même rôle « cet objet existe et voici comment y accéder ».

Ça semble anodin. En pratique, les dnodes sont impliqués dans presque toutes les opérations de métadonnées qui vous importent : recherches, stat, permissions, attributs étendus et parcours des blocs indirects.

Pourquoi dnodesize compte

dnodesize contrôle la taille maximale d’un dnode. Des dnodes plus grands peuvent stocker plus d’informations « bonus » en ligne (utile pour les attributs étendus). Mais des dnodes plus grands augmentent aussi l’empreinte des métadonnées. Plus d’empreinte signifie moins de dnodes dans l’ARC pour la même RAM, plus de misses, plus d’IO disque et des opérations de métadonnées plus lentes.

Ce n’est pas théorique. C’est l’une des raisons les plus courantes pour lesquelles un système ZFS semble « correct pour les gros fichiers » et « affreux pour les petits fichiers ». L’équipe applicative appelle ça une régression de stockage. Vous appelez ça des mathématiques.

Données bonus et xattrs : le multiplicateur caché

ZFS peut stocker les attributs étendus (xattrs) de différentes façons. Quand les xattrs sont stockés en sa (system attributes), ils résident dans la zone bonus du dnode quand c’est possible. C’est excellent pour éviter des IO supplémentaires par attribut—jusqu’à ce que vous augmentiez dnodesize partout et découvriez que vous avez juste alourdi les métadonnées de chaque objet, qu’il en ait besoin ou pas.

Blague #1 : Les métadonnées, c’est comme la paperasserie au bureau — personne n’en veut plus, et pourtant elles continuent de se générer pendant que vous dormez.

« Mais j’ai mis dnodesize=auto, donc je suis sûr. » Pas forcément.

dnodesize=auto permet à ZFS d’augmenter la taille du dnode pour accueillir les données bonus. Cela aide souvent pour les charges lourdes en xattr. Mais cela signifie aussi que votre dataset peut accumuler progressivement des dnodes plus grands à mesure que des fichiers sont créés. Si vous pivotez plus tard vers « millions de petits fichiers sans xattrs », vous pourriez continuer à payer la taxe d’empreinte en cache à cause de décisions antérieures, snapshots et clones. ZFS est honnête ; il se souvient.

Où vivent les métadonnées et pourquoi c’est coûteux

ARC : votre absorbeur de choc pour métadonnées

L’ARC (Adaptive Replacement Cache) n’est pas juste un cache de lecture ; c’est votre accélérateur de métadonnées. Sur des charges métadonnées-intensives, le taux de hits de l’ARC fait la différence entre « assez rapide » et « pagination du pool une lecture 4K à la fois ». L’accès aux métadonnées a tendance à être aléatoire et difficile à précharger. Donc le cache est roi.

Mais l’ARC est une ressource partagée. Blocs de données, blocs de métadonnées, flux de prélecture et listes MFU/MRU se concurrencent. Si votre ARC est rempli par des lectures en streaming ou de gros blocs, les métadonnées peuvent être chassées. Le système « travaille plus » en émettant plus de petites lectures, ce qui augmente la latence, provoque des plaintes utilisateur, incite quelqu’un à lancer plus de diagnostics, ce qui ajoute encore de la charge. Félicitations : vous avez construit une boucle de rétroaction.

Blocs indirects, pointeurs de blocs et poursuite de pointeurs

ZFS est copy-on-write et utilise un arbre de pointeurs de blocs. Une opération de métadonnées devient souvent : lire le dnode → lire le(s) bloc(s) indirect(s) → lire les blocs ZAP → peut-être lire les blocs SA de débordement → répéter. Chaque étape dépend de la précédente. Si ces blocs ne sont pas en cache, l’opération devient une série de lectures aléatoires synchrones.

C’est pourquoi le NVMe « résout » si souvent la douleur liée aux métadonnées : pas parce que la charge devient séquentielle, mais parce que la latence des lectures aléatoires baisse d’un ordre de grandeur. Avec des disques, vous pouvez avoir beaucoup de plateaux et perdre quand même parce que la chaîne de dépendance sérialise le débit effectif.

vdev spécial : séparer volontairement les métadonnées (et petits blocs)

OpenZFS supporte une classe d’allocation « special » vdev (souvent appelée special vdev) qui peut stocker les métadonnées et, optionnellement, les petits blocs. Bien faite, c’est l’un des outils les plus puissants pour transformer un système lié par les métadonnées en un système tolérable.

Mal faite, elle devient un seul point de drame « pourquoi le pool est suspendu », car perdre un special vdev peut rendre le pool impossible à importer si les métadonnées s’y trouvent. Traitez-le comme du stockage haut de gamme : en miroir, surveillé et correctement dimensionné.

Écritures synchrones et le ZIL

Les opérations de métadonnées incluent souvent des sémantiques synchrones : créations, renommages, patterns fsync-intensifs, bases de données qui exigent un ordre. ZFS utilise le ZIL (ZFS Intent Log) pour satisfaire les écritures synchrones de manière sûre. Un périphérique SLOG séparé peut réduire la latence des écritures sync, mais il ne rend pas les lectures de métadonnées plus rapides. Il aide quand votre goulot est la « latence des écritures synchrones », pas la « latence des lectures aléatoires de métadonnées ».

En d’autres termes : le SLOG n’est pas un pansement pour une traversée de répertoire lente. C’est un pansement pour les applications qui exigent des écritures synchrones et sont sensibles à la latence.

Snapshots : le voyage dans le temps des métadonnées n’est pas gratuit

Les snapshots sont bon marché jusqu’à ce que vous les utilisiez comme système de contrôle de version pour des filesystems entiers. Chaque snapshot préserve d’anciens pointeurs de blocs. Cela augmente les métadonnées qui doivent rester accessibles, accroît la fragmentation et peut augmenter le coût des suppressions (parce qu’une suppression n’est pas une suppression ; c’est « libre quand aucun snapshot ne le référence plus »). Plus vous avez de snapshots et de churn, plus de métadonnées interviennent dans la maintenance quotidienne.

Modes de défaillance : comment les goulots d’étranglement métadonnées se manifestent en production

Groupe de symptômes A : « Le stockage est lent, mais iostat semble correct. »

Classique. Vous verrez un débit modéré, des IOPS modérés, et pourtant une latence côté utilisateur. Pourquoi ? Parce que le goulot est la latence par opération, pas le débit agrégé. Un scan de répertoire peut effectuer une ou deux lectures dépendantes à la fois, ne créant jamais assez de profondeur de queue pour « sembler occupé », et pourtant chaque lecture prend suffisamment de temps pour ruiner l’expérience utilisateur.

Groupe de symptômes B : « Le CPU est élevé en temps noyau ; l’app est bloquée. »

Les charges métadonnées-intensives stressent le DMU, la couche vnode et les chemins de checksum/compression. Un CPU système élevé plus des changements de contexte fréquents peuvent apparaître même quand les disques ne sont pas saturés — surtout si le système thrash sur des misses de cache et traite beaucoup de petites terminaisons IO.

Groupe de symptômes C : « Tout allait bien jusqu’à ce qu’on ajoute des snapshots / active les xattrs / change dnodesize. »

Ce sont des multiplicateurs. Les snapshots préservent l’historique, qui préserve la portée des métadonnées. Les xattrs stockés en SA peuvent augmenter l’usage des données bonus. Des dnodes plus grands augmentent l’empreinte en cache. Chacun est rationnel isolément. Ensemble, ils peuvent transformer « assez rapide » en « pourquoi ls prend 10 secondes ».

Groupe de symptômes D : « Les scrubs/resilvers rendent le cluster inutilisable. »

Scrub et resilver touchent largement métadonnées et données. Si vous êtes déjà proche du bord, les IO de fond vous poussent dans la latence visible par l’utilisateur. Les misses de métadonnées augmentent parce que l’ARC est déplacé par les lectures de scrub. Si votre vdev spécial est surchargé ou si vos métadonnées sont sur des disques lents, l’effet est aggravé.

Groupe de symptômes E : « La suppression est lente ; libérer l’espace prend une éternité. »

Les suppressions sous charge de snapshots signifient parcourir les métadonnées, mettre à jour les spacemaps et différer les libérations effectives. Si vous supprimez des millions de petits fichiers, le système effectue un grand nombre de mises à jour de métadonnées. Si votre fragmentation metaslab est élevée, les allocations et libérations deviennent plus coûteuses.

Faits intéressants et contexte historique (ce qui explique les bizarreries d’aujourd’hui)

  • ZFS est né au milieu des années 2000 chez Sun Microsystems avec des checksums de bout en bout et le copy-on-write comme points de conception centraux ; la sécurité des métadonnées était un objectif de première classe, pas un ajout.
  • Le copy-on-write signifie que les mises à jour de métadonnées sont des écritures : mettre à jour un pointeur de bloc ou une entrée de répertoire déclenche de nouveaux blocs, pas des modifications en place, ce qui est plus sûr mais peut amplifier les IO sur des charges à métadonnées fortement volatiles.
  • L’ARC a été conçu pour mettre en cache à la fois les données et les métadonnées, et en pratique le caching des métadonnées est souvent l’utilisation RAM à plus fort ROI sur les machines ZFS.
  • Les system attributes (« SA ») ont été introduits pour réduire les IO liés aux xattrs en emballant les attributs dans des buffers bonus, rendant les opérations de métadonnées plus rapides pour les charges riches en attributs.
  • Les feature flags ont permis à ZFS d’évoluer sans forks de format sur disque : les systèmes OpenZFS modernes négocient des fonctionnalités comme extensible_dataset et autres, changeant les capacités de métadonnées au fil du temps.
  • Les vdevs d’allocation spéciale sont apparus pour traiter une vraie douleur : les métadonnées sur HDD étaient un limiteur de performance même quand le débit des données était correct.
  • Le ZAP (ZFS Attribute Processor) est le format « dictionnaire » de ZFS utilisé pour les répertoires et propriétés ; il peut être micro-optimisé en cache et devient un point chaud dans les charges lourdes en répertoires.
  • Le tuning de recordsize est souvent mal compris : il affecte les blocs de données des fichiers, pas les dnodes, et ne résoudra pas à lui seul la lenteur des traversées de répertoire.
  • Le NVMe n’a pas juste rendu ZFS plus rapide ; il a changé les modes de défaillance : la latence des lectures aléatoires s’améliorant, le CPU et la contention des verrous peuvent devenir visibles là où les disques masquaient auparavant ces problèmes.

Playbook de diagnostic rapide

Quand un système ZFS « semble lent », vous devez décider si vous êtes lié par les données, par les métadonnées ou par les écritures synchrones. Ne réfléchissez pas trop. Exécutez le triage dans l’ordre.

1) D’abord : confirmez que c’est de la latence, et identifiez lecture vs écriture

  • Vérifiez la latence du pool et le queueing avec zpool iostat -v et, si disponible, les colonnes de latence par vdev.
  • Recherchez des motifs d’IO petits : beaucoup d’opérations, faible bande passante, fort wait.
  • Demandez : la douleur apparaît-elle dans ls -l, find, untar, opérations git ? C’est des métadonnées jusqu’à preuve du contraire.

2) Deuxième : vérifiez la santé de l’ARC et la pression sur les métadonnées

  • L’ARC est-il stable et proche de la cible ? Thrash-il ?
  • Les misses de métadonnées sont-elles élevées ? La prélecture pollue-t-elle l’ARC ?
  • La RAM est-elle suffisante pour le nombre d’objets et le working set ?

3) Troisième : trouvez où les métadonnées atterrissent physiquement

  • Avez-vous un special vdev ? Est-il en miroir ? Est-il saturé ?
  • Les métadonnées et petits blocs sont-ils sur HDD tandis que les données sont sur SSD (ou inverse) ?
  • Le pool est-il fragmenté ou très utilisé, générant plus de surcharge de métadonnées ?

4) Quatrième : isolez un « micro-benchmark métadonnées » représentatif des symptômes de production

  • Chronométrez une traversée de répertoire.
  • Chronométrez une boucle create/delete lourde en métadonnées dans un dataset de test.
  • Corrélez avec zpool iostat et les stats ARC pendant l’exécution.

5) Cinquième : validez le chemin d’écritures synchrones si l’app est fsync-intensive

  • Vérifiez le réglage sync, la présence d’un SLOG et la latence d’écriture.
  • Confirmez que la charge émet effectivement des écritures synchrones (ne supposez pas).

Tâches pratiques : commandes, sorties, ce que ça signifie, et la décision à prendre

Voici les commandes que j’exécute quand quelqu’un dit « ZFS est lent » et que je veux arrêter de deviner. Les sorties sont représentatives ; vos chiffres seront différents. L’important est ce que vous concluez.

Task 1: Identifier la topologie du pool et la présence d’un special vdev

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

        NAME                         STATE     READ WRITE CKSUM
        tank                         ONLINE       0     0     0
          raidz2-0                   ONLINE       0     0     0
            sda                      ONLINE       0     0     0
            sdb                      ONLINE       0     0     0
            sdc                      ONLINE       0     0     0
            sdd                      ONLINE       0     0     0
          special
            mirror-1                 ONLINE       0     0     0
              nvme0n1                ONLINE       0     0     0
              nvme1n1                ONLINE       0     0     0

errors: No known data errors

Ce que ça signifie : Il existe un special vdev en miroir. Les métadonnées (et possiblement les petits blocs) peuvent résider sur NVMe, ce qui est bon. S’il n’y a pas de special vdev et que vous avez des HDD, la latence des métadonnées est un suspect principal.

Décision : Si les métadonnées sont sur du rust lent et que la charge est métadonnées-intense, planifiez un special vdev (mirroré) ou déplacez la charge vers du flash.

Task 2: Mesurer la latence au niveau du pool et la forme des IO

cr0x@server:~$ zpool iostat -v tank 1 5
              capacity     operations     bandwidth
pool        alloc   free   read  write   read  write
----------  -----  -----  -----  -----  -----  -----
tank        7.12T  3.88T    950    420  12.3M  8.1M
  raidz2-0  7.12T  3.88T    120     80  12.0M  8.0M
    sda         -      -     30     20  3.0M   2.0M
    sdb         -      -     30     20  3.0M   2.0M
    sdc         -      -     30     20  3.0M   2.0M
    sdd         -      -     30     20  3.0M   2.0M
  special      80G   720G    830    330   0.3M  0.1M
    mirror-1     -      -    415    165   0.3M  0.1M
      nvme0n1    -      -    210     85   0.1M  0.0M
      nvme1n1    -      -    205     80   0.1M  0.0M

Ce que ça signifie : Beaucoup d’opérations, bande passante faible sur le special vdev : ça hurle métadonnées. Le grand vdev a un débit modeste ; votre douleur vient probablement de la latence des petites IO et des chaînes de dépendances.

Décision : Concentrez-vous sur le taux de hits ARC, la saturation du special vdev et les datasets lourds en métadonnées. Ne perdez pas de temps à chasser le débit séquentiel.

Task 3: Vérifier les propriétés des datasets qui influencent le comportement des métadonnées

cr0x@server:~$ zfs get -o name,property,value -s local,received -r atime,recordsize,xattr,dnodesize,primarycache,secondarycache,sync tank/app
NAME      PROPERTY        VALUE
tank/app  atime           off
tank/app  recordsize      128K
tank/app  xattr           sa
tank/app  dnodesize       auto
tank/app  primarycache    all
tank/app  secondarycache  all
tank/app  sync            standard

Ce que ça signifie : xattr=sa et dnodesize=auto peuvent être excellents pour les charges riches en xattr, mais peuvent aussi gonfler l’empreinte des métadonnées. primarycache=all autorise les données et les métadonnées dans l’ARC ; parfois vous voulez metadata pour des workloads en streaming qui évinceraient les métadonnées.

Décision : Si ce dataset sert des opérations de petits fichiers intensives en métadonnées, gardez le cache activé et considérez si primarycache=metadata est approprié pour des charges mixtes.

Task 4: Vérifier la taille de l’ARC et le comportement de base

cr0x@server:~$ sudo arcstat 1 3
    time  read  miss  miss%  dmis  dm%  pmis  pm%  mmis  mm%  arcsz     c
21:10:01   480    22      4    10   45    12   55     6   27   62G   64G
21:10:02   510    28      5    14   50    14   50     8   29   62G   64G
21:10:03   495    30      6    18   60    12   40    10   33   62G   64G

Ce que ça signifie : L’ARC est proche de la cible (arcsz proche de c). Le taux de miss est modéré. Si vous voyez miss% monter et rester élevé pendant des opérations métadonnées, vous allez sur disque pour les métadonnées.

Décision : Si l’ARC est trop petit ou pollué par des données, changez la stratégie de cache ou ajoutez de la RAM. Si les misses sont surtout des métadonnées (mm% élevé), le special vdev et le tuning des métadonnées deviennent urgents.

Task 5: Confirmer si les lectures atteignent vraiment les disques ou le cache

cr0x@server:~$ sudo zpool iostat -r tank 1 3
                 read
pool          ops/s   bandwidth
------------  -----  ---------
tank            980     12.6M
  raidz2-0      130     12.2M
  special       850      0.4M

Ce que ça signifie : Un grand nombre d’opérations de lecture vers le special vdev suggère que les métadonnées ne résident pas suffisamment dans l’ARC. Si c’était une charge à cache chaud, vous attendriez moins de lectures physiques lors de parcours répétés de répertoires.

Décision : Analysez l’éviction de l’ARC, les réglages de cache et la pression mémoire. Envisagez d’ajuster le comportement de cache par dataset.

Task 6: Vérifier l’utilisation du pool et le risque de fragmentation

cr0x@server:~$ zpool list -o name,size,alloc,free,cap,frag,health tank
NAME  SIZE  ALLOC  FREE  CAP  FRAG  HEALTH
tank  11T   7.12T  3.88T 64%  38%   ONLINE

Ce que ça signifie : 64% d’utilisation n’est pas effrayant, mais 38% de fragmentation suggère que les allocations deviennent moins contiguës. Le churn des métadonnées peut faire monter ce chiffre, rendant les opérations futures plus coûteuses.

Décision : Si la capacité est >80% et la fragmentation élevée, planifiez une récupération d’espace, une réécriture ou une extension du pool. La performance des métadonnées chute souvent brutalement à haute utilisation.

Task 7: Identifier les datasets « petits fichiers » et compter les objets

cr0x@server:~$ zfs list -o name,used,refer,logicalused,compressratio,usedsnap -r tank
NAME            USED   REFER  LUSED  RATIO  USEDSNAP
tank            7.12T  256K   7.10T  1.18x  1.4T
tank/app        2.20T  2.00T  2.30T  1.12x  600G
tank/ci-cache   1.10T  920G   1.30T  1.05x  480G
tank/backups    3.60T  3.50T  3.40T  1.24x  320G

Ce que ça signifie : Les datasets avec un grand USEDSNAP et un churn élevé (CI caches) déclenchent souvent des douleurs de métadonnées : les suppressions deviennent coûteuses, l’espace ne revient pas immédiatement et les opérations de répertoire ralentissent.

Décision : Ciblez tank/ci-cache pour revoir la politique de snapshots, modifier la politique de cache et potentiellement lui adjoindre son propre pool ou vdev spécial.

Task 8: Observer la latence en direct et la profondeur de queue par vdev

cr0x@server:~$ zpool iostat -v tank 1 3
              capacity     operations     bandwidth
pool        alloc   free   read  write   read  write
----------  -----  -----  -----  -----  -----  -----
tank        7.12T  3.88T   1100    600  14.0M  10.2M
  raidz2-0  7.12T  3.88T    150    120  13.7M  10.1M
  special      80G   720G    950    480   0.3M   0.1M

Ce que ça signifie : Si les opérations du special vdev augmentent lors de parcours de répertoire ou d’averses de création de fichiers, il fait son travail — mais il peut aussi devenir le goulot si sous-dimensionné ou si les appareils sont de qualité grand public et s’étouffent sur de l’IO aléatoire soutenue.

Décision : Si le special vdev est saturé, améliorez-le (NVMe plus rapide, plus de miroirs) ou réduisez les IO de métadonnées (politique de snapshots, isolation des workloads).

Task 9: Vérifier si votre charge est heavy en écritures synchrones

cr0x@server:~$ zpool status tank
  pool: tank
 state: ONLINE
  scan: scrub repaired 0B in 06:12:44 with 0 errors on Sun Dec 22 03:10:01 2025
config:

        NAME        STATE     READ WRITE CKSUM
        tank        ONLINE       0     0     0
          raidz2-0  ONLINE       0     0     0
        logs
          nvme2n1   ONLINE       0     0     0
        special
          mirror-1  ONLINE       0     0     0

errors: No known data errors

Ce que ça signifie : Il y a un périphérique SLOG (logs). Cela aide la latence des écritures synchrones. Ça ne fait rien pour les lectures de métadonnées. Les gens confondent souvent les deux.

Décision : Si les utilisateurs se plaignent de la latence d’fsync, validez la santé et la latence du SLOG. S’ils se plaignent de find et ls, arrêtez de regarder le SLOG.

Task 10: Rechercher des comptes pathologiques de snapshots et la rétention

cr0x@server:~$ zfs list -t snapshot -o name,used,refer,creation -s creation | tail -n 5
tank/ci-cache@auto-2025-12-26-0900    0B   920G  Fri Dec 26 09:00 2025
tank/ci-cache@auto-2025-12-26-1000    0B   920G  Fri Dec 26 10:00 2025
tank/ci-cache@auto-2025-12-26-1100    0B   920G  Fri Dec 26 11:00 2025
tank/ci-cache@auto-2025-12-26-1200    0B   920G  Fri Dec 26 12:00 2025
tank/ci-cache@auto-2025-12-26-1300    0B   920G  Fri Dec 26 13:00 2025

Ce que ça signifie : Des snapshots fréquents peuvent être acceptables, mais sur des datasets volatils ils préservent des blocs morts et augmentent les métadonnées à parcourir. « 0B used » par snapshot peut encore cacher beaucoup de complexité métadonnées selon le churn.

Décision : Pour les caches CI et datasets temporaires : raccourcissez la rétention, réduisez la fréquence ou ne les snapshottez pas du tout.

Task 11: Identifier si l’ARC est pollué par des lectures en streaming

cr0x@server:~$ zfs get -o name,property,value primarycache -r tank/backups
NAME         PROPERTY      VALUE
tank/backups primarycache  all

Ce que ça signifie : Les datasets de sauvegarde font souvent du streaming. S’ils sont autorisés à mettre en cache les données dans l’ARC, ils peuvent évincer les métadonnées nécessaires aux workloads sensibles à la latence.

Décision : Envisagez de définir primarycache=metadata sur les datasets en streaming pour que l’ARC garde les métadonnées chaudes sans gaspiller de RAM sur des données volumineuses que vous ne relirez pas bientôt.

Task 12: Appliquer un changement de cache ciblé (avec prudence)

cr0x@server:~$ sudo zfs set primarycache=metadata tank/backups

Ce que ça signifie : ZFS cessera de mettre en cache les blocs de données de ce dataset dans l’ARC, mais continuera de mettre en cache les métadonnées. Cela améliore souvent la latence métadonnées ailleurs dans le pool.

Décision : Si des datasets sensibles à la latence partagent le pool avec des workloads en streaming, c’est l’un des réglages les plus propres et à faible risque.

Task 13: Inspecter les choix xattr et dnodesize sur le dataset le plus chaud

cr0x@server:~$ zfs get -o name,property,value xattr,dnodesize tank/ci-cache
NAME          PROPERTY  VALUE
tank/ci-cache xattr     sa
tank/ci-cache dnodesize auto

Ce que ça signifie : Cette combinaison est souvent pertinente pour les workloads riches en xattr (les conteneurs peuvent l’être). Mais elle peut gonfler l’empreinte des métadonnées. Si votre charge n’est pas riche en xattr, cela peut être superflu.

Décision : Ne basculez pas ces réglages à la légère sur un dataset existant en espérant des résultats immédiats ; cela affecte les objets nouvellement créés. Envisagez des tests A/B sur un nouveau dataset.

Task 14: Micro-test basique « est-ce que c’est des métadonnées ? » avec un chronométrage de traversée

cr0x@server:~$ time find /tank/ci-cache/workdir -type f -maxdepth 3 -printf '.' >/dev/null

real    0m28.412s
user    0m0.812s
sys     0m6.441s

Ce que ça signifie : Un temps système élevé et une longue durée murale pour une simple traversée indiquent fortement des métadonnées et de l’attente IO. Si vous le répétez et que ça devient beaucoup plus rapide, l’ARC aide ; sinon, l’ARC n’est pas assez grand ou est évincé.

Décision : Répétez immédiatement et comparez. Si la deuxième exécution n’est pas beaucoup plus rapide, vous manquez des hits dans l’ARC et touchez fortement les disques/special vdev.

Trois mini-récits d’entreprise (anonymisés, plausibles, douloureusement familiers)

1) L’incident causé par une mauvaise hypothèse : « On a du NVMe, donc les métadonnées ne peuvent pas être le problème »

Une entreprise SaaS de taille moyenne exécutait une ferme de build sur ZFS. Ils avaient du NVMe moderne pour « le pool », donc l’infra supposait que la latence de stockage était essentiellement résolue. Le système de build subissait malgré tout des blocages sporadiques de 30–90 secondes pendant les heures de pointe. Les ingénieurs accusaient l’orchestrateur CI et l’excusaient comme « Kubernetes étant Kubernetes ».

En astreinte, on a fini par corréler les blocages avec des jobs qui décompressaient de grands arbres de dépendances : des dizaines de milliers de petits fichiers. Pendant ces fenêtres, le débit moyen du pool était faible et les NVMe étaient loin d’être saturés. Tout le monde acquiesçait devant les dashboards et concluait : pas le stockage.

La faute : ils ne regardaient que le débit et l’utilisation agrégée. Ils n’ont pas regardé la distribution des tailles d’IO, le taux de hits du cache, ou le fait que les opérations métadonnées étaient sérialisées et dominées par la latence. L’ARC était petit par rapport au working set d’objets, et une tâche de backup concurrente streamait des données via l’ARC, en évincant les métadonnées.

La correction fut ennuyeuse : définir primarycache=metadata sur les datasets de backup, ajouter de la RAM et déplacer les métadonnées du dataset CI sur un special vdev en miroir correctement dimensionné. Les « blocages mystères » se sont évaporés. L’orchestrateur n’a pas changé. Les hypothèses ont changé.

2) L’optimisation qui s’est retournée contre eux : « On va augmenter dnodesize pour accélérer les xattrs »

Une grande entreprise gérait un service de fichiers sensible à la conformité. Ils stockaient beaucoup d’étiquettes et d’attributs d’audit en tant que xattrs. Quelqu’un a lu qu’augmenter les dnodes aidait à garder les attributs en ligne et réduisait les IO. Vrai. Demi-vérité dangereuse.

Ils ont appliqué un template de dataset qui mettait dnodesize=auto à large échelle, y compris sur des datasets qui n’en avaient pas besoin : staging de logs, artefacts de build, scratch temporaire. Au fil du temps, ces datasets ont accumulé des objets avec des dnodes plus grands. L’efficacité de l’ARC s’est détériorée. Les misses métadonnées ont augmenté. Les opérations de répertoire ont ralenti, puis la latence client a grimpé, puis le volume de tickets a explosé.

Le retour arrière n’a pas été un simple toggle car les objets existants conservent leur structure de dnode. Ils ont dû créer de nouveaux datasets avec des valeurs par défaut sensées, migrer les données et traiter les anciens comme des déchets radioactifs jusqu’à leur retraite. La leçon n’était pas « ne jamais utiliser des dnodes plus gros ». La leçon était : appliquez-les où vous avez mesuré, pas où c’est à la mode.

3) La pratique ennuyeuse mais correcte qui a sauvé la situation : traiter le vdev spécial comme du stockage critique en production

Une organisation de services financiers exécutait OpenZFS avec un special vdev pour garder les métadonnées sur du flash. Ils l’ont fait correctement : NVMe entreprise en miroir, surveillance stricte et règle que la capacité du special vdev ne doit jamais être autorisée à « se remplir accidentellement ».

Pendant un problème de firmware fournisseur qui causait des timeouts NVMe intermittents sous charge de lecture aléatoire soutenue, le special vdev a commencé à produire des erreurs. La surveillance s’est déclenchée tôt parce qu’ils surveillaient séparément les compteurs d’erreurs et la latence sur la classe special, pas seulement la santé du pool.

Ils ont dégradé le pool de façon ordonnée : ralenti les jobs non essentiels, reporté les scrubs et migré les charges métadonnées les plus chaudes hors du système avant que la situation n’empire. Les disques de remplacement étaient prêts, et ils avaient déjà une procédure testée pour remplacer les membres du special vdev sans paniquer.

La plupart des équipes découvrent que « les métadonnées sont spéciales » quand le pool n’arrive pas à s’importer. Cette équipe l’a découvert un mardi matin dans une fenêtre de changement, et tout le monde a pu déjeuner. La fiabilité ressemble souvent à de la paranoïa bien documentée.

Erreurs courantes : symptômes → cause racine → correction

1) ls et find sont lents, mais les lectures de gros fichiers sont rapides

Symptôme : Lectures/écritures en streaming atteignent le débit attendu ; les traversées de répertoire et outils à stat rame.

Cause racine : Misses de métadonnées dans l’ARC ; métadonnées sur vdevs lents ; IOPS insuffisants pour des lectures aléatoires petites.

Fix : Augmenter l’efficacité de l’ARC (plus de RAM, primarycache=metadata sur datasets streaming), déployer un special vdev en miroir, réduire le churn des snapshots.

2) « On a ajouté un SLOG et rien n’a amélioré »

Symptôme : Latence applicative inchangée après installation d’un device journal rapide.

Cause racine : Le goulot est les lectures de métadonnées ou des opérations asynchrones ; le SLOG n’aide que les écritures synchrones.

Fix : Confirmez le taux d’écritures synchrones ; gardez le SLOG si nécessaire, mais réparez le chemin des métadonnées (ARC, special vdev, nombre d’objets, snapshots).

3) Pics de latence pendant scrubs/resilvers

Symptôme : IO utilisateur lent pendant les opérations de maintenance.

Cause racine : Déplacement de l’ARC par les lectures de scrub ; contention vdev ; lectures de métadonnées poussées sur des médias plus lents ; special vdev saturé.

Fix : Planifiez les scrubs hors-peak, limitez-les si possible, assurez-vous que le special vdev n’est pas sous-dimensionné et évitez les scans lourds en période de pic.

4) Supprimer des fichiers est lent et l’espace ne revient pas

Symptôme : rm -rf met une éternité ; le pool reste plein.

Cause racine : Les snapshots gardent les blocs vivants ; les mises à jour de métadonnées sont massives ; les frees sont différées à travers des TXG.

Fix : Réduisez la rétention/fréquence des snapshots sur les datasets volatils ; envisagez une rotation périodique des datasets (nouveau dataset + migration) pour le scratch.

5) « On a augmenté recordsize, c’est toujours lent »

Symptôme : Le tuning de recordsize n’a pas amélioré les opérations lourdes en métadonnées.

Cause racine : Recordsize affecte les blocs de données des fichiers, pas les traversées de répertoire ou l’empreinte du cache des dnodes.

Fix : Concentrez-vous sur les métadonnées : ARC, special vdev, stratégie xattr/dnodesize, politique de snapshots et isolation des workloads.

6) Le special vdev se remplit de manière inattendue

Symptôme : L’allocation du special vdev croît jusqu’à presque plein.

Cause racine : De petits blocs sont alloués au special à cause du réglage special_small_blocks, ou la croissance des métadonnées due à l’explosion d’objets.

Fix : Réévaluez le seuil special_small_blocks, augmentez la capacité du special vdev (ajoutez un miroir) et contrôlez la croissance d’objets (politiques de nettoyage).

Blague #2 : Un special vdev est « spécial » de la même manière qu’un point de défaillance unique est « spécial » — il attire toute l’attention à 3 h du matin.

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

Étape 1 : Classifier la charge (ne pas deviner)

  1. Exécutez zpool iostat -v pendant le ralentissement.
  2. Chronométrez une opération lourde en métadonnées (parcours de répertoire) et une opération lourde en données (lecture séquentielle).
  3. Si les ops sont élevées et la bande passante faible, considérez-le comme métadonnées/petites IO.

Étape 2 : Faites travailler l’ARC pour vous

  1. Vérifiez la taille et le taux de miss de l’ARC (utilisez arcstat).
  2. Si des workloads en streaming partagent le pool, définissez primarycache=metadata sur ces datasets.
  3. Si l’ARC est simplement trop petit pour le working set, ajoutez de la RAM avant d’acheter plus de disques. Vraiment.

Étape 3 : Placez les métadonnées sur le bon média

  1. Si vous avez des pools HDD servant des workloads métadonnées-intenses, planifiez un special vdev en miroir sur NVMe entreprise.
  2. Dimensionnez-le avec de la marge. Les métadonnées grandissent avec les objets et les snapshots, pas seulement les octets utilisés.
  3. Surveillez-le séparément : latence, erreurs et allocation.

Étape 4 : Contrôlez le churn et les snapshots comme un adulte

  1. Identifiez les datasets à churn élevé (CI caches, staging temporaire, couches de conteneurs).
  2. Réduisez la fréquence et la rétention des snapshots pour ces datasets.
  3. Envisagez la rotation de dataset : créez un nouveau dataset, basculez la charge, détruisez l’ancien quand c’est sûr. Cela évite des métadonnées pathologiques de longue durée.

Étape 5 : Testez les changements avec un micro-benchmark réaliste

  1. Utilisez un arbre de répertoire représentatif.
  2. Mesurez comportement cache froid et cache chaud.
  3. Validez avec zpool iostat et les stats ARC pendant les tests, pas après.

Étape 6 : Hygiène opérationnelle qui réduit les incidents « mystères »

  1. Maintenez l’utilisation du pool sous contrôle (évitez de vivre au-dessus de ~80% à moins d’aimer les surprises).
  2. Gardez les programmes de scrub prévisibles.
  3. Documentez les valeurs par défaut des datasets : xattr, dnodesize, primarycache, politiques de snapshots.

Une citation à mettre dans votre runbook

Idée paraphrasée, attribuée à John Allspaw : « Dans les systèmes complexes, le succès et l’échec viennent du même endroit : le travail normal et les décisions normales. »

FAQ

1) Qu’est-ce précisément qu’un dnode, en termes opérationnels ?

Un dnode est l’entrée de métadonnées pour un objet ZFS (fichier, répertoire, objet de dataset, etc.). Si votre charge effectue beaucoup de stat(), de recherches, de créations, de suppressions ou d’xattrs, l’accès aux dnodes est sur le chemin critique.

2) Les métadonnées sont-elles toujours sur le special vdev si j’en ai un ?

Typiquement les métadonnées sont allouées à la classe special quand elle est présente, mais le comportement peut varier selon les fonctionnalités et les réglages. De plus, les « petits blocs » peuvent y aller si special_small_blocks est configuré. Vous devez considérer le special vdev comme critique et le surveiller en conséquence.

3) Dois-je toujours activer special_small_blocks ?

Non. C’est puissant, mais cela peut remplir votre special vdev et transformer un accélérateur de métadonnées en un problème de capacité. Utilisez-le quand votre workload est dominé par de petits blocs et que vous avez suffisamment de flash miroir d’entreprise avec de la marge.

4) Augmenter recordsize aide-t-il les métadonnées ?

Pas vraiment. recordsize concerne les blocs de données de fichiers. Les opérations de métadonnées sont dominées par les dnodes, ZAP, blocs indirects et autres structures de métadonnées.

5) Ajouter un SLOG corrigera-t-il les traversées de répertoire lentes ?

Non. Le SLOG aide la latence des écritures synchrones. La lenteur des traversées de répertoire vient généralement des lectures de métadonnées et du comportement du cache. Conservez le SLOG si votre workload en a besoin, mais n’attendez pas qu’il règle les blocages côté lecture.

6) Pourquoi la deuxième exécution de find est parfois beaucoup plus rapide ?

Parce que l’ARC s’est réchauffé. La première exécution a chargé les métadonnées en cache. Si la deuxième exécution n’est pas plus rapide, soit le working set ne tient pas dans l’ARC, soit une autre charge l’évince.

7) Comment les snapshots affectent-ils la performance des métadonnées ?

Les snapshots préservent d’anciens pointeurs de blocs, ce qui augmente la quantité de métadonnées restée accessible. Sur des datasets volatils, cela peut amplifier le coût des suppressions et accroître la fragmentation, rendant les opérations métadonnées plus lentes avec le temps.

8) Quel est le « quick win » le plus sûr quand les métadonnées sont le goulot ?

Souvent : définir primarycache=metadata sur les datasets en streaming qui polluent l’ARC, et réduire la rétention des snapshots sur les datasets volatils. Ces deux changements améliorent fréquemment les hit rates métadonnées sans changer le format disque.

9) Dois-je changer dnodesize sur un dataset existant ?

Faites attention. Cela affecte les nouveaux objets, et les objets existants ne « rétréciront » pas forcément. Si vous avez besoin d’un état propre, créez un nouveau dataset avec les propriétés souhaitées et migrez.

10) Si j’ai beaucoup d’IOPS sur le papier, pourquoi les opérations métadonnées stagnent-elles encore ?

Parce que les métadonnées nécessitent souvent des lectures dépendantes en séquence. Vous ne pouvez pas paralléliser une chaîne de « lire un pointeur pour trouver le pointeur suivant » comme vous pouvez paralléliser de grandes lectures. La latence domine.

Conclusion : prochaines étapes qui font réellement la différence

Si vous retirez une leçon opérationnelle du comportement des métadonnées ZFS, retenez ceci : cessez de traiter la « performance stockage » comme un problème de bande passante. Pour de nombreuses charges réelles, c’est un problème de cache et de latence déguisé.

Faites ceci ensuite :

  • Exécutez le playbook de diagnostic rapide lors du prochain incident et classez correctement le goulot.
  • Protégez l’ARC des workloads en streaming avec primarycache=metadata au niveau des datasets lorsque c’est approprié.
  • Si vous êtes lourd en métadonnées sur HDD, ne négociez pas avec la physique : utilisez un special vdev miroir sur du flash entreprise, dimensionné avec marge.
  • Auditez les politiques de snapshots sur les datasets volatils et arrêtez de snapshotter le scratch comme s’il s’agissait d’une histoire précieuse.
  • Documentez et appliquez les valeurs par défaut des datasets. La plupart des désastres métadonnées commencent par « juste un changement de propriété ».

ZFS fera exactement ce que vous lui demandez, et il continuera de le faire bien après que vous ayez oublié que vous l’avez demandé. Rendez le chemin des métadonnées ennuyeux, prévisible et rapide. Votre futur vous remerciéra avec moins d’alertes et de meilleurs week-ends.

← Précédent
CPU Docker à 100 % : localiser le conteneur bruyant et le limiter correctement
Suivant →
Erreurs de permissions de fichiers WordPress : ce que 755/644 doivent être et pourquoi

Laisser un commentaire