ZFS dnodesize=auto : l’accélérateur de métadonnées que tout le monde oublie

Cet article vous a aidé ?

Les conversations sur les performances ZFS gravitent souvent autour des gros réglages visibles : recordsize, les vdevs spéciaux, les dispositifs slog, la compression, et l’éternel « faut-il mirror ou RAIDZ ? ». Pendant ce temps, une des optimisations de métadonnées les plus efficaces se tient dans un coin comme la roue de secours qu’on a oubliée : dnodesize=auto.

Si vous exécutez des charges avec beaucoup de petits fichiers, de lourds attributs étendus (xattrs), des ACL, ou simplement un flux incessant de parcours de répertoires, dnodesize=auto peut faire la différence entre « les disques sont inactifs mais tout est lent » et « les métadonnées sont redevenues ennuyeuses ». Cet article explique comment rendre les métadonnées ennuyeuses — parce que des métadonnées ennuyeuses sont un cadeau qu’on n’apprécie qu’après un appel à 03:00 pour un ls lent.

Table des matières

Qu’est-ce qu’un dnode (et pourquoi s’en soucier)

Dans ZFS, chaque fichier, répertoire, snapshot et objet de dataset est décrit par une structure appelée dnode (abréviation de « data node »). Pensez-y comme la carte d’identité du fichier plus son carnet d’adresses : il stocke les métadonnées et des pointeurs vers les blocs qui contiennent le contenu du fichier. Quand le fichier est petit ou riche en métadonnées, le dnode devient le centre de gravité.

Voici la conséquence pratique : si ZFS peut loger plus de métadonnées utiles dans le dnode, il peut éviter des lectures supplémentaires de « spill blocks » — des blocs supplémentaires qui contiennent les métadonnées débordantes. Les spill blocks ne sont pas diaboliques, mais ce sont des E/S en plus, de la pression sur le cache en plus et de la latence en plus — particulièrement douloureux quand vous faites beaucoup d’opérations de métadonnées.

La plupart des incidents de performance que j’ai traités dans ce domaine avaient une empreinte commune : les E/S données semblaient correctes, mais les parcours de répertoires et les motifs lourds en stat (pensez : scanners de sauvegarde, runners CI, couches de conteneurs, gestionnaires de paquets) sont passés de « assez rapides » à « pourquoi find prend 20 minutes ? ». Ce sont des problèmes de métadonnées, et le dnode est l’endroit où commencer.

Une petite blague, comme promis : les métadonnées, c’est comme la paperasserie de bureau — personne ne budgete du temps pour ça, et pourtant ça décide quand vous pouvez rentrer chez vous.

Ce que fait réellement dnodesize=auto

dnodesize est une propriété de dataset qui contrôle la taille des dnodes stockés sur disque. Historiquement, la taille de dnode par défaut était de 512 octets. C’est suffisant pour les métadonnées de base et un nombre limité de pointeurs de blocs — correct pour beaucoup de charges, mais pas idéal quand vous bourrez beaucoup d’attributs étendus, d’ACL ou d’autres métadonnées « bonus » dans l’objet fichier.

Quand vous définissez dnodesize=auto, ZFS est autorisé à utiliser des dnodes plus grands quand c’est nécessaire (jusqu’à une taille maximale supportée, typiquement 16K selon l’implémentation et les feature flags). Il ne gonfle pas aveuglément chaque objet ; il dimensionne les dnodes pour répondre aux besoins des métadonnées. L’objectif est de réduire (ou éliminer) les spill blocks pour les métadonnées qui autrement n’auraient pas leur place.

Bonus buffers, xattrs et spill blocks : le cœur du sujet

Chaque dnode contient un « bonus buffer », où ZFS stocke les métadonnées au-delà des champs de base — des éléments comme les informations ZPL (couche POSIX), les ACL, et potentiellement les xattrs inline selon la configuration.

Si le bonus buffer est trop petit, ZFS stocke le débordement dans un spill block. Les spill blocks sont des blocs supplémentaires qui doivent être lus pour accéder à ces métadonnées. C’est le moment où votre simple appel stat() devient « stat plus des E/S aléatoires supplémentaires ». Sur du flash cela peut encore avoir de l’impact ; sur des HDDs cela peut être catastrophique sous forte concurrence.

Avec dnodesize=auto, le bonus buffer peut être plus grand parce que le dnode lui-même peut être plus grand — ainsi les xattrs/ACL peuvent souvent vivre directement dedans. Le résultat pratique est moins d’IOPS consommées simplement pour répondre à la question « qu’est-ce que ce fichier ? ».

