ZFS Special Small Blocks : comment turbocharger les charges de petits fichiers

Cet article vous a aidé ?

Si votre système de stockage paraît rapide pour le streaming de gros fichiers mais commence à tousser dès que quelqu’un lance
find sur un arbre de petits fichiers, vous rencontrez l’ennemi le plus ancien du stockage d’entreprise : les métadonnées et
les E/S de petits blocs. ZFS est excellent pour protéger les données, mais il ne change pas la physique — les lectures aléatoires sur des médias lents restent
des lectures aléatoires sur des médias lents. La bonne nouvelle est que ZFS vous donne une issue : les special vdevs et les
special small blocks.

Bien fait, c’est une de ces optimisations rares qui ressemble à de la triche : les traversées de répertoires s’accélèrent, les
checkouts git ne traînent plus, l’extraction d’images de conteneurs devient plus réactive, et les tickets « pourquoi ls est lent ? »
disparaissent. Mal fait, c’est aussi un moyen spectaculaire d’apprendre combien les métadonnées sont cruciales pour la survie de votre pool.
Ce guide explique comment le faire correctement — en production, avec les contraintes qui surviennent quand ça sonne à l’astreinte.

Ce que « special small blocks » signifie vraiment

Dans ZFS, « special small blocks » est un raccourci pour une politique : stocker les blocs en dessous d’un seuil de taille choisi sur un
special vdev — typiquement SSD ou NVMe — tout en laissant les données volumineuses sur les vdevs principaux (souvent HDD).
Ce n’est pas un cache. Ce n’est pas du best-effort. Ces blocs y vivent.

Le special vdev a déjà une tâche même sans seuil : il peut héberger les métadonnées. Les métadonnées incluent des éléments comme
les blocs indirects, les dnodes, les entrées de répertoire et d’autres structures qui rendent les opérations du système de fichiers possibles.
Quand vous réglez special_small_blocks sur un dataset, vous étendez cette tâche : les petits blocs de données de fichier
atterrissent aussi sur le special vdev.

La magie n’est pas que le SSD soit « plus rapide ». La magie est que les charges de petits fichiers sont dominées par des E/S aléatoires, et
les E/S aléatoires sur HDD sont essentiellement un test de sympathie mécanique que vous perdrez. Si vous pouvez diriger ces lectures vers des médias à faible latence,
le caractère de la charge change : moins de seeks, moins de blocages IOPS, et une file d’attente plus courte au mauvais endroit.

En une phrase : les special vdevs permettent à ZFS de séparer « ce qu’il faut trouver vite » de « ce qui est gros à lire ».

Blague n°1 : Pensez aux special small blocks comme si vous donniez une voiture de sport à vos métadonnées. Souvenez-vous simplement : si vous grillez la voiture,
vous ne ratez pas seulement une réunion — vous oubliez où est le bureau.

Faits et contexte intéressants (pourquoi ça existe)

Voici quelques éléments d’histoire et de contexte concrets qui comptent quand vous prenez des décisions de conception :

  1. ZFS est né avec l’hypothèse de RAM coûteuse et de disques plus lents. L’ARC a été conçu pour être malin au sujet du cache parce que « ajouter de la RAM »
    n’était pas toujours la solution dans les premières déploiements.
  2. La « classe d’allocation spéciale pour métadonnées » est arrivée plus tard. Au départ ZFS n’avait pas de moyen propre
    pour épingler les métadonnées sur un autre type de vdev ; les special vdevs ont ajouté une classe explicite pour ça.
  3. La performance sur petits fichiers a toujours fait la réputation d’un système de fichiers. Les utilisateurs pardonnent
    moins les flux lents que les sauvegardes lentes — mais les ls lents et les builds lents provoquent une colère immédiate.
  4. Les tailles de secteur 4K ont changé les règles du jeu. « Les petits fichiers » se traduisent encore en plusieurs
    blocs de métadonnées, et des lectures alignées sur 4K impliquent que vous payez une taille I/O minimale même pour un petit contenu.
  5. Le copy-on-write rend les motifs d’écritures aléatoires plus courants. ZFS doit écrire de nouveaux blocs et mettre à jour
    les pointeurs ; il évite les mises à jour in-place, ce qui est excellent pour la cohérence et les snapshots mais peut augmenter l’agitation des métadonnées.
  6. NVMe n’a pas seulement ajouté de la vitesse ; il a changé le comportement des files d’attente. L’écart de latence entre NVMe et HDD
    signifie que déplacer une petite fraction des E/S (métadonnées + petits blocs) peut changer dramatiquement les temps de réponse bout en bout.
  7. « Special » n’est pas « cache ». L2ARC est un cache et peut être reconstruit ; l’allocation vers special vdev est
    authoritative. Ce seul fait guide la plupart des décisions de risque opérationnel.
  8. Les dnodes sont un vilain silencieux. Les dnodes sont des métadonnées par objet et peuvent devenir un hotspot pour des arbres à millions de fichiers ;
    placer les dnodes sur le special vdev peut être un énorme gain.
  9. La comptabilité d’espace compte plus que vous ne le pensez. Si le special vdev se remplit, ZFS ne « déverse » pas
    les métadonnées sur les HDD de façon amicale ; vous pouvez vous retrouver avec des échecs d’allocation et des choix opérationnels désagréables.

Comment ça marche sous le capot (suffisant pour bien décider)

The special vdev allocation class