Auto vs dnodesize fixe

Vous pouvez aussi définir dnodesize sur des valeurs fixes comme 1k, 2k, 4k, etc. Les valeurs fixes sont des instruments grossiers : elles peuvent aider, mais elles peuvent gaspiller de l’espace quand vous n’avez pas besoin d’une taille plus grande. auto est l’approche « agrandir seulement quand c’est rentable ».

D’un point de vue opérationnel, j’aime auto parce que c’est la chose la plus proche d’un « je veux de bonnes performances métadonnées sans payer en permanence pour chaque objet ». Ce n’est pas magique, mais c’est un défaut raisonnable pour des charges modernes mixtes.

Pourquoi tout le monde oublie ce réglage

Trois raisons :

  1. Ce n’est pas tape-à-l’œil. Vous ne verrez pas un graphe de débit séquentiel doublé. Vous verrez moins de latence, moins d’IOPS consommés, moins de blocages dans les tâches intensives en répertoires — plus difficile à mettre en avant.
  2. Il est lié aux feature flags et aux habitudes de création de datasets. Beaucoup d’organisations ont des pools anciens mis à jour « juste assez », et les propriétés de dataset ont tendance à se fossiliser.
  3. Les problèmes de métadonnées ressemblent à « le système est lent ». Les gens chassent le CPU, le réseau ou l’hyperviseur. Pendant ce temps, le stockage subit une mort par mille coupures de métadonnées.

Deuxième petite blague : quand quelqu’un dit « ce n’est que des métadonnées », c’est votre signal pour programmer une longue réunion et annuler votre week-end.

Faits et contexte historique à répéter en réunion

  • ZFS a été conçu avec l’intégrité de bout en bout en premier. Les checksums et le copy-on-write n’étaient pas des ajouts ; ils ont façonné tout, y compris la disposition des métadonnées.
  • Les dnodes classiques faisaient 512 octets. Cela avait du sens quand les disques étaient plus lents et que les attentes en métadonnées étaient plus simples ; les charges modernes transportent plus « d’affaires » par fichier (ACL, xattrs, labels, métadonnées de conteneurs).
  • Les attributs étendus ont tout changé. À mesure que les OS et les applications ont commencé à s’appuyer sur les xattrs pour des labels de sécurité, des métadonnées utilisateur et l’indexation d’applications, les « métadonnées » ont gagné en importance.
  • Les ACL peuvent être volumineuses et bavardes. Les ACL NFSv4 en particulier peuvent gonfler les métadonnées par fichier, transformant les parcours de répertoires en tempête d’E/S de métadonnées.
  • Les feature flags ZFS ont débloqué des améliorations du format sur disque. Beaucoup de comportements « nouveaux » (y compris le stockage de métadonnées plus flexible) dépendent de l’activation de features au niveau du pool.
  • Les métadonnées dominent souvent les charges à petits fichiers. Dans les boîtes aux lettres, espaces CI, registres de langages et couches de conteneurs, vous êtes souvent goulot d’étranglement sur « lookups et stats », pas sur les lectures de payload.
  • La pression de l’ARC peut être une pression de métadonnées. L’ARC est un cache, mais il n’est pas sans fin. Des métadonnées surdimensionnées ou trop de spill blocks peuvent le faire churner.
  • Les équipes opérations l’ont appris à la dure. Le passage de monolithes à microservices a multiplié les arbres de fichiers, shards de logs et motifs de « petits objets » — les métadonnées sont devenues du trafic de production.

Quelles charges en bénéficient (et lesquelles non)

Bonnes candidates

dnodesize=auto a tendance à aider lorsque vous avez :

  • Beaucoup de petits fichiers et une activité fréquente de stat()/readdir() (systèmes de build, gestionnaires de paquets, runners CI).
  • Des xattrs lourds (labels de sécurité, taggage d’applications, métadonnées de sauvegarde, flux Samba, métadonnées macOS sur stockage partagé).
  • Environnements riches en ACL (ACL NFSv4, partages d’entreprise avec permissions complexes).
  • Datasets riches en snapshots où les métadonnées sont constamment référencées et parcourues.

Impact neutre ou limité

  • Fichiers séquentiels volumineux (vidéo, sauvegardes, gros blobs) : la charge est dominée par les blocs de données, pas par les spill de métadonnées.
  • Motifs de stockage orientés objet où vous stockez de gros objets et énumérez rarement des répertoires.

Compromis

Le compromis est simple : des dnodes plus grands peuvent légèrement augmenter l’empreinte métadonnée sur disque lorsqu’ils sont utilisés. Plus important encore, changer dnodesize ne réécrit pas magiquement les objets existants. Cela affecte les fichiers nouvellement créés (et parfois ceux modifiés lorsque les métadonnées sont réécrites), donc vous devez le traiter comme une optimisation prospective ou planifier une migration.

Comment l’activer en toute sécurité

Règles générales pour rester hors de problèmes :

  1. Vérifiez d’abord les feature flags. Certaines implémentations exigent l’activation de features de pool pour supporter des dnodes plus grands. Si votre pool est ancien, faites la diligence ennuyeuse.
  2. Activez au niveau du dataset où cela compte. Vous n’êtes pas obligé de le changer partout. Commencez par les points chauds connus de métadonnées.
  3. Mesurez avant et après. Les améliorations de métadonnées apparaissent dans la latence, les motifs d’IOPS et « combien de temps prend un parcours de répertoire ». Choisissez un test reproductible.
  4. Comprenez que ce n’est pas rétroactif dans la plupart des cas. Si vous avez besoin que les fichiers existants en bénéficient, planifiez un copy/rsync, un send/receive de migration, ou une reconstruction.

Tâches pratiques (commandes + interprétation)

Ci-dessous des tâches concrètes à exécuter en production (avec prudence) ou en staging (de préférence). Chacune inclut quoi observer.

Task 1: Identify datasets and current dnodesize

cr0x@server:~$ zfs list -o name,used,avail,mountpoint -r tank
NAME                 USED  AVAIL  MOUNTPOINT
tank                 980G  2.60T  /tank
tank/home            120G  2.60T  /tank/home
tank/ci              220G  2.60T  /tank/ci
tank/shares          410G  2.60T  /tank/shares

cr0x@server:~$ zfs get -o name,property,value,source dnodesize -r tank
NAME        PROPERTY   VALUE  SOURCE
tank        dnodesize  legacy local
tank/home   dnodesize  legacy inherited
tank/ci     dnodesize  legacy inherited
tank/shares dnodesize  legacy inherited

Interprétation : Si vous voyez legacy ou une petite taille fixe sur des datasets riches en métadonnées, vous avez un candidat. « Legacy » signifie souvent « ancien défaut ».

Task 2: Check pool feature flags status

cr0x@server:~$ zpool get all tank | egrep 'feature@|compatibility'
tank  compatibility  off    default
tank  feature@async_destroy  active  local
tank  feature@spacemap_histogram  active  local
tank  feature@extensible_dataset  active  local

Interprétation : Vous recherchez un ensemble de features plutôt moderne. Les noms exacts varient selon la plateforme. Si votre pool montre beaucoup de features disabled ou si vous êtes en mode compatibilité contraint, marquez une pause et évaluez avant de présumer du support du dimensionnement des dnodes.

Task 3: Enable dnodesize=auto on a target dataset

cr0x@server:~$ sudo zfs set dnodesize=auto tank/ci
cr0x@server:~$ zfs get dnodesize tank/ci
NAME     PROPERTY   VALUE  SOURCE
tank/ci  dnodesize  auto   local

Interprétation : Cela change le comportement pour les objets nouveaux/ressignés dans tank/ci. Cela ne réécrira pas l’ensemble du dataset par lui-même.

Task 4: Confirm xattr storage mode (SA vs dir)

cr0x@server:~$ zfs get xattr tank/ci
NAME     PROPERTY  VALUE  SOURCE
tank/ci  xattr     sa     inherited

Interprétation : xattr=sa stocke les xattrs dans la zone « system attribute » (bonus buffer) quand c’est possible. Cela s’accorde bien avec des dnodes plus grands parce que vous pouvez loger plus d’xattrs inline et éviter des objets séparés.

Task 5: Inspect ACL mode and inheritance settings

cr0x@server:~$ zfs get acltype,aclinherit,aclmode tank/shares
NAME        PROPERTY    VALUE      SOURCE
tank/shares acltype     nfsv4      local
tank/shares aclinherit  passthrough local
tank/shares aclmode     passthrough local

Interprétation : Les ACL NFSv4 peuvent être lourdes en métadonnées. Si les utilisateurs se plaignent de listings de répertoires lents sur des partages riches en ACL, le dimensionnement des dnodes et les choix xattr/SA peuvent avoir un impact.

Task 6: Spot metadata-bound behavior with iostat