Les pools ZFS sont composés de vdevs. Un « special vdev » est un vdev assigné à une classe d’allocation spéciale. ZFS utilise
des classes d’allocation pour décider où placer les blocs. Lorsqu’un pool dispose d’un special vdev, ZFS peut y allouer certains types de blocs —
en particulier les métadonnées. Si vous activez le placement des petits blocs, les petits blocs de données y vont aussi.

Qu’est-ce qui compte comme « métadonnées » ? C’est plus que des répertoires. Cela inclut l’arbre des blocs indirects qui pointent vers les blocs de contenu de fichier,
et ces blocs indirects sont accédés pendant les lectures. Les mettre sur SSD peut même bénéficier aux lectures de gros fichiers — particulièrement pour des fichiers fragmentés
ou des arbres de blocs profonds.

special_small_blocks : le réglage politique

special_small_blocks est une propriété par dataset. Si elle est réglée sur une taille (par exemple 16K), alors les blocs
de données de fichier jusqu’à cette taille sont alloués sur le special vdev.

Trois vérités opérationnelles :

  • Ce n’est pas rétroactif. Les blocs existants restent là où ils sont sauf s’ils sont réécrits.
  • Ça interagit avec recordsize. Si votre recordsize est 128K et que votre charge écrit beaucoup de petits fichiers, vous ne stockez
    que la portion utilisée, mais le comportement d’allocation et de compression peut importer.
  • Ça peut augmenter rapidement la consommation du special vdev. Les datasets de petits fichiers sont souvent « entièrement petits blocs ».
    C’est le but, mais aussi le piège.

Dnodes, dnodesize et « métadonnées qui se comportent comme des données »

ZFS stocke les métadonnées par fichier dans les dnodes. Quand les attributs étendus et les ACL sont lourds (pensez : partages corporatifs avec audit agressif et clients Windows),
les dnodes peuvent gonfler. Si les dnodes ne tiennent pas dans le « bonus buffer », ZFS peut déverser des métadonnées dans des blocs séparés, augmentant les lectures.
Des réglages comme xattr=sa et dnodesize=auto peuvent réduire les déversements et amplifier l’avantage du special vdev.

Pourquoi ça ressemble à un « turbo »

Pour de nombreuses charges de petits fichiers, le chemin critique est :

  1. Rechercher les entrées de répertoire (E/S de métadonnées).
  2. Lire les attributs de fichier (plus d’E/S de métadonnées).
  3. Lire les petites données de fichier (petites lectures aléatoires).

Si ces étapes se passent sur HDD, vous êtes limité par les seeks et la profondeur de file d’attente. Si elles se passent sur NVMe, vous êtes limité par
le CPU, les taux de hit ARC, et le modèle de threads de l’application. C’est un problème différent, et généralement meilleur.

Blague n°2 : Les HDD sont excellents pour enseigner la patience. Malheureusement, votre pipeline CI ne s’était pas inscrit au cours.

Charges de travail qui gagnent (et celles qui ne gagnent pas)

Meilleurs candidats

Les special small blocks brillent quand votre working set inclut beaucoup de petits fichiers et des opérations lourdes en métadonnées :

  • Arbres source et artefacts de build (millions de petits fichiers, traversées répétées, beaucoup d’appels stat).
  • Couches d’images de conteneurs (tempêtes d’unpack, nombreux petits fichiers, extraction parallèle).
  • Workloads de type Maildir (beaucoup de petits messages en tant que fichiers individuels).
  • Hébergement web et contenu CMS (milliers de petits fichiers PHP, plugins, vignettes, churn de métadonnées).
  • Partages corporatifs avec arbres profonds (navigation de répertoire intensive, vérifications ACL, clients Windows).
  • Dépôts de sauvegarde avec nombreux petits chunks (dépend de l’outil ; certains créent beaucoup de petits objets).

Résultats mitigés

Images VM et bases de données peuvent bénéficier indirectement parce que les métadonnées et blocs indirects sont plus rapides,
mais elles sont généralement dominées par des E/S aléatoires moyennes à grandes et par le comportement des écritures synchrones. Si vous cherchez à corriger
la latence d’une base, vous devez probablement parler de SLOG, recordsize, ashift et tuning applicatif en premier.

Mauvais candidats

Si votre charge est principalement de grosses lectures/écritures séquentielles et que vous avez déjà un prefetch sain et un recordsize large,
les special small blocks ne changeront pas la donne. Vous pouvez néanmoins vouloir mettre les métadonnées sur special pour des opérations de gestion plus réactives,
mais n’attendez pas de miracles pour des flux séquentiels.

Concevoir un special vdev : redondance, dimensionnement et domaine de panne

La règle non négociable : redondance ou regret

Le special vdev fait partie du pool. S’il meurt et que vous n’avez pas de redondance, votre pool n’est pas « dégradé » de façon sympathique.
Vous pouvez perdre le pool parce que les métadonnées peuvent manquer. Traitez les special vdevs comme du stockage haut de gamme, pas comme des accélérateurs jetables.

En pratique, cela signifie mirror au minimum, et dans certains environnements RAIDZ (pour la classe special) si vous pouvez tolérer l’amplification d’écritures
et voulez l’efficacité de capacité. Les mirrors sont courants parce qu’ils gardent la latence faible et simplifient les temps de rebuild.

Dimensionnement : la partie que tout le monde sous-estime

Le dimensionnement du special vdev est là où le projet « turbo » réussit ou devient un générateur d’incidents trimestriels. Vous devez estimer :

  • L’empreinte des métadonnées pour les données existantes et futures.
  • L’empreinte des petits fichiers sous votre seuil special_small_blocks.
  • La croissance due aux snapshots (métadonnées et petits blocs qui peuvent se multiplier avec le churn).
  • De l’espace libre pour la performance et la santé de l’allocateur.

Un modèle mental pratique : si vous stockez des millions de petits fichiers, les « données » peuvent être petites, mais le
nombre d’objets rend les métadonnées non triviales. Un arbre avec 50 millions de fichiers peut faire « seulement » quelques téraoctets, et
rester une machine à métadonnées.

Un autre modèle pratique : le special vdev est là où vit le cerveau de votre système de fichiers. Ne construisez pas un cerveau avec les plus petites pièces que vous avez trouvées dans le placard des achats.

Choisir un seuil

Les seuils typiques sont 8K, 16K, 32K, parfois 64K. La bonne valeur dépend de :

  • Votre distribution de tailles de fichiers.
  • À quelle vitesse vous pouvez augmenter la capacité special.
  • Que les « petits fichiers » du dataset soient vraiment sensibles à la latence.

Commencez conservateur. Vous pouvez augmenter le seuil plus tard ; le réduire ne déplace pas les blocs existants.

Compression et special

La compression change la taille effective des blocs stockés. Si vous avez compression=zstd, de nombreux fichiers « moyens » peuvent se compresser
en dessous de votre seuil et atterrir sur special. Parfois c’est excellent, parfois c’est une surprise.

Sélection des dispositifs et endurance

Les special vdevs subissent beaucoup d’écritures aléatoires petites, surtout pour les charges lourdes en métadonnées et les snapshots. Choisissez des dispositifs
avec une endurance décente et une protection contre la perte de puissance si votre environnement l’exige. Les NVMe grand public peuvent fonctionner en labo ;
en production c’est souvent un ticket de support au ralenti.

Implémentation : pas à pas avec commandes réelles

Les commandes ci‑dessous supposent OpenZFS sur Linux. Si vous êtes sur FreeBSD, les concepts et la plupart des commandes sont les mêmes,
mais les chemins de périphérique et certains outils diffèrent. Remplacez les noms de pool/dataset et les identifiants de périphériques pour correspondre à votre système.

Task 1: Confirm pool topology and current health

cr0x@server:~$ sudo zpool status -v
  pool: tank
 state: ONLINE
  scan: scrub repaired 0B in 0 days 02:11:32 with 0 errors on Sun Dec 22 03:15:12 2025
config:

        NAME                        STATE     READ WRITE CKSUM
        tank                        ONLINE       0     0     0
          raidz2-0                  ONLINE       0     0     0
            /dev/disk/by-id/...     ONLINE       0     0     0
            /dev/disk/by-id/...     ONLINE       0     0     0
            /dev/disk/by-id/...     ONLINE       0     0     0
            /dev/disk/by-id/...     ONLINE       0     0     0
            /dev/disk/by-id/...     ONLINE       0     0     0
            /dev/disk/by-id/...     ONLINE       0     0     0

errors: No known data errors

Interprétation : confirmez qu’il n’y a pas de classe special existante et que le pool est stable. N’ajoutez pas de complexité structurelle
à un pool déjà malade.

Task 2: Identify candidate SSD/NVMe devices by stable IDs

cr0x@server:~$ ls -l /dev/disk/by-id/ | grep -E 'nvme|ssd|ata' | head
lrwxrwxrwx 1 root root 13 Dec 24 09:10 nvme-SAMSUNG_MZVLB1T0HBLR-00000_S4EWNX0R123456 -> ../../nvme0n1
lrwxrwxrwx 1 root root 13 Dec 24 09:10 nvme-SAMSUNG_MZVLB1T0HBLR-00000_S4EWNX0R789012 -> ../../nvme1n1

Interprétation : utilisez /dev/disk/by-id pour éviter les surprises de renumérotation des périphériques après des redémarrages ou des swaps HBA.

Task 3: Check ashift and general pool properties

cr0x@server:~$ sudo zdb -C tank | grep -E 'ashift|name'
        name: 'tank'
        ashift: 12

Interprétation : ashift=12 implique des secteurs 4K, ce qui est bon. Les special vdevs doivent aussi être alignés de façon appropriée ;
vous ne voulez généralement pas de mélange qui crée une amplification d’écritures inutile.

Task 4: Add a mirrored special vdev (metadata class)

cr0x@server:~$ sudo zpool add tank special mirror \
  /dev/disk/by-id/nvme-SAMSUNG_MZVLB1T0HBLR-00000_S4EWNX0R123456 \
  /dev/disk/by-id/nvme-SAMSUNG_MZVLB1T0HBLR-00000_S4EWNX0R789012
cr0x@server:~$ sudo zpool status tank
  pool: tank
 state: ONLINE
config:

        NAME                                            STATE     READ WRITE CKSUM
        tank                                            ONLINE       0     0     0
          raidz2-0                                      ONLINE       0     0     0
            /dev/disk/by-id/...                         ONLINE       0     0     0
            ...
          special
            mirror-1                                    ONLINE       0     0     0
              nvme-SAMSUNG_MZVLB1T0HBLR...123456        ONLINE       0     0     0
              nvme-SAMSUNG_MZVLB1T0HBLR...789012        ONLINE       0     0     0

Interprétation : le pool a maintenant une classe special. À partir de ce moment, les allocations de métadonnées peuvent y atterrir (selon les flags de fonctionnalité et le type de bloc).
Le destin du pool est désormais lié à ce mirror. Surveillez-le sérieusement.

Task 5: Verify special vdev allocation is recognized