cr0x@server:~$ zpool iostat -v tank 1 5
              capacity     operations     bandwidth
pool        alloc   free   read  write   read  write
----------  -----  -----  -----  -----  -----  -----
tank         980G  2.60T    950   1200  12.3M  18.1M
  mirror     490G  1.30T    480    610  6.1M   9.0M
    sda         -      -    240    305  3.0M   4.5M
    sdb         -      -    240    305  3.1M   4.5M
  mirror     490G  1.30T    470    590  6.2M   9.1M
    sdc         -      -    235    295  3.1M   4.6M
    sdd         -      -    235    295  3.1M   4.5M

Interprétation : Des opérations élevées avec un débit modéré signifie souvent des petites E/S. Ce n’est pas une preuve formelle de problèmes de métadonnées, mais c’est un motif commun quand les parcours de répertoires et l’activité de petits fichiers dominent.

Task 7: Measure directory walk time (repeatable micro-benchmark)

cr0x@server:~$ time find /tank/ci/workspace -type f -maxdepth 4 -print >/dev/null

real    0m18.442s
user    0m0.312s
sys     0m2.901s

Interprétation : Suivez ceci avant et après les changements sur des arbres de fichiers comparables. Si votre temps sys est élevé et que le temps mur est dominé par des attentes I/O, les lectures de métadonnées sont suspectes.

Task 8: Check ARC pressure and metadata caching signals

cr0x@server:~$ arcstat 1 3
    time  read  miss  miss%  dmis  dm%  pmis  pm%  mmis  mm%  arcsz     c
12:01:10   820   140     17    60  43%    20  14%    60  43%   28G   32G
12:01:11   790   180     23    95  53%    15   8%    70  39%   28G   32G
12:01:12   810   210     26   120  57%    10   5%    80  38%   28G   32G

Interprétation : Une hausse des misses de demande pendant des opérations lourdes en métadonnées peut indiquer un thrash de l’ARC. Moins de spill blocks peut réduire le nombre de blocs métadonnées distincts que vous devez mettre en cache.

Task 9: Verify dataset properties commonly coupled to metadata performance

cr0x@server:~$ zfs get atime,compression,primarycache,secondarycache,logbias tank/ci
NAME     PROPERTY       VALUE     SOURCE
tank/ci  atime          off       local
tank/ci  compression    lz4       inherited
tank/ci  primarycache   all       default
tank/ci  secondarycache all       default
tank/ci  logbias        latency   default

Interprétation : Désactiver atime réduit les écritures de métadonnées pour les arbres de fichiers en lecture intensive. Gardez primarycache=all sauf si vous avez une raison forte ; le caching uniquement métadonnées (metadata) peut être utile en cas de RAM contrainte mais n’est pas une recommandation par défaut.

Task 10: Check whether you’re paying for xattr spill the hard way

cr0x@server:~$ getfattr -d -m - /tank/ci/workspace/somefile 2>/dev/null | head
# file: tank/ci/workspace/somefile
user.build_id="9f1c..."
user.origin="pipeline-17"

Interprétation : Cela ne montre pas le spill directement, mais confirme que des xattrs sont en jeu. Si vous voyez une utilisation généralisée des xattrs et des opérations métadonnées lentes, le dimensionnement des dnodes devient plus pertinent.

Task 11: Evaluate small-file metadata behavior with a simple create/stat test