cr0x@server:~$ sudo zpool list -v tank
NAME         SIZE  ALLOC   FREE  CKPOINT  EXPANDSZ   FRAG    CAP  DEDUP  HEALTH  ALTROOT
tank        54.5T  31.2T  23.3T        -         -    23%    57%  1.00x  ONLINE  -
  raidz2-0  54.5T  31.2T  23.3T        -         -    23%    57%
  special    1.8T   120G   1.7T        -         -     2%     6%

Interprétation : vous pouvez voir la capacité special séparément. Gardez un œil là-dessus ; « special n’est utilisé qu’à 6% » est une phrase rassurante que vous voulez continuer à prononcer.

Task 6: Create a dataset for the small-file workload with sane defaults

cr0x@server:~$ sudo zfs create -o atime=off -o compression=zstd -o xattr=sa tank/work
cr0x@server:~$ sudo zfs get -o name,property,value atime,compression,xattr tank/work
NAME       PROPERTY     VALUE
tank/work  atime        off
tank/work  compression  zstd
tank/work  xattr        sa

Interprétation : désactiver atime réduit les écritures de métadonnées pour les workloads en lecture. xattr=sa réduit souvent le déversement
vers des objets xattr séparés pour les attributs étendus Linux, ce qui aide lors des tempêtes de petits fichiers.

Task 7: Enable special small blocks on that dataset

cr0x@server:~$ sudo zfs set special_small_blocks=16K tank/work
cr0x@server:~$ sudo zfs get special_small_blocks tank/work
NAME       PROPERTY              VALUE   SOURCE
tank/work  special_small_blocks  16K     local

Interprétation : les nouveaux blocs de données de fichier jusqu’à 16K seront alloués sur le special vdev. Cela ne réécrira pas les anciennes données.

Task 8: Ensure dnodes can scale with metadata-heavy workloads

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

Interprétation : dnodesize=auto permet des dnodes plus grands quand nécessaire, ce qui peut réduire les blocs de métadonnées supplémentaires
et améliorer les traversées de répertoire et les accès aux attributs. Cela peut augmenter l’empreinte des métadonnées — planifiez la capacité.

Task 9: Baseline small-file behavior before and after (practical micro-benchmark)

cr0x@server:~$ mkdir -p /tank/work/testtree
cr0x@server:~$ /usr/bin/time -f "elapsed=%E" bash -c 'for i in $(seq 1 200000); do echo x > /tank/work/testtree/f_$i; done'
elapsed=0:04:31
cr0x@server:~$ /usr/bin/time -f "elapsed=%E" bash -c 'find /tank/work/testtree -type f -exec stat -c %s {} \; > /dev/null'
elapsed=0:00:46

Interprétation : ne prenez pas les chiffres absolus au pied de la lettre entre systèmes ; cherchez les changements relatifs et où le temps se déplace (CPU vs I/O).
Aussi : 200k fichiers suffisent pour montrer la douleur sans transformer votre système de fichiers en projet scientifique à long terme.

Task 10: Observe real-time I/O distribution (special vs main)

cr0x@server:~$ sudo zpool iostat -v tank 2 5
              capacity     operations     bandwidth
pool        alloc   free   read  write   read  write
----------  -----  -----  -----  -----  -----  -----
tank        31.3T  23.2T    210   1800   8.3M   145M
  raidz2-0  31.2T  23.3T     40    220   7.9M    40M
  special    140G  1.6T     170   1580   420K   105M
----------  -----  -----  -----  -----  -----  -----

Interprétation : vous voulez voir beaucoup d’IOPS d’écriture de métadonnées/petits blocs sur le special vdev au lieu de marteler les vdevs HDD.
Le débit sur special peut sembler « petit » tandis que les opérations sont élevées — c’est normal.

Task 11: Check dataset properties that commonly interact with small blocks

cr0x@server:~$ sudo zfs get -o name,property,value recordsize,primarycache,logbias,sync tank/work
NAME       PROPERTY      VALUE
tank/work  recordsize    128K
tank/work  primarycache  all
tank/work  logbias       latency
tank/work  sync          standard

Interprétation : pour les arbres de petits fichiers, recordsize n’est généralement pas votre levier (il importe plus pour les gros fichiers et bases).
Mais primarycache peut être ajusté si vous manquez d’ARC, et sync peut tuer la performance si vous faites des écritures synchrones involontaires.

Task 12: Find whether special is filling up and why

cr0x@server:~$ sudo zfs list -o name,used,available,usedbysnapshots,usedbydataset,usedbychildren -r tank/work
NAME                 USED  AVAIL  USEDSNAP  USEDDS  USEDCHILD
tank/work           1.02T   9.1T      120G    900G         -
cr0x@server:~$ sudo zpool list -v tank | sed -n '1,5p'
NAME         SIZE  ALLOC   FREE  CKPOINT  EXPANDSZ   FRAG    CAP  DEDUP  HEALTH  ALTROOT
tank        54.5T  32.2T  22.3T        -         -    24%    59%  1.00x  ONLINE  -
  raidz2-0  54.5T  31.6T  22.9T        -         -    24%    58%
  special    1.8T   600G   1.2T        -         -     9%    33%

Interprétation : si l’utilisation du special augmente de manière disproportionnée par rapport à l’utilisation du dataset, vous pouvez avoir un dataset à fort churn,
une promotion causée par la compression, ou des snapshots qui épinglent des petits blocs. Suivez l’used-by-snapshots attentivement.

Task 13: Force rewriting old blocks to migrate to special (controlled)

Parce que special_small_blocks n’est pas rétroactif, vous pouvez vouloir réécrire les données en place. Vous pouvez le faire
en copiant à l’intérieur du dataset (ce qui alloue de nouveaux blocs selon la politique actuelle) ou en utilisant send/receive vers un nouveau dataset.

cr0x@server:~$ sudo zfs snapshot tank/work@rewrite-start
cr0x@server:~$ sudo rsync -aHAX --delete /tank/work/ /tank/work_rewritten/

Interprétation : c’est lourd en I/O. Faites-le avec des garde-fous (limitation d’IO, fenêtres hors pics) et assurez-vous que le special a la capacité pour absorber les blocs migrés.

Task 14: Verify special class is actually being used for small blocks

cr0x@server:~$ sudo zfs get -r special_small_blocks tank/work
NAME       PROPERTY              VALUE  SOURCE
tank/work  special_small_blocks  16K    local
cr0x@server:~$ sudo zdb -dddd tank/work | head -n 20
Dataset tank/work [ZPL], ID 123, cr_txg 45678, 1.02T, 720000 objects

    Object  lvl   iblk   dblk  dsize  dnsize  lsize   %full  type
       123    1   128K    16K    16K     512   16K    100%  ZFS plain file
       124    1   128K    16K    16K     512   12K     75%  ZFS plain file

Interprétation : la sortie de zdb varie selon la version, mais vous cherchez des preuves que les blocs de données sont petits et alloués sous la politique actuelle.
Pour une vérification plus approfondie, corrélez avec zpool iostat -v pendant les pics de charge et regardez les opérations special augmenter.

Exploitation : surveillance, planification de capacité et mises à niveau

Ce que vous surveillez change quand special existe

Avant les special vdevs, la « capacité du pool » était la métrique principale. Après special, vous avez deux capacités qui
peuvent indépendamment ruiner votre journée :

  • Espace libre des vdevs principaux (la classique).
  • Espace libre du special vdev (la nouvelle falaise).

Le special peut se remplir plus vite que prévu parce qu’il contient les blocs qui churnent le plus : métadonnées, petits fichiers,
et parfois des blocs compressés qui tombent sous votre seuil.

Trois mini-histoires du monde corporate

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

Une grande organisation interne d’ingénierie a déployé des special vdevs pour accélérer une ferme de build monorepo. Les résultats initiaux étaient
glorieux : les checkouts et scans de dépendances se sont accélérés, et la file CI a cessé de s’accumuler chaque matin.

L’hypothèse erronée était subtile : « special, c’est juste des métadonnées ; ça ne va pas beaucoup croître. » Ils ont dimensionné le miroir NVMe
special sur la base d’une règle empirique d’un ancien déploiement metadata-only. Ensuite ils ont mis special_small_blocks=64K pour « être sûrs », car beaucoup
de leurs fichiers faisaient moins de 50KB.

Deux mois plus tard, le special vdev de la ferme de build atteignait une utilisation inconfortable. Le système a commencé à émettre des avertissements d’allocation et des
stalls intermittents. Les développeurs décrivaient cela comme « le stockage fait des crises de panique », ce qui est injuste mais pas totalement inexact.

Le postmortem ne portait pas sur ZFS étant fragile. Il portait sur la responsabilité : personne n’avait une alerte sur la capacité de la classe special.
Ils regardaient l’espace libre global du pool et se sentaient calmes, tandis que le cerveau du pool se vidait doucement.

La correction fut ennuyeuse et efficace : ajouter plus de capacité special (mirror), baisser le seuil pour certains datasets, et
introduire une politique selon laquelle tout dataset activant special_small_blocks doit déclarer un modèle de croissance et un plan d’alerte. La seconde correction
fut la vraie victoire ; la première n’était qu’un paiement.

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

Une équipe de services de fichiers a essayé d’« optimiser tout » dans une même fenêtre de changement : special vdev ajouté, seuil réglé à 32K, compression passée de lz4 à zstd, et
atime modifié — plus une migration massive de données pour réécrire les anciens blocs sur special.

Le retour de bâton n’était pas que zstd soit mauvais ni que special soit risqué. C’était la combinaison de boutons et le timing. La migration a réécrit d’énormes volumes
de petits données et métadonnées. La compression a réduit certains blocs sous le seuil de 32K, ce qui a fait atterrir bien plus de données sur special que prévu. Les snapshots
ont épinglé des blocs supplémentaires. L’amplification d’écritures du mirror special a explosé.

Le symptôme en production était une « lenteur aléatoire ». Pas une panne totale — pire. Un mélange de pics de latence, de longues listes de répertoire, et des plaintes
éparses d’utilisateurs. La surveillance montrait les dispositifs NVMe à haute latence pendant les rafales tandis que les vdevs HDD semblaient presque oisifs.

La récupération fut de l’humilité opérationnelle : arrêter la migration de réécriture, revenir sur le seuil le plus agressif sur les datasets les plus chargés, et relancer la migration
plus tard en lots plus petits avec des limites d’IO explicites. Ils mirent aussi en place un SLO de latence d’écriture pour les dispositifs special. Ce n’est pas une métrique sophistiquée ;
c’est la différence entre « nous savons qu’il est malade » et « on l’apprendra par un VP ».

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

Une autre équipe gérait un store d’artefacts ZFS pour des releases internes. Ils n’utilisèrent les special vdevs que pour les métadonnées au départ et activèrent
special_small_blocks seulement sur un dataset unique utilisé pour index et manifests. Rien d’héroïque : un miroir de SSD entreprise, seuil conservateur (8K),
et une alerte agressive sur l’utilisation special.