cr0x@server:~$ mkdir -p /tank/ci/.bench
cr0x@server:~$ rm -rf /tank/ci/.bench/*
cr0x@server:~$ time bash -c 'for i in $(seq 1 20000); do echo x > /tank/ci/.bench/f.$i; done'

real    0m24.901s
user    0m2.210s
sys     0m12.884s

cr0x@server:~$ time bash -c 'for i in $(seq 1 20000); do stat /tank/ci/.bench/f.$i >/dev/null; done'

real    0m11.332s
user    0m0.411s
sys     0m2.870s

Interprétation : C’est grossier mais utile. Si stat est disproportionnellement lent, vous êtes probablement limité par la récupération des métadonnées et le comportement du cache, pas par le débit de données.

Task 12: Confirm property inheritance and prevent accidental drift

cr0x@server:~$ zfs get -s local,inherited dnodesize -r tank | sed -n '1,12p'
NAME      PROPERTY   VALUE  SOURCE
tank      dnodesize  legacy local
tank/ci   dnodesize  auto   local
tank/home dnodesize  legacy inherited

Interprétation : C’est ainsi que vous repérez le problème « on l’a corrigé une fois mais les nouveaux datasets sont toujours incorrects ». Si vous voulez de la cohérence, définissez-le sur un dataset parent et laissez l’héritage faire son travail intentionnellement.

Task 13: Use send/receive to actually apply the new dnode sizing to existing data (migration pattern)

cr0x@server:~$ sudo zfs snapshot -r tank/ci@pre-dnode-mig
cr0x@server:~$ sudo zfs create -o dnodesize=auto -o xattr=sa tank/ci_new
cr0x@server:~$ sudo zfs send -R tank/ci@pre-dnode-mig | sudo zfs receive -F tank/ci_new

Interprétation : C’est la méthode propre « appliquer les nouvelles propriétés à tout ». Vous créez un nouveau dataset avec les propriétés souhaitées et vous recevez dedans. Il vous faudra un plan de basculement (points de montage, services, permissions), mais c’est ainsi que vous évitez d’attendre le churn organique.

Task 14: Validate post-change with a targeted metadata-heavy workload

cr0x@server:~$ sudo sh -c 'echo 3 > /proc/sys/vm/drop_caches'
cr0x@server:~$ time ls -lR /tank/ci/workspace >/dev/null

real    0m42.118s
user    0m0.902s
sys     0m6.331s

Interprétation : Vider les caches est perturbant et pas toujours possible en production ; utilisez du staging si vous le pouvez. L’objectif est d’éliminer l’excuse « c’était déjà en cache » et de forcer le système à montrer son comportement métadonnées sur disque.

Feuille de route de diagnostic rapide

Ceci est la checklist « vous avez 15 minutes avant que le responsable d’incident ne demande une direction ». L’objectif est de décider si les métadonnées sont le goulot et si le dimensionnement des dnodes/xattrs est dans le périmètre.

First: prove it smells like metadata

  1. Vérifiez les symptômes : les utilisateurs signalent des ls -l lents, find lent, vérifications de permissions lentes, étapes CI lentes comme « checkout » ou « npm install », mais les lectures/écritures en masse semblent correctes.
  2. Observez la forme des E/S : IOPS élevées, faible bande passante sur zpool iostat.
  3. Vérifiez la latence : même sur SSD, les pics de latence métadonnées apparaissent comme des latences tail du service (p95/p99) plutôt qu’une chute de débit.

Second: identify where the metadata pressure lives

  1. Trouvez le dataset : quel point de montage est lent ? Mappez-le à un dataset avec zfs list.
  2. Inspectez les propriétés clés : dnodesize, xattr, atime, acltype, primarycache.
  3. Vérifiez le comportement de l’ARC : si les demand misses montent pendant les parcours de répertoires, vous ne mettez probablement pas en cache ce que vous pensez.

Third: decide on the intervention level

  1. Faible risque : activez dnodesize=auto pour les futurs objets ; définissez/confirmez xattr=sa quand approprié ; désactivez atime si c’est sans risque.
  2. Risque moyen : migrez le dataset chaud via send/receive pour « repacker » les métadonnées avec les nouveaux réglages.
  3. Haut risque : changements de features au niveau du pool, modifications de vdevs spéciaux ou changements architecturaux. Ne faites pas ça en plein incident à moins d’aimer écrire des postmortems qui commencent par « par excès d’optimisme ».

Trois mini-récits du monde d’entreprise

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

Le ticket ressemblait à une farce : « ls est lent sur le partage ; copier de gros fichiers est OK. » C’est le genre de phrase qui déclenche deux instincts opposés : soit « ce n’est pas le stockage » parce que le débit est correct, soit « c’est forcément le stockage » parce que ls est en réalité un micro-benchmark de métadonnées déguisé.

L’environnement était mixte — des clients Linux, des utilisateurs SMB, et quelques jobs d’automatisation qui adoraient parcourir des arbres entiers plusieurs fois par heure. L’hypothèse qui a causé l’incident était simple et commune : « les métadonnées sont en RAM, donc ça ne peut pas être le goulot ». L’équipe avait dimensionné la RAM pour les caches applicatifs et supposait que ZFS gérerait le reste.

En réalité, l’ARC était en churn constant. Chaque parcours de répertoire déclenchait un défilé de lectures de métadonnées, et beaucoup de ces lectures tiraient des spill blocks parce que les fichiers portaient des xattrs et ACL lourdes. Rien n’était « cassé » au sens d’erreurs ou de disques défaillants. C’était juste une taxe que le système payait discrètement — jusqu’à ce que l’utilisation croisse assez pour transformer la taxe en outage.

La correction n’a pas été dramatique. D’abord, ils ont cessé de blâmer le réseau. Ensuite, ils ont activé dnodesize=auto et validé xattr=sa sur le dataset utilisé pour le partage. L’amélioration immédiate a été modeste parce que les objets existants avaient encore de petits dnodes. Le vrai gain est arrivé après une migration planifiée (send/receive dans un dataset neuf avec les nouvelles propriétés). Les listings de répertoires ont cessé de timeouter, et l’incident s’est clos avec la cause racine la moins glamour imaginable : « inefficacité de disposition des métadonnées ». Ce qui, à mon avis, est un compliment. Les causes ennuyeuses sont celles que l’on peut prévenir.

Mini-récit 2 : L’optimisation qui a mal tourné

Une autre organisation avait une obsession pour la performance, le genre qui arrive quand chaque équipe produit a un tableau de bord et que personne ne s’accorde sur ce que « rapide » signifie. Leur équipe stockage a fait un changement apparemment raisonnable : prioriser le cache sur les métadonnées parce que « la plupart des charges sont à petits fichiers ». Ils ont mis primarycache=metadata sur un dataset occupé hébergeant à la fois des artefacts de build petits et des images de conteneurs modérément volumineuses.

Au début, ça ressemblait à un succès. Les parcours de répertoire sont devenus plus réactifs. Puis les pulls d’images ont commencé à saccader. La pipeline de build qui streamait auparavant les couches de façon fluide a commencé à souffrir de latences tail. La rotation on-call a gagné une nouvelle alerte favorite : « registry fetch timed out ».

Le problème n’était pas que le caching métadonnées seul soit toujours mauvais ; c’est qu’ils l’ont appliqué largement, sans isoler les types de charge. En expulsant les données de l’ARC, ils ont poussé plus de lectures disque pour les couches de conteneurs. Le système est devenu excellent pour lister les fichiers et médiocre pour les lire — une optimisation qui a résolu la mauvaise douleur pour les mauvais consommateurs.

La résolution a été double : revenir à primarycache = all pour les charges mixtes, et utiliser dnodesize=auto plus xattr=sa pour réduire la surcharge de métadonnées sans priver le cache de données. La leçon est ancienne mais toujours valable : ne pas échanger le p95 d’une équipe contre l’outage d’une autre sans pouvoir nommer et défendre ce compromis.

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

Une des équipes opérations les plus saines que j’ai vues avait un rituel presque trop simple : à chaque création d’un nouveau dataset top-level, ils appliquaient un ensemble de propriétés de base — compression, atime, xattrs, politique ACL, et oui, dnodesize=auto quand approprié. Ils ne comptaient pas sur le savoir tribal. Ils l’ont codifié.

Des mois plus tard, un déploiement de sécurité est arrivé : plus d’étiquetage, plus d’xattrs, plus de complexité ACL. Le même type de changement qui avait fait fondre d’autres services de fichiers par le passé. Leur environnement… a surtout hausser les épaules. Il y a eu une certaine augmentation de l’utilisation des métadonnées, mais aucune chute soudaine.

Quand un partage particulier a montré des opérations de répertoire plus lentes, leur dépannage a été lui aussi ennuyeux : comparer les propriétés au baseline, confirmer le comportement de l’ARC, et isoler si le ralentissement venait d’un pattern côté client (certaines applis font des « stat tout » pathologiques deux fois). Ils n’ont pas eu à se précipiter pour rétrofiter les propriétés pendant un incident parce que les défauts étaient déjà raisonnables.

Voici la valeur cachée de dnodesize=auto : ce n’est pas un bouton de sauvetage héroïque ; c’est un bouton d’hygiène de base. Il transforme certaines classes d’incidents futurs en « on a vu une régression et on a corrigé », au lieu de « on a découvert que les métadonnées ont de la physique ».

Erreurs courantes, symptômes et corrections

Mistake 1: Expecting dnodesize changes to rewrite existing files

Symptôme : Vous définissez dnodesize=auto, relancez votre charge, et rien ne change.

Pourquoi : Les objets existants conservent leur taille de dnode actuelle à moins que les métadonnées ne soient réécrites d’une façon qui alloue une nouvelle taille de dnode ou que vous migriez les données.

Correction : Planifiez une migration de dataset (send/receive dans un nouveau dataset avec les propriétés souhaitées) ou acceptez que les bénéfices arrivent au fil du temps quand les fichiers churnent.

Mistake 2: Enabling dnodesize=auto without aligning xattr strategy

Symptôme : Vous voyez encore de fortes E/S métadonnées et les applis lourdes en xattr restent lentes.

Pourquoi : Si les xattrs sont stockés comme objets séparés (xattr=dir), vous effectuez toujours des recherches et lectures supplémentaires même avec des dnodes plus grands.

Correction : Évaluez xattr=sa pour le dataset, en tenant compte de la compatibilité OS/client et du comportement de la charge. Appliquez-le intentionnellement, pas comme une superstition.

Mistake 3: Applying metadata tuning to mixed workloads indiscriminately

Symptôme : Les opérations de répertoire s’améliorent mais les lectures en streaming se dégradent ; les utilisateurs se plaignent d’autres choses après la « correction ».

Pourquoi : Des propriétés comme primarycache et même des choix de recordsize peuvent déplacer la performance entre les chemins métadonnées et données.

Correction : Séparez les datasets par type de charge quand c’est possible. Utilisez l’outil ennuyeux : des points de montage séparés pour différentes personnalités de performance.

Mistake 4: Treating slow ls as “network” by default

Symptôme : Les utilisateurs SMB/NFS voient des listings lents ; les équipes ops poursuivent MTU, DNS et buffers de switch.

Pourquoi : La requête client déclenche une tempête de recherches de métadonnées ; le réseau n’est que le messager.

Correction : Corrélez les opérations client avec les IOPS côté serveur et les misses ARC. Exécutez un benchmark local serveur de parcours de répertoire pour séparer « serveur lent » de « réseau lent ».

Mistake 5: Ignoring ACL amplification

Symptôme : Les répertoires lourds en permissions sont dramatiquement plus lents que des répertoires de taille similaire avec des permissions plus simples.

Pourquoi : L’évaluation et le stockage des ACL peuvent gonfler les métadonnées, provoquant plus de spill et plus de lectures.

Correction : Passez en revue acltype et le mode d’héritage ; assurez-vous que le dataset est configuré pour la sémantique ACL attendue. Associez avec dnodesize=auto pour garder les métadonnées ACL inline quand c’est possible.

Listes de contrôle / plan pas à pas

Plan A: Low-risk rollout (new data benefits first)

  1. Choisissez le bon dataset : Identifiez le dataset avec les pires symptômes de métadonnées (workspace CI, répertoires home partagés, arbres de checkout).
  2. Capturez les paramètres actuels :
    cr0x@server:~$ zfs get dnodesize,xattr,acltype,atime,compression tank/ci
  3. Activez dnodesize=auto :
    cr0x@server:~$ sudo zfs set dnodesize=auto tank/ci
  4. Validez la politique xattr :
    cr0x@server:~$ sudo zfs set xattr=sa tank/ci
  5. Confirmez la politique atime : Si sûr pour vos applis :
    cr0x@server:~$ sudo zfs set atime=off tank/ci
  6. Mesurez avec un test reproductible : Gardez un benchmark find/stat de référence et comparez au fil du temps à mesure que de nouveaux objets sont créés.

Plan B: Migration rollout (existing data benefits now)

  1. Planifiez une fenêtre : Vous avez besoin d’un plan de basculement. N’improvisez pas des swaps de points de montage pendant que des utilisateurs écrivent.
  2. Snapshot de la source :
    cr0x@server:~$ sudo zfs snapshot -r tank/ci@mig-start
  3. Créez un dataset de destination avec les propriétés désirées :
    cr0x@server:~$ sudo zfs create -o dnodesize=auto -o xattr=sa -o atime=off tank/ci_v2
  4. Send/receive :
    cr0x@server:~$ sudo zfs send -R tank/ci@mig-start | sudo zfs receive -F tank/ci_v2
  5. Basculer : Arrêtez les writers, envoi incrémental final (si nécessaire), remontez et redémarrez les services.
  6. Validation post-cutover : relancez vos benchmarks métadonnées et observez les patterns ARC/iostat pendant la charge de pointe.

Plan C: Prevent drift (the practice that keeps paying)

  1. Définissez des templates de datasets de base par charge (usage général, partages, CI, logs).
  2. Faites respecter via l’automatisation : provisionnez les datasets avec des propriétés explicites plutôt que d’hériter de défauts inconnus.
  3. Audit régulier :
    cr0x@server:~$ zfs get -r -o name,property,value,source dnodesize,xattr,atime,acltype tank | head -n 40

FAQ

1) Qu’est-ce que dnodesize=auto change en termes simples ?

Il permet à ZFS d’allouer des dnodes plus grands seulement lorsqu’un objet en a besoin, de sorte que davantage de métadonnées puissent vivre inline et que moins de spill blocks soient nécessaires.

2) L’activer accélérera-t-il tout ?

Non. Il cible principalement les motifs lourds en métadonnées : beaucoup de petits fichiers, beaucoup de xattrs/ACL, et les parcours de répertoires. Les lectures/écritures séquentielles volumineuses ne remarqueront généralement rien.

3) Est-il sûr de l’activer sur un dataset existant ?

Généralement oui ; c’est un changement de propriété de dataset. Le principal « piège » est l’attente : il ne réécrira pas rétroactivement les anciens fichiers. La sécurité dépend aussi du support de votre plateforme et des features de pool activées.

4) Est-ce que ça augmente l’utilisation d’espace ?

Potentiellement, pour les objets qui utilisent effectivement des dnodes plus grands. L’objectif de auto est de payer le coût d’espace seulement quand cela réduit les spill blocks et améliore l’efficacité.

5) Quel lien avec xattr=sa ?

xattr=sa stocke les xattrs dans la zone system attribute (bonus buffer) quand c’est possible. Des dnodes plus grands signifient un budget de bonus buffer plus important, ce qui peut garder plus d’xattrs inline et réduire des E/S supplémentaires.

6) Si je définis dnodesize=auto, ai-je toujours besoin d’un vdev spécial pour les métadonnées ?

Ils résolvent des problèmes différents. dnodesize=auto réduit les E/S métadonnées en logeant plus de choses inline et en évitant les spill. Un vdev spécial accélère les E/S métadonnées en plaçant les métadonnées sur un média plus rapide. Vous pouvez utiliser les deux, mais ne traitez pas l’un comme un substitut de l’autre.

7) Comment savoir si les spill blocks me pénalisent ?

En pratique : stat et parcours de répertoire lents, IOPS élevés avec faible bande passante, misses ARC lors d’opérations métadonnées, et ralentissement disproportionné dans des arbres lourds en xattr/ACL. Prouver l’implication des spill blocks précisément peut être spécifique à la plateforme, donc considérez cela comme un exercice de corrélation plus des benchmarks contrôlés.

8) Devrais-je définir dnodesize sur une valeur fixe plus grande au lieu de auto ?

Les valeurs fixes peuvent convenir pour des datasets spécialisés où vous savez que les métadonnées seront toujours lourdes. Pour des charges mixtes ou incertaines, auto est généralement la meilleure option « ne pas trop payer ».

9) dnodesize=auto affecte-t-il send/receive ?

Il affecte la manière dont les objets nouvellement reçus sont disposés dans le dataset de destination, car ce sont les propriétés de destination qui gouvernent le comportement d’allocation. C’est pourquoi la migration via send/receive est un moyen pratique d’« appliquer » le dimensionnement des dnodes aux données existantes.

10) Quel est le gain le plus rapide si je ne peux pas migrer ?

Activez dnodesize=auto maintenant pour que les nouveaux fichiers en bénéficient, assurez-vous que xattr=sa est approprié, et éliminez les écritures de métadonnées évitables (comme atime=on sur des arbres chauds). Ensuite planifiez une migration quand l’activité le permettra.

Conclusion

dnodesize=auto est un de ces réglages ZFS qui a l’air de ne pas devoir compter — jusqu’à ce que vous soyez du mauvais côté d’un mur de métadonnées. Il n’emballe pas les graphiques de débit. Il fait que les parcours de répertoire cessent d’être un événement de performance. Il réduit la taxe I/O des xattrs et ACL. Et dans les environnements de production modernes — où le logiciel adore créer des montagnes de petits fichiers et attacher des métadonnées à tout — ce n’est pas une amélioration de niche. C’est de la stabilité.

Si vous retenez une chose opérationnelle : traitez les métadonnées comme une charge de travail de première classe. Définissez dnodesize=auto délibérément sur les datasets qui le méritent, associez-le à une politique cohérente xattr/ACL, et mesurez les résultats avec des tests reproductibles. Le meilleur jour pour corriger les métadonnées était avant l’incident. Le deuxième meilleur jour est avant que le prochain ls ne devienne votre tableau de bord d’incident.

← Précédent
Ubuntu 24.04 : pare-feu IPv6 oublié — colmatez la vraie faille (pas seulement IPv4) (cas n°12)
Suivant →
RISC-V : Vrai Challenger ou Belle Idée ?

Laisser un commentaire