Six mois plus tard, un SSD commença à signaler des erreurs média. Le pool passa en degraded, mais rien d’autre ne se produisit — pas de drame, pas de panique.
Ils avaient un runbook : remplacer le périphérique, resilver, vérifier. Ils l’avaient pratiqué en staging avec la même topologie. (Oui, staging. La chose que tout le monde prétend avoir et que personne ne finance.)

Le vrai sauvetage vint d’une chose encore moins excitante : ils avaient des scrubs planifiés et ils examinaient les rapports de scrub.
Le comptage d’erreurs du disque augmenta lentement sur des semaines, et ils le remplacèrent avant que ça devienne une alerte à 3h du matin. Le resilver fut rapide parce que le special était mirroré, petit et sain.

La leçon n’était pas « achetez de meilleurs SSD ». C’était « traitez les special vdevs comme des citoyens de première classe ». Si le cerveau de votre pool y vit, votre maturité opérationnelle doit y vivre aussi.

Métriques opérationnelles pratiques

Choses que j’ai appris à surveiller en environnements réels :

  • Capacité du special vdev (absolue et tendance). Les alertes doivent déclencher tôt.
  • Latence du special vdev (lecture et écriture) pendant les pics connus.
  • Fragmentation du pool et si le comportement de l’allocateur change après des modifications de politique.
  • Rétention des snapshots et datasets à churn élevé épinglant des blocs special.
  • Pression ARC car le cache des métadonnées change vos patterns hit/miss.

Mises à niveau et cycle de vie : planifier le « comment ajouter plus plus tard »

Vous pouvez ajouter des mirrors special vdev supplémentaires pour étendre la capacité special (comme ajouter un autre mirror vdev à la classe special).
Vous ne pouvez pas facilement retirer des special vdevs d’un pool dans la plupart des configurations de production pratiques. C’est une porte sans retour pour de nombreux opérateurs.

Donc votre conception doit supposer la croissance. Si vous pensez « nous n’aurons jamais besoin de plus de 2TB special », vous êtes probablement sur le point d’apprendre quelque chose
sur votre distribution de tailles de fichiers.

Playbook de diagnostic rapide

Quand quelqu’un dit « les petits fichiers sont lents » ou « git checkout rame », vous pouvez perdre une journée en théories. Voici une
triage pratique en trois passes qui trouve rapidement le goulot d’étranglement.

Premier : confirmer que le goulot est le stockage et pas le client

  1. Vérifiez les contraintes côté client : saturation CPU, extraction mono‑thread, antivirus, ou latence réseau.
  2. Sur le serveur, surveillez la charge système et l’I/O wait.
cr0x@server:~$ uptime
 09:41:22 up 102 days,  3:12,  5 users,  load average: 12.11, 11.90, 10.80

cr0x@server:~$ vmstat 1 5
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 8  2      0 812344  91232 9023112   0    0   420  1800 3200 7000 12  6 55 27  0

Interprétation : un wa élevé suggère une latence de stockage. Ce n’est pas une preuve, mais c’est un indice fort.

Second : identifier si special aide ou nuit

  1. Vérifiez la capacité et la santé du special.
  2. Surveillez les IOPS et la latence par vdev pendant l’opération lente.
cr0x@server:~$ sudo zpool status tank | sed -n '1,25p'
  pool: tank
 state: ONLINE
config:
        NAME          STATE     READ WRITE CKSUM
        tank          ONLINE       0     0     0
          raidz2-0    ONLINE       0     0     0
          special
            mirror-1  ONLINE       0     0     0
cr0x@server:~$ sudo zpool list -v tank | tail -n 3
  raidz2-0  54.5T  31.6T  22.9T        -         -    24%    58%
  special    1.8T   1.4T   420G        -         -    18%    77%

Interprétation : special à 77% est un signal d’alarme. Même si la performance semble correcte aujourd’hui, l’allocateur et la croissance future ne seront pas indulgents.

cr0x@server:~$ sudo zpool iostat -v tank 1 10

Interprétation : si special a d’énormes files/ops et une latence en hausse tandis que les HDD sont inoccupés, special est votre goulot
(trop petit, trop lent, ou on lui demande trop).

Troisième : valider la politique du dataset et la réalité de la charge

  1. Confirmez que le dataset en question a les propriétés attendues.
  2. Vérifiez si les écritures sync, les snapshots ou la compression ont changé le comportement.
  3. Cherchez les surprises de type « pas rétroactif » : les anciens blocs toujours sur HDD.
cr0x@server:~$ sudo zfs get -o name,property,value special_small_blocks,compression,atime,xattr,dnodesize -r tank/work
NAME       PROPERTY              VALUE
tank/work  special_small_blocks  16K
tank/work  compression           zstd
tank/work  atime                 off
tank/work  xattr                 sa
tank/work  dnodesize             auto

Interprétation : si le dataset n’a pas la propriété réglée, vous n’utilisez pas la fonctionnalité. S’il l’a, mais que la performance n’a pas changé,
vous pouvez être lié par des écritures sync, le CPU, ou les données n’ont pas été réécrites.

Erreurs courantes, symptômes et corrections

Mistake 1: Adding a non-redundant special vdev

Symptôme : Tout fonctionne jusqu’au jour où ça ne fonctionne plus ; quand le périphérique meurt, le pool peut devenir irrécupérable.

Fix : Toujours mirror (ou RAIDZ) les special vdevs. Si vous avez déjà fait cette erreur, traitez-la comme une dette technique urgente et planifiez une migration vers un pool conçu correctement.
La « correction » in-place n’est pas le type d’aventure excitante que vous voulez.

Mistake 2: Setting special_small_blocks too high “just in case”

Symptôme : La capacité special croît plus vite que prévu ; la performance peut s’améliorer initialement puis se dégrader à mesure que special approche une utilisation élevée.

Fix : Abaissez le seuil pour les nouvelles écritures lorsque c’est approprié, et ajoutez plus de capacité special. Souvenez-vous qu’abaisser la propriété ne déplace pas les blocs existants.
Pour certains datasets, reconstruire (send/receive ou réécriture) est l’approche propre.

Mistake 3: Forgetting that compression can move more data onto special

Symptôme : Après avoir activé zstd ou augmenté le niveau de compression, l’utilisation de special accélère, surtout pour les fichiers « moyens » très compressibles.

Fix : Réévaluez le seuil et la sélection des datasets. Envisagez un seuil plus petit (8K/16K) pour les datasets compressés qui contiennent beaucoup de fichiers modérément grands mais hautement compressibles.

Mistake 4: No alerting on special vdev usage

Symptôme : Le pool a beaucoup d’espace libre, mais les opérations échouent ou la latence augmente ; l’équipe est confuse parce que « le pool n’est rempli qu’à 60% ».

Fix : Mettez en place des alertes sur l’utilisation de la classe special séparément, avec un seuil d’avertissement précoce (par exemple avertir à 60–70%, critique à 80–85%,
adapté à votre taux de croissance et tolérance au risque).

Mistake 5: Assuming enabling the property makes existing data faster

Symptôme : Vous avez réglé special_small_blocks, les utilisateurs ne voient aucune amélioration, et quelqu’un affirme « ça ne marche pas ».

Fix : Expliquez et planifiez la réécriture : déplacez les données vers un nouveau dataset via send/receive, ou réécrivez les fichiers en place (avec prudence).
Validez avec zpool iostat -v pendant les exécutions de charge.

Mistake 6: Overloading special with too many datasets at once

Symptôme : Les dispositifs special montrent une latence d’écriture élevée, tandis que les vdevs principaux semblent sous-utilisés ; ralentissements intermittents lors de tempêtes de métadonnées.

Fix : Priorisez les datasets qui en ont vraiment besoin ; utilisez des seuils conservateurs ; scalez la capacité special avant d’élargir la portée. Traitez-le comme une ressource critique partagée, pas comme une décharge pour chaque souhait de performance.

Mistake 7: Ignoring snapshots and churn

Symptôme : L’utilisation de special augmente même quand « la taille des données » semble stable ; la suppression de fichiers ne libère pas beaucoup d’espace car des snapshots retiennent des blocs.

Fix : Auditez la rétention des snapshots et les datasets à fort churn. Ajustez la politique de snapshots ou déplacez les workloads à fort churn vers des pools/datasets séparés avec une rétention adaptée.

Listes de contrôle / plan pas à pas

Checklist A: Decide whether special small blocks is the right tool

  1. Confirmez que la charge est dominée par métadonnées/petits fichiers (lent find, lent stat, lente extraction untar).
  2. Confirmez que le goulot est la latence/IOPS de stockage, pas le réseau ou le CPU.
  3. Mesurez la distribution des tailles de fichiers et le nombre d’objets (approximatif suffit, mais mesurez).
  4. Décidez quels datasets en ont vraiment besoin (ne l’activez pas globalement).

Checklist B: Design the special vdev safely

  1. Choisissez une topologie redondante (mirror minimum).
  2. Choisissez des dispositifs avec endurance appropriée et comportement d’alimentation stable pour votre environnement.
  3. Dimensionnez pour métadonnées + petits blocs attendus + overhead snapshots + croissance.
  4. Planifiez l’expansion : laissez des baies/emplacements ou des lanes PCIe, et budgetez l’ajout d’un autre mirror vdev plus tard.
  5. Créez des alertes spécifiques pour l’utilisation special et les taux d’erreurs des dispositifs.

Checklist C: Rollout plan that won’t wake you up at 3 a.m.

  1. Ajoutez le special vdev et vérifiez la santé du pool.
  2. Activez sur un dataset d’abord avec un seuil conservateur (8K ou 16K).
  3. Mesurez avant/après sur des opérations réelles (checkout, untar, indexation, scans de répertoire).
  4. Réécrivez les données seulement si nécessaire, et limitez le débit des migrations.
  5. Étendez le périmètre dataset par dataset en surveillant la tendance d’utilisation special.

Checklist D: Ongoing operations

  1. Revue hebdomadaire de la tendance d’utilisation special et de la croissance des snapshots.
  2. Scrubs planifiés et rapports de scrub examinés.
  3. Vérifications santé des dispositifs (SMART/NVMe logs) intégrées aux seuils d’alerte.
  4. Validation trimestrielle : tester une procédure de remplacement de périphérique et de resilver (en environnement sûr).

Extra practical tasks (ops-focused)

Si vous voulez des tâches pratiques au‑delà des douze de base, celles-ci rapportent souvent rapidement.

Task 15: Check ARC stats (metadata caching pressure)

cr0x@server:~$ sudo arcstat 1 5
    time  read  miss  miss%  dmis  dm%  pmis  pm%  mmis  mm%  arcsz     c
09:52:01   812   102     12    48   47    40   39    14   14   96G   110G

Interprétation : si le taux de miss ARC augmente pendant les tempêtes de répertoire, le special vdev sera plus sollicité. C’est acceptable si
c’est dimensionné et rapide. Sinon, vous verrez de la latence.

Task 16: Quick check of mountpoint and dataset mapping (avoid tuning the wrong dataset)

cr0x@server:~$ mount | grep tank
tank/work on /tank/work type zfs (rw,xattr,noacl)

cr0x@server:~$ sudo zfs list -o name,mountpoint -r tank | grep -E '^tank/work|/tank/work'
tank/work  /tank/work

Interprétation : dans les vraies boîtes, « le chemin » et « le dataset » dérivent avec le temps. Réglez le dataset qui sert réellement le chemin utilisé par les utilisateurs.

Task 17: Check snapshot count and retention pressure

cr0x@server:~$ sudo zfs list -t snapshot -o name,used,creation -S creation | head
NAME                      USED  CREATION
tank/work@daily-2025-12-24 1.2G  Wed Dec 24 02:00 2025
tank/work@daily-2025-12-23 1.1G  Tue Dec 23 02:00 2025
tank/work@daily-2025-12-22 1.3G  Mon Dec 22 02:00 2025

Interprétation : les snapshots ne sont pas « gratuits ». Sur des datasets churny, les snapshots épinglent d’anciens petits blocs et métadonnées.

FAQ

1) Is a special vdev the same thing as L2ARC?

Non. L2ARC est un cache et peut être supprimé et reconstruit. Le special vdev stocke de vrais blocs (métadonnées et éventuellement petites données de fichier).
Si le special est perdu sans redondance, le pool peut être perdu.

2) Do I need special small blocks if I already have lots of RAM for ARC?

Peut-être. L’ARC aide si votre working set tient dedans et le pattern d’accès est favorable au cache. Mais les charges de petits fichiers dépassent souvent l’ARC
ou impliquent des scans froids (pensez aux runners CI qui démarrent à zéro). Le special vdev améliore la pénalité de miss en faisant atterrir les misses sur SSD/NVMe
plutôt que sur HDD.

3) What’s a good starting value for special_small_blocks?

8K ou 16K est un point de départ sensé pour de nombreux environnements. Si vous sautez à 64K, vous vous engagez à stocker beaucoup de données réelles sur special.
Cela peut être correct, mais prenez cette décision avec des calculs de dimensionnement, pas des impressions.

4) Can I enable it on the whole pool at once?

Techniquement vous pouvez le définir largement sur des datasets, mais opérationnellement vous ne devriez pas. Déployez dataset par dataset, mesurez,
et surveillez la tendance d’utilisation special. Special est une ressource partagée ; un dataset bruyant peut la consommer.

5) Why didn’t performance improve after I set the property?

Raisons courantes : vos données n’ont pas été réécrites (la propriété n’est pas rétroactive), votre goulot est des écritures sync ou le réseau,
la charge est CPU‑bound, ou le special est saturé/lent. Validez avec zpool iostat -v pendant la charge.

6) What happens if the special vdev fills up?

Des choses mauvaises de façon ennuyeuse : des allocations peuvent échouer pour les métadonnées et petits blocs, et le pool peut devenir instable ou commencer à retourner des erreurs.
Traitez « special approche du plein » comme une condition sérieuse — ajoutez de la capacité ou réduisez les facteurs de croissance avant que ça ne devienne un incident de production.

7) Can I remove a special vdev later?

Dans de nombreuses configurations réelles, retirer des special vdevs n’est pas simple ou pas pris en charge comme les gens l’imaginent.
Planifiez comme si c’était permanent. Si vous avez besoin de réversibilité, envisagez de construire un nouveau pool et de migrer.

8) Should I also move metadata to SSD by using a “metadata-only” pool layout?

Les special vdevs sont exactement ce concept, intégré aux classes d’allocation ZFS. Si votre pool est entièrement SSD, les special vdevs n’apporteront pas beaucoup.
Si votre pool est basé HDD, special peut être un gros gain sans déplacer toutes les données sur SSD.

9) Does xattr=sa matter for this topic?

Souvent oui. Quand les xattrs vivent dans la zone d’attributs système, ZFS peut éviter des objets xattr séparés dans beaucoup de cas,
réduisant les E/S de métadonnées. Cela accélère les scans de répertoire et les workloads lourds en attributs et peut réduire la pression sur la capacité et les IOPS du special.

10) Should I use RAIDZ for the special vdev instead of mirrors?

Les mirrors sont courants pour special parce qu’ils gardent la latence basse et simplifient le rebuild. RAIDZ peut être viable pour l’efficacité de capacité,
mais vous devez accepter l’amplification d’écritures et la dynamique de rebuild. Pour la plupart des workloads sensibles à la latence sur petits fichiers,
les mirrors special sont le choix sûr par défaut.

Conclusion

Les special vdevs ZFS et special_small_blocks sont l’un des moyens les plus efficaces de changer la sensation d’un système de fichiers qui se noie dans les petits fichiers.
Ils ne se contentent pas de « rendre plus rapide » — ils déplacent la partie la plus difficile de la charge (métadonnées et petites E/S aléatoires) vers un média qui peut réellement le faire.

Mais ce n’est pas un déjeuner gratuit, et ce n’est pas un cache que vous pouvez balayer d’un revers de main. Le special vdev devient une infrastructure critique : il nécessite
redondance, surveillance, planification de capacité et discipline opérationnelle. Si vous le traitez comme un simple hack de performance, il finira par vous traiter comme quelqu’un qui aime lire les logs du noyau à minuit.

Commencez conservateur, mesurez honnêtement, et étendez avec intention. C’est comme ça que vous obtenez le turbo sans le joint de culasse fissuré.

← Précédent
Docker multi-hôtes sans Kubernetes : options réelles et limites strictes
Suivant →
Docker AppArmor et seccomp : le renforcement minimal qui compte

Laisser un commentaire