Benchmark ZFS : les règles pour éviter les résultats faux

Cet article vous a aidé ?

On vous tend une capture d’écran : « Notre nouveau serveur ZFS fait 6 GB/s ! » Vous posez une question—« Depuis où ? »—et la pièce se tait. Parce que le dispositif de stockage le plus rapide du datacenter est celui que vous n’aviez pas voulu tester : la RAM. Le deuxième plus rapide est celui que vous aviez oublié d’activer : la compression.

Les benchmarks servent à financer, approuver et ensuite accuser des projets de stockage. En production, « presque juste » revient à faux, juste avec de jolis graphiques. Voici un ensemble de règles et de méthodes de terrain pour benchmarker ZFS sans vous mentir à vous-même, à votre chef ou au prochain intervenant de garde.

Ce que vous mesurez réellement (et ce que vous ne mesurez pas)

Le benchmarking ZFS échoue pour une raison centrale : les gens benchmarkent le « stockage » comme si c’était une seule chose. Ce n’est pas le cas. Vous testez une chaîne : application → libc → kernel VFS → ZFS DMU → ZIO scheduler → files de vdev → disques/flash → contrôleur → firmware. Ajoutez ARC, L2ARC, SLOG, compression, checksums et métadonnées, et vous benchmarkez un système avec plus de pièces mobiles qu’une réorganisation d’entreprise.

Il y a trois chiffres qui comptent :

  • Débit (MB/s ou GB/s) : vitesse de lecture/écriture en masse. Idéal pour sauvegardes, médias, scans séquentiels — et pour enjoliver des slides.
  • IOPS : opérations par seconde. Pertinent pour petits blocs, accès aléatoires, métadonnées et bases de données réelles.
  • Latence (surtout p95/p99) : le seul chiffre que vos utilisateurs ressentent. ZFS peut fournir un haut débit tout en massacrant tranquillement la latence en queue de distribution.

Un benchmark qui produit un seul chiffre et pas de distribution de latence, c’est un test d’ambiance, pas de l’ingénierie.

Le benchmarking, c’est de l’inférence, pas une mesure brute

Le stockage est difficile à « mesurer » directement parce que le système s’adapte. L’ARC chauffe. Les transaction groups (TXG) regroupent le travail. Le prefetch et le readahead de ZFS modifient les motifs. La compression modifie les octets physiques. Si vous ne contrôlez pas ces variables, vous ne testez pas le pool — vous testez ce que ZFS a décidé de faire pendant que vous regardiez.

Une règle sèche : si vous ne pouvez pas expliquer pourquoi un résultat s’est amélioré, il ne s’est pas amélioré. Vous avez juste eu de la chance.

Quelques faits et un peu d’histoire qui changent la manière de benchmarker

Un peu de contexte vous aide à prédire le comportement de ZFS dans les tests. Voici des faits concrets qui comptent en labo et en production :

  1. ZFS a été conçu avec l’intégrité de données de bout en bout comme objectif principal, pas comme un add-on. Checksums, copy-on-write et auto-réparation influencent l’amplification d’écriture et la latence.
  2. Les travaux initiaux de ZFS chez Sun visaient à rendre les disques commodity « assez entreprise ». Le but était de survivre à du matériel capricieux grâce à du logiciel intelligent. Les benchmarks qui ignorent les options d’intégrité manquent la finalité.
  3. L’ARC n’est pas juste un cache, c’est un moteur de politique mémoire. Il concurrence votre application pour la RAM, et son comportement change selon la charge. Benchmarker sans contrôler l’ARC, c’est benchmarker la gestion de mémoire.
  4. Copy-on-write signifie qu’un « overwrite » est en réalité allocate-new + mise à jour des métadonnées. Les réécritures aléatoires se comportent comme des écritures aléatoires plus du churn de métadonnées. Les systèmes de fichiers qui écrivent en place peuvent sembler « plus rapides » sur certains motifs, jusqu’à corrompre les données.
  5. Le batching TXG explique pourquoi les tests courts mentent. ZFS groupe les écritures en TXG et les commite périodiquement. Si vous testez 10 secondes, vous pourriez ne jamais voir le comportement en état stable.
  6. Le SLOG existe parce que les écritures synchrones sont coûteuses. Le ZIL est toujours présent ; le SLOG est le dispositif séparé qui peut accélérer les écritures sync. Si vous ne testez jamais le comportement sync, vous benchmarkez le mauvais risque.
  7. Recordsize est un contrat de performance. ZFS peut stocker de gros records efficacement pour des charges séquentielles, mais de gros records peuvent punir les petites écritures aléatoires via un pattern read-modify-write.
  8. L’alignement des secteurs (ashift) est pour toujours. Si vous choisissez mal, chaque écriture peut devenir deux écritures. On peut corriger beaucoup de choses dans ZFS. On ne peut pas « tuner » une mauvaise ashift sans reconstruire.
  9. La compression rend souvent ZFS « plus rapide » en effectuant moins d’I/O. Ce n’est pas de la triche — sauf si vos données de production ne compressent pas. Alors c’est l’équivalent benchmark d’une descente en luge.

Une citation pour rester honnête :

“Hope is not a strategy.” — General Gordon R. Sullivan

Les règles : comment éviter les faux résultats ZFS

Règle 1 : Formulez votre question avant d’exécuter une commande

« Quelle est la vitesse de ce pool ? » n’est pas une question. Posez quelque chose de testable :

  • Quel est le IOPS soutenu en lecture aléatoire à 4k, avec p99 de latence < 5 ms ?
  • Quel est le débit séquentiel en écriture pour des blocs de 1M avec compression désactivée ?
  • Quelle est la latence d’écriture sync avec et sans SLOG sous une concurrence de 16 ?
  • Changer recordsize de 128K à 16K améliore-t-il la latence tail des écritures de la base de données ?

Écrivez la question. Si vous ne pouvez pas la formuler, vous ne pouvez pas interpréter le résultat.

Règle 2 : Choisissez la bonne surface de test : dataset fichier vs zvol

Tester un dataset fichier fait passer par le chemin filesystem, les métadonnées et le recordsize. Tester un zvol exerce un périphérique bloc, volblocksize et un profil d’I/O différent. Les bases de données sur périphériques bruts (ou iSCSI) se comportent différemment que sur fichiers. Ne benchmarkez pas la mauvaise interface puis n’« optimisez » pas en vous basant dessus.

Règle 3 : Contrôlez le caching ou mesurez-le explicitement

L’ARC peut rendre les lectures surnaturelles. C’est correct si votre working set de production tient dans la RAM. Sinon, les benchmarks de lecture propulsés par l’ARC sont de la fan fiction performancielle.

Décidez ce que vous testez :

  • Performance cold-cache : ce qui se passe après un reboot ou après avoir vidé le cache, et quand le working set >> RAM.
  • Performance warm-cache : la réalité à l’état chaud d’un dataset. Utile, mais ne la confondez pas avec la performance disque.

Deuxième règle sèche : si vous ne mentionnez pas l’état du cache dans votre rapport, les chiffres ne sont pas recevables.

Règle 4 : Les écritures synchrones sont leur propre univers

La majorité des douleurs en production provient des écritures sync : bases de données avec fsync, NFS avec sémantique sync, stockage VM qui tient à la durabilité. Les benchmarks async peuvent paraître glorieux alors que votre charge réelle s’effondre parce qu’elle exige la durabilité.

Benchmarkez le sync explicitement et incluez les percentiles de latence. Si vous avez un SLOG, testez avec et sans. Faites afficher au système le vrai coût de la durabilité.

Règle 5 : Utilisez l’état stable ; les tests courts mentent

Le comportement de ZFS change dans le temps : l’ARC chauffe, les metaslabs s’allouent, la fragmentation croît et les TXG se stabilisent. Votre benchmark doit durer suffisamment longtemps pour atteindre l’état stable et capturer la latence tail. À titre indicatif :

  • Tests de débit : 2–5 minutes par configuration, après un court warm-up.
  • Tests de latence aléatoire : au moins 5 minutes, collecter p95/p99/p999.
  • Comparaisons recordsize / compression : répétez les exécutions et comparez des distributions, pas seulement les moyennes.

Règle 6 : Éliminez les variables « utiles »

Scaling de fréquence CPU, scrubs en arrière-plan, resilvers, tests SMART et voisins bruyants vont tous contaminer les résultats. Les benchmarks ne sont pas un moment pour « partager la machine » poliment. Soyez impoli avec le reste du système. Vous pourrez vous excuser plus tard avec de l’uptime.

Règle 7 : Ne benchmarkez pas des pools vides et appelez ça la production

La fragmentation et le comportement d’allocation de metaslab changent à mesure que les pools se remplissent. La performance à 5 % plein peut être excellente ; la performance à 80 % plein peut être une autre personnalité. Si la production vous importe, testez à un niveau de remplissage réaliste ou simulez-le au moins avec de la préallocation et du churn de fichiers.

Règle 8 : Validez que votre outil réalise réellement de l’I/O

Les outils peuvent vous tromper. Un fio mal configuré peut benchmarker le page cache. dd peut benchmarker le readahead et les effets de cache. Votre monitoring peut regarder le mauvais périphérique.

Faites confiance mais vérifiez : pendant tout test, confirmez que les disques montrent de l’I/O réelle et que les compteurs ZFS bougent.

Règle 9 : Mesurez à plusieurs couches

Un seul chiffre est un piège. Capturez toujours au moins :

  • Niveau application (sortie fio : IOPS, bw, clat percentiles)
  • Niveau ZFS (zpool iostat, arcstat)
  • Niveau périphérique (iostat -x : util, await, signaux svctm-ish)
  • CPU et interruptions (mpstat, top)

Quand les résultats semblent « trop bons », une de ces couches sera suspecteusement silencieuse.

Règle 10 : Ne changez qu’une chose à la fois

Le benchmarking n’est pas un festival d’ajustements. Si vous changez recordsize, compression, sync et atime simultanément, vous faites de la génération de nombres aléatoires avec des étapes supplémentaires.

Blague #1 : Le benchmarking, c’est comme cuisiner — si vous changez le four, la recette et le chef, ne vous vantez pas du soufflé.

Adapter la charge : fichiers, blocs, sync et latence

Archétypes de charges qui comptent

La plupart des cas d’usage réels de ZFS entrent dans quelques catégories. Votre benchmark doit en reproduire une :

  • Lectures séquentielles en masse : sauvegardes, médias, scans analytiques. Favorisez de gros blocs, le débit et les effets de prefetch.
  • Écritures séquentielles en masse : pipelines d’ingestion, cibles de sauvegarde. Surveillez le comportement TXG et le débit soutenu après saturation des caches.
  • Lectures aléatoires : key-value stores, orages de démarrage VM. L’ARC peut dominer ici ; les tests cold-cache importent.
  • Écritures aléatoires (async) : logging, stockages temporaires. Souvent ça a l’air correct jusqu’à ce que le sync arrive.
  • Écritures aléatoires (sync) : bases de données, NFS sync, datastores VM sensibles aux durabilités. Latence et comportement SLOG sont cruciaux.
  • Charges riches en métadonnées : serveurs git, maildirs, arbres d’artefacts CI. Petits fichiers, opérations sur répertoires et vdev spécial peuvent compter.

Les propriétés du dataset ne sont pas des « boutons de tuning », ce sont des contrats de charge

recordsize, compression, atime, primarycache, logbias et sync sont des décisions sur l’amplification d’écriture, la priorité de cache et la sémantique de durabilité. Les benchmarker sans comprendre la charge, c’est créer un système qui gagne des benchmarks et perd des incidents.

Exemple : définir recordsize=1M sur un dataset fournissant une base de données peut gonfler l’overhead read-modify-write pour des updates de 8K. Vous verrez peut-être un excellent débit séquentiel, puis une explosion de latence quand la base effectue des updates aléatoires. Le benchmark était « correct ». La question était mauvaise.

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

Voici des tâches réelles à exécuter sur un hôte ZFS. Chaque tâche inclut : la commande, ce que la sortie signifie et la décision à prendre. Le but n’est pas de collectionner des trivia ; c’est de vous empêcher de faire confiance à un chiffre menteur.

Tâche 1 : Confirmer la topologie du pool et ashift (le réglage « pour toujours »)

cr0x@server:~$ sudo zpool status -v tank
  pool: tank
 state: ONLINE
  scan: scrub repaired 0B in 0 days 00:12:41 with 0 errors on Tue Dec 24 03:11:03 2025
config:

        NAME                        STATE     READ WRITE CKSUM
        tank                        ONLINE       0     0     0
          raidz2-0                  ONLINE       0     0     0
            ata-SAMSUNG_SSD_1       ONLINE       0     0     0
            ata-SAMSUNG_SSD_2       ONLINE       0     0     0
            ata-SAMSUNG_SSD_3       ONLINE       0     0     0
            ata-SAMSUNG_SSD_4       ONLINE       0     0     0
            ata-SAMSUNG_SSD_5       ONLINE       0     0     0
            ata-SAMSUNG_SSD_6       ONLINE       0     0     0

errors: No known data errors
cr0x@server:~$ sudo zdb -C tank | grep -E 'ashift|vdev_tree' -n | head
67:        vdev_tree:
92:            ashift: 12

Sens : ashift=12 implique des secteurs 4K. ashift=9 implique 512B, et sur des disques 4K-native modernes cela peut causer de l’amplification d’écriture et une latence terrible.

Décision : Si ashift est erroné pour votre média, arrêtez de « tuner ». Planifiez une reconstruction ou une migration. Tout le reste, c’est du maquillage.

Tâche 2 : Capturer les propriétés du dataset qui affectent les benchmarks

cr0x@server:~$ sudo zfs get -o name,property,value -s local,default recordsize,compression,atime,sync,primarycache,logbias tank/test
NAME       PROPERTY      VALUE
tank/test  recordsize    128K
tank/test  compression   off
tank/test  atime         off
tank/test  sync          standard
tank/test  primarycache  all
tank/test  logbias       latency

Sens : Vous savez maintenant si la « performance » venait de la compression, si le sync était désactivé ou si le cache était restreint.

Décision : Gèle ces paramètres pour la session de benchmark. Tout changement nécessite une nouvelle exécution et une raison claire.

Tâche 3 : Vérifier que vous ne benchmarkez pas accidentellement le page cache

cr0x@server:~$ free -h
               total        used        free      shared  buff/cache   available
Mem:            256G         18G        190G        1.2G         48G        235G
Swap:             0B          0B          0B
cr0x@server:~$ sudo arcstat.py 1 3
    time  read  miss  miss%  dmis  dm%  pmis  pm%  mmis  mm%  arcsz     c
12:01:01   219     3      1     0    0     3  100     0    0   18G   190G
12:01:02   241     2      0     0    0     2  100     0    0   18G   190G
12:01:03   210     4      1     0    0     4  100     0    0   18G   190G

Sens : La taille ARC (arcsz) et les taux hit/miss du cache sont visibles. Si votre « benchmark de lecture disque » montre des misses quasi nuls, félicitations — vous avez benchmarké la RAM.

Décision : Si vous avez besoin de chiffres cold-cache, soit redémarrez entre les runs, utilisez un working set plus grand que l’ARC, ou définissez primarycache=metadata sur le dataset de test (avec prudence).

Tâche 4 : Confirmer qu’il y a de l’I/O réel sur les périphériques pendant un run

cr0x@server:~$ sudo zpool iostat -v tank 1 3
                                            capacity     operations     bandwidth
pool                                      alloc   free   read  write   read  write
----------------------------------------  -----  -----  -----  -----  -----  -----
tank                                       3.12T  8.77T    120    980  15.0M  980M
  raidz2-0                                 3.12T  8.77T    120    980  15.0M  980M
    ata-SAMSUNG_SSD_1                          -      -     20    165  2.6M  170M
    ata-SAMSUNG_SSD_2                          -      -     19    166  2.4M  169M
    ata-SAMSUNG_SSD_3                          -      -     21    162  2.7M  168M
    ata-SAMSUNG_SSD_4                          -      -     20    161  2.5M  166M
    ata-SAMSUNG_SSD_5                          -      -     20    164  2.4M  169M
    ata-SAMSUNG_SSD_6                          -      -     20    162  2.4M  168M
----------------------------------------  -----  -----  -----  -----  -----  -----

Sens : Vous voyez les opérations et le débit par vdev. Si fio dit « 3 GB/s » et zpool iostat affiche 0 MB/s, fio tape le cache ou ne fait rien.

Décision : Faites confiance à zpool iostat pour valider que votre test exerce le pool.

Tâche 5 : Vérifier la présence de travaux en arrière-plan qui vont empoisonner les résultats

cr0x@server:~$ sudo zpool status tank | sed -n '1,25p'
  pool: tank
 state: ONLINE
  scan: scrub in progress since Wed Dec 25 11:48:03 2025
        1.11T scanned at 4.52G/s, 220G issued at 898M/s, 3.12T total
        0B repaired, 7.03% done, 00:53:12 to go

Sens : Un scrub est en cours. Vos résultats de benchmark sont maintenant un duo : votre charge plus l’I/O du scrub.

Décision : Suspendez les benchmarks. Attendez, planifiez les tests en dehors des fenêtres de scrub, ou arrêtez-le temporairement si la politique le permet.

Tâche 6 : Établir un test de référence d’écriture séquentielle (fichiers)

cr0x@server:~$ sudo zfs create -o compression=off -o atime=off tank/bench
cr0x@server:~$ fio --name=seqwrite --directory=/tank/bench --rw=write --bs=1M --size=40G --numjobs=1 --iodepth=16 --direct=1 --time_based=1 --runtime=120 --group_reporting
seqwrite: (groupid=0, jobs=1): err= 0: pid=24819: Wed Dec 25 12:03:44 2025
  write: IOPS=980, BW=981MiB/s (1029MB/s)(115GiB/120001msec)
    clat (usec): min=320, max=18900, avg=2450.11, stdev=702.41
    lat (usec): min=340, max=18940, avg=2475.88, stdev=704.13

Sens : direct=1 évite le page cache. C’est une référence de débit, mais encore sujette aux métadonnées ARC et au comportement ZFS. La latence montre la distribution ; les pics max comptent.

Décision : Utilisez cette référence pour comparer des changements (compression, recordsize, layout vdev). Ne la traitez pas comme la « vitesse universelle du pool ».

Tâche 7 : IOPS lecture aléatoire avec latence tail (froid-ish vs chaud)

cr0x@server:~$ fio --name=randread4k --directory=/tank/bench --rw=randread --bs=4k --size=40G --numjobs=4 --iodepth=32 --direct=1 --time_based=1 --runtime=180 --group_reporting --lat_percentiles=1
randread4k: (groupid=0, jobs=4): err= 0: pid=24910: Wed Dec 25 12:09:12 2025
  read: IOPS=182k, BW=712MiB/s (747MB/s)(125GiB/180002msec)
    clat percentiles (usec):
     |  1.00th=[  118],  5.00th=[  128], 10.00th=[  136], 50.00th=[  176]
     | 90.00th=[  260], 95.00th=[  310], 99.00th=[  560], 99.90th=[ 1400]

Sens : Si c’est « cold cache », ces chiffres sont étonnants et l’ARC est probablement impliqué. Si c’est warm cache, cela peut être réaliste pour une charge de lecture qui tient en mémoire.

Décision : Répétez avec un working set beaucoup plus grand que l’ARC si vous avez besoin d’un comportement limité par disque. Comparez les percentiles, pas seulement les IOPS.

Tâche 8 : Test d’écriture sync (celui qui ruine les plans heureux)

cr0x@server:~$ sudo zfs set sync=always tank/bench
cr0x@server:~$ fio --name=syncwrite4k --directory=/tank/bench --rw=randwrite --bs=4k --size=10G --numjobs=4 --iodepth=8 --direct=1 --time_based=1 --runtime=180 --group_reporting --lat_percentiles=1
syncwrite4k: (groupid=0, jobs=4): err= 0: pid=25103: Wed Dec 25 12:15:41 2025
  write: IOPS=9800, BW=38.3MiB/s (40.2MB/s)(6.73GiB/180002msec)
    clat percentiles (usec):
     | 50.00th=[  780], 90.00th=[ 1900], 95.00th=[ 2600], 99.00th=[ 6800]
     | 99.90th=[22000]

Sens : Ceci reflète le coût de la durabilité. Si vous vous attendiez à 100k IOPS parce que la fiche technique du SSD le disait, bienvenue dans la différence entre la vitesse NAND et la sémantique du système.

Décision : Si p99/p999 est trop élevé, évaluez la qualité du SLOG, la mise en file d’attente et la saturation CPU. Considérez aussi si la charge nécessite vraiment sync=always ou si la durabilité côté application est déjà correcte.

Tâche 9 : Vérifier si SLOG est présent et réellement utilisé

cr0x@server:~$ sudo zpool status tank | sed -n '1,80p'
  pool: tank
 state: ONLINE
config:

        NAME                        STATE     READ WRITE CKSUM
        tank                        ONLINE       0     0     0
          raidz2-0                  ONLINE       0     0     0
            ata-SAMSUNG_SSD_1       ONLINE       0     0     0
            ata-SAMSUNG_SSD_2       ONLINE       0     0     0
            ata-SAMSUNG_SSD_3       ONLINE       0     0     0
            ata-SAMSUNG_SSD_4       ONLINE       0     0     0
            ata-SAMSUNG_SSD_5       ONLINE       0     0     0
            ata-SAMSUNG_SSD_6       ONLINE       0     0     0
        logs
          nvme-INTEL_SLOG0          ONLINE       0     0     0
cr0x@server:~$ sudo zpool iostat -v tank 1 3
                                            capacity     operations     bandwidth
pool                                      alloc   free   read  write   read  write
----------------------------------------  -----  -----  -----  -----  -----  -----
tank                                       3.15T  8.74T     10   1200  1.2M  120M
  raidz2-0                                 3.15T  8.74T     10    900  1.2M   90M
  logs                                         -      -      0    300    0   30M
    nvme-INTEL_SLOG0                           -      -      0    300    0   30M
----------------------------------------  -----  -----  -----  -----  -----  -----

Sens : Pendant un travail riche en sync, le vdev de log doit montrer des écritures. S’il reste inactif, soit votre charge n’émet pas d’écritures sync, soit le SLOG est mal appliqué, soit vous ne testez pas ce que vous pensez tester.

Décision : Si la performance sync est critique et que le SLOG n’est pas engagé, corrigez le test ou la configuration avant de blâmer le pool.

Tâche 10 : Identifier les goulets CPU et les effets de compression

cr0x@server:~$ mpstat -P ALL 1 3
Linux 6.8.0 (server) 	12/25/2025 	_x86_64_	(64 CPU)

12:19:01 AM  CPU   %usr %nice %sys %iowait  %irq %soft  %steal  %guest  %gnice  %idle
12:19:02 AM  all   18.2  0.0  22.9    0.4   0.0   2.1    0.0     0.0     0.0   56.4
12:19:03 AM  all   19.0  0.0  24.1    0.5   0.0   2.2    0.0     0.0     0.0   54.2
cr0x@server:~$ sudo zfs set compression=zstd tank/bench
cr0x@server:~$ fio --name=seqwrite-cmpr --directory=/tank/bench --rw=write --bs=1M --size=40G --numjobs=1 --iodepth=16 --direct=1 --time_based=1 --runtime=120 --group_reporting
seqwrite-cmpr: (groupid=0, jobs=1): err= 0: pid=25218: Wed Dec 25 12:20:44 2025
  write: IOPS=1500, BW=1501MiB/s (1574MB/s)(176GiB/120001msec)
    clat (usec): min=350, max=24100, avg=2600.88, stdev=880.03

Sens : Le débit s’est amélioré avec la compression. C’est légitime si vos données de production compressent de la même manière. L’utilisation CPU peut augmenter ; la latence tail peut se modifier.

Décision : Validez la compressibilité avec des échantillons de données réelles (ou synthétiques représentatifs). Si les données de production sont déjà compressées (médias, blobs chiffrés), ce benchmark est sans objet.

Tâche 11 : Vérifier le ratio de compression réel sur disque

cr0x@server:~$ sudo zfs get -o name,property,value compressratio,logicalused,used tank/bench
NAME       PROPERTY       VALUE
tank/bench compressratio  1.72x
tank/bench logicalused    180G
tank/bench used           105G

Sens : compressratio montre ce qui s’est passé, pas ce que vous espériez. 1.72x signifie que vous avez écrit moins de données physiques que logiques.

Décision : Si compressratio est proche de 1.00x sur des données représentatives, arrêtez de compter sur la compression comme plan de performance.

Tâche 12 : Inspecter la saturation périphérique et les files d’attente

cr0x@server:~$ iostat -x 1 3
Linux 6.8.0 (server) 	12/25/2025 	_x86_64_	(64 CPU)

Device            r/s     w/s   rkB/s   wkB/s  rrqm/s  wrqm/s  %util  await
nvme0n1           0.0   300.0     0.0  30720.0    0.0     0.0   6.5    0.41
sda              25.0   165.0  2560.0 168960.0    0.0     1.0  78.2    2.90
sdb              24.0   166.0  2450.0 169100.0    0.0     1.0  79.0    2.88
sdc              25.0   162.0  2600.0 168100.0    0.0     1.0  77.5    2.95

Sens : %util proche de 100 % avec await en hausse indique que le périphérique est saturé. Si les périphériques ne sont pas occupés mais la latence est élevée, le goulet est ailleurs (CPU, verrous, sync, réseau).

Décision : Si les disques sont saturés, optimisez le layout ou ajoutez des vdevs. Si les disques sont inactifs, arrêtez d’acheter des disques et commencez à profiler la pile.

Tâche 13 : Confirmer que le test n’est pas limité par un seul fichier ou un petit chemin métadonnées

cr0x@server:~$ fio --name=randread4k-many --directory=/tank/bench --rw=randread --bs=4k --size=40G --numjobs=8 --iodepth=32 --direct=1 --time_based=1 --runtime=180 --group_reporting --filename_format=job.$jobnum.file
randread4k-many: (groupid=0, jobs=8): err= 0: pid=25440: Wed Dec 25 12:28:11 2025
  read: IOPS=240k, BW=938MiB/s (984MB/s)(165GiB/180004msec)
    clat (usec): min=95, max=3200, avg=210.34, stdev=81.22

Sens : Plusieurs fichiers réduisent la contention de verrou sur un seul fichier et peuvent exposer du parallélisme. Si la performance augmente massivement, votre test précédent a peut-être benchmarké un goulet que vous n’avez pas en production — ou un goulet que vous avez absolument.

Décision : Choisissez le nombre de fichiers pour correspondre à la réalité : beaucoup de fichiers pour images VM ou DB sharded ; moins pour de gros logs ; zvol pour des charges bloc.

Tâche 14 : Surveiller la latence ZFS sous charge (sanity rapide)

cr0x@server:~$ sudo zpool iostat -r -w tank 1 3
               read                            write
pool   ops  bandwidth  total_wait  disk_wait  ops  bandwidth  total_wait  disk_wait
tank   120     15.0M       1ms         1ms   980     980M       4ms         3ms
tank   110     14.2M       1ms         1ms   995     990M       5ms         4ms
tank   118     15.1M       2ms         1ms   970     970M       4ms         3ms

Sens : total_wait inclut le temps dans les queues ZFS ; disk_wait est le temps sur le périphérique. Si total_wait est beaucoup plus grand que disk_wait, le goulet peut être dans la planification ZFS, le CPU, la contention ou le chemin sync.

Décision : Utilisez la différence (total_wait – disk_wait) comme indice : attendez-vous sur les disques ou sur le logiciel ?

Tâche 15 : Vérifier l’espace libre du pool et le risque de fragmentation

cr0x@server:~$ zfs list -o name,used,avail,refer,mountpoint tank
NAME   USED  AVAIL  REFER  MOUNTPOINT
tank  3.15T  8.74T   128K  /tank
cr0x@server:~$ sudo zpool list -o name,size,alloc,free,fragmentation,capacity
NAME  SIZE   ALLOC  FREE   FRAG  CAP
tank  11.9T  3.15T  8.74T   18%  26%

Sens : Fragmentation et capacité importent. À mesure que les pools se remplissent et se fragmentent, l’allocation devient plus complexe et la latence peut augmenter.

Décision : Si vous benchmarkez à 10–30 % plein et que la production tourne à 80 %, vous benchmarkez une machine différente. Planifiez des tests à l’occupation réaliste.

Playbook de diagnostic rapide

Quand un résultat de benchmark semble faux — ou que la production est lente et que vous essayez de le reproduire — ne commencez pas à changer des propriétés ZFS au hasard. Faites ceci à la place, dans l’ordre. Rapide. Déterministe. Ennuyeux.

Premier : confirmez que le test est de l’I/O réelle, pas du théâtre cache

  1. Vérifiez zpool iostat pendant l’exécution du benchmark. Si le bandwidth du pool est proche de zéro, arrêtez.
  2. Vérifiez les taux de miss avec arcstat. Si les misses sont bas, vous testez le comportement ARC.
  3. Vérifiez fio direct=1 et confirmez que la taille des fichiers dépasse la RAM si le cold-cache est requis.

Second : déterminez si le goulet est disque, CPU ou sémantique sync

  1. Exécutez iostat -x. Si %util est élevé et await augmente, les périphériques sont saturés.
  2. Exécutez mpstat. Si %sys est élevé et que les CPUs sont occupés alors que les disques ne le sont pas, vous êtes lié par le CPU (checksums, compression, interruptions, verrous).
  3. Forcez le comportement sync sur le dataset (sync=always) pour un court test. Si la performance s’effondre et que la latence explose, votre goulet réel est le chemin sync et/ou le SLOG.

Troisième : validez les hypothèses de configuration ZFS

  1. Vérifiez la topologie (mirror/raidz) et ashift.
  2. Confirmez les propriétés du dataset : recordsize/volblocksize, compression, logbias, primarycache.
  3. Vérifiez les travaux en arrière-plan : scrub, resilver, TRIM, snapshots send/receive.

Si vous suivez cet ordre, vous pouvez généralement nommer le goulet en 5–10 minutes. Pas le résoudre — le nommer. Nommer est la première étape pour ne pas tatillonner au hasard.

Trois mini-histoires d’entreprise (doucement plausibles)

Incident causé par une mauvaise hypothèse : « C’est du NVMe, donc le sync ira bien »

Une entreprise moyenne a déployé un nouveau datastore sur ZFS pour machines virtuelles. Tout était flash. C’était cher. Le benchmark était magnifique : les écritures aléatoires étaient « géniales », et les graphiques avaient assez de couleurs pour qualifier d’art moderne.

Deux semaines plus tard, les connexions du matin sont devenues un désastre au ralenti. Les consoles VM se figeaient pendant des secondes. Les bases de données à l’intérieur des VM ont commencé à timeout. L’équipe stockage a refait le benchmark qu’elle approuvait, a vu de bons chiffres et a déclaré que le problème devait être le « réseau ».

L’hypothèse erronée était simple : « Tout flash signifie que les écritures sync seront rapides. » Leur benchmark utilisait des écritures async avec de grandes files et mesurait la latence moyenne. En production, il y avait beaucoup de fsync — journaling, métadonnées et flushs du système invité. ZFS considérait cela comme des écritures synchrones, et sans un SLOG approprié, le chemin de latence pour les commits durables s’est bottlenecké sur le pire comportement du flash.

La solution n’était pas magique. Ils ont ajouté un SLOG low-latency avec protection contre la perte d’alimentation et ont relancé le benchmark avec sync=always et percentiles de latence activés. Les chiffres étaient plus bas mais honnêtes. Les orages de connexion du matin sont redevenus ennuyeux, ce qui est le plus grand compliment que l’on puisse faire à un système de stockage.

Une optimisation qui s’est retournée : recordsize et « débit gratuit »

Une équipe analytique voulait accélérer les scans. Quelqu’un a lu que « recordsize plus grand = lectures séquentielles plus rapides », et c’est souvent vrai. Ils ont mis recordsize à 1M sur un dataset contenant à la fois des fichiers parquet analytiques et un service de métadonnées base de données suivi de petits enregistrements.

Les benchmarks ont immédiatement progressé pour les lectures en masse. Ils ont célébré. Puis le service de métadonnées a commencé à voir des pics de latence. La base faisait de petites mises à jour aléatoires, et ZFS devait maintenant gérer de gros records pour de petites modifications, causant une amplification read-modify-write et des mises à jour métadonnées supplémentaires. Le service n’avait pas besoin de débit ; il avait besoin d’une latence d’écriture petite et prévisible.

Ils l’ont « corrigé » en augmentant iodepth et la concurrence fio jusqu’à ce que le benchmark ait de beaux chiffres. Cela a embelli les graphiques tout en détériorant le service, parce que la mise en file masquait la latence en la transformant en backlog.

La vraie solution était ennuyeuse : séparer les charges. Garder recordsize=1M pour les fichiers analytiques sur un dataset. Mettre la base sur son propre dataset avec un recordsize plus petit et des sémantiques sync appropriées. Le graphique de débit est devenu moins impressionnant ; la file d’incidents a diminué. Choisissez vos trophées.

Une pratique ennuyeuse mais correcte qui a sauvé la mise : mesurer et documenter l’état du cache

Une société financière effectuait des vérifications trimestrielles de capacité et performance avant des deadlines de reporting. Ce n’était pas glamour. Ils avaient une procédure décrivant : niveau de remplissage du pool, taille ARC au départ, propriétés du dataset, fichiers de job fio et comment enregistrer p95/p99 de latence. Chaque run incluait des tests « cold-ish » et « warm ».

Un trimestre, une mise à jour firmware sur un lot de SSD a introduit une bizarrerie de latence sous certaines profondeurs de file. Les débits séquentiels ne bougeaient pas. La latence moyenne de lecture aléatoire semblait correcte. Mais p99 a bondi d’une manière qui n’apparaissait que lorsque le cache était froid et que le working set dépassait l’ARC.

Parce qu’ils avaient des baselines cohérentes, l’écart était évident. Ils l’ont détecté avant la fenêtre de reporting. Ils ont rollbacké le firmware et mis le lot en quarantaine. Pas de débogage héroïque à 2h du matin, pas d’achat d’urgence, pas d’« est-ce le réseau ? »

Cette équipe n’a jamais gagné de prix d’innovation. Ils ont gagné mieux : du sommeil ininterrompu.

Erreurs courantes : symptômes → cause racine → correction

Voici la section où vos résultats de benchmark sont diagnostiqués comme un serveur malade : faits froids, pas d’ambiance.

1) Symptom : « Les lectures sont incroyablement rapides, plus rapides que les SSD »

  • Cause racine : ARC (ou cache OS) sert les lectures. Le working set tient en RAM, ou vous avez répété le run et chauffé le cache.
  • Correction : Utilisez direct=1, augmentez la taille du dataset au-delà de l’ARC, enregistrez le taux de miss ARC et étiquetez explicitement les résultats « warm-cache » vs « cold-cache ».

2) Symptom : « Les IOPS d’écriture aléatoire sont excellents, mais les BDs en production sont lentes »

  • Cause racine : Le benchmark utilisait des écritures async ; la production exige des écritures sync (fsync). Pas de SLOG, ou SLOG faible, ou vous n’avez pas testé le chemin sync.
  • Correction : Testez avec sync=always, mesurez la latence p99, ajoutez/validez le SLOG avec protection contre la perte d’alimentation, et confirmez son utilisation via zpool iostat.

3) Symptom : « Le débit est élevé pendant 30 secondes, puis chute »

  • Cause racine : Vous avez benchmarké le cache et le buffering d’écriture (TXG) plutôt que le débit soutenu du périphérique. Les runs courts ratent aussi le comportement d’allocation en état stable.
  • Correction : Lancez des tests plus longs, utilisez des runs time_based avec durée suffisante, et surveillez le bandwidth du pool dans le temps avec zpool iostat 1.

4) Symptom : « Des pics de latence toutes les quelques secondes »

  • Cause racine : Commits TXG, flushs sync, ou pics de latence du dispositif SLOG. Parfois scheduling CPU ou tempêtes d’interruptions.
  • Correction : Capturez les percentiles de fio, corrélez avec zpool iostat -r -w et vérifiez le comportement CPU/interruption. Validez la qualité du SLOG et le firmware.

5) Symptom : « Changer la compression rend tout plus rapide, donc on va s’y fier »

  • Cause racine : Les données de benchmark se compressent ; les données de production peuvent ne pas l’être (déjà compressées/chiffrées). La compression masque les limites d’I/O.
  • Correction : Vérifiez compressratio sur des données représentatives. Si la production est incompressible, benchmarkez avec la compression désactivée ou avec des données réalistes.

6) Symptom : « Un job fio montre de bons chiffres ; l’app réelle est plus lente »

  • Cause racine : Mismatch de la charge : taille de bloc, comportement sync, concurrence, motif d’accès, nombre de fichiers, mélange métadonnées.
  • Correction : Construisez des jobs fio qui ressemblent à l’app : mêmes tailles de bloc, même taux de fsync, concurrence similaire, nombre de fichiers réaliste et ratios mix read/write.

7) Symptom : « La performance du pool est incohérente entre les runs »

  • Cause racine : L’état du cache change, travaux en arrière-plan (scrub/resilver), niveau de remplissage du pool change, throttling thermique, changements du gouverneur CPU.
  • Correction : Contrôlez l’environnement : isolez l’hôte, fixez le gouverneur, arrêtez les tâches en arrière-plan, faites un warm-up et enregistrez le niveau de remplissage et l’état ARC à chaque run.

8) Symptom : « Les IOPS semblent bons mais p99 est affreux »

  • Cause racine : La mise en file masque la douleur. Une iodepth profonde peut gonfler les IOPS tout en augmentant la latence tail. Aussi possible : contention ou stalls sync.
  • Correction : Benchmarkez avec plusieurs valeurs d’iodepth, tracez IOPS vs p99 et choisissez le point qui respecte vos SLO de latence. N’optimisez pas seulement pour les IOPS.

Blague #2 : Si votre benchmark ne rapporte que des moyennes, c’est essentiellement un horoscope avec un meilleur formatage.

Listes de contrôle / plan étape par étape

Étape par étape : une session de benchmark ZFS répétable (grade production)

  1. Écrivez la question du benchmark (débit vs IOPS vs latence, sync vs async, fichier vs zvol).
  2. Enregistrez l’environnement : version du kernel, version ZFS, modèle CPU, RAM, modèles de périphériques, contrôleur, état du firmware.
  3. Confirmez la topologie du pool et ashift (zpool status, zdb -C).
  4. Créez un dataset dédié pour les benchmarks et définissez explicitement les propriétés (compression, atime, sync, recordsize/primarycache).
  5. Mettez l’état du pool : pas de scrub/resilver, température stable, gouverneur CPU stable, bruit minimal.
  6. Décidez le mode cache : cold-ish vs warm. Documentez la taille ARC et les misses. Si cold-ish, utilisez un gros working set et/ou redémarrez entre les runs.
  7. Warm-up : exécutez un court pré-test pour stabiliser l’allocation et éviter les anomalies « first touch ».
  8. Exécutez le benchmark assez longtemps pour l’état stable. Capturez la sortie fio, plus zpool iostat et iostat -x en parallèle.
  9. Répétez au moins 3 fois par configuration. Si la variance est élevée, vous avez du bruit ou un goulet non déterministe.
  10. Changez une variable (recordsize ou compression ou sync ou logbias), relancez la même suite.
  11. Interprétez en utilisant plusieurs couches : fio + zpool iostat + iostat -x + statistiques CPU.
  12. Décidez en fonction de votre SLO : choisissez la configuration qui respecte les objectifs p99 de latence, pas celle avec le plus gros débit pic.

Suite de benchmarks template : minimale mais honnête

  • Écriture/lecture séquentielle : blocs 1M, 1–4 jobs, iodepth 16, 2 minutes.
  • Lecture aléatoire : blocs 4k, jobs 4–8, iodepth 16–32, 3 minutes, percentiles.
  • Écriture aléatoire async : blocs 4k, jobs 4–8, iodepth 8–16, 3 minutes.
  • Écriture aléatoire sync : même que ci-dessus, mais avec sync=always et mesure p99/p999.
  • Mix 70/30 read/write : 8k ou 16k, percentiles de latence.

FAQ

1) Dois-je benchmarker avec la compression activée ou désactivée ?

Les deux, mais étiquetez-les. La compression est une fonctionnalité réelle qui peut améliorer le débit et réduire la latence en effectuant moins d’I/O. Ce n’est « faux » que si vos données de benchmark compressent et que les données de production ne compressent pas.

2) Utiliser dd est-il acceptable pour des benchmarks ZFS ?

Pour un test rapide séquentiel de fumée, oui. Pour tout ce qui implique la latence, l’I/O aléatoire, la concurrence ou la sémantique sync, utilisez fio et capturez les percentiles. dd est trop facile à configurer de façon à benchmarker accidentellement le cache et le readahead.

3) Qu’est-ce que sync=disabled fait aux résultats du benchmark ?

Il peut rendre les écritures dramatiquement plus rapides en les accusant avant qu’elles ne soient durables. Ce n’est pas un « trick » de tuning ; c’est modifier le contrat de durabilité. Si vous l’utilisez en production, vous acceptez une perte de données en cas de coupure d’alimentation ou de crash.

4) Quelle taille doivent avoir mes fichiers de test fio ?

Pour un comportement cold-cache : plus grand que l’ARC (souvent plus grand que la RAM). Pour un comportement warm-cache : dimensionné pour correspondre à votre working set réaliste. Indiquez toujours lequel vous avez testé.

5) Pourquoi mes chiffres changent après que le pool se remplit ?

L’allocation devient plus difficile, la fragmentation augmente et le comportement des metaslabs change. La performance ZFS à 20 % plein n’est pas la même qu’à 80 %. Benchmarkez à une capacité réaliste si la production vous importe.

6) Ai-je besoin d’un dispositif SLOG ?

Seulement si votre charge émet des écritures synchrones et que vous tenez à réduire la latence sync. Un SLOG n’aidera pas le débit async et ne réparera pas un pool déjà saturé disque. C’est pour la latence sync, pas pour la vitesse générale.

7) Quelle iodepth devrais-je utiliser dans fio ?

Utilisez une plage. Des files peu profondes (iodepth 1–4) montrent la latence et la réactivité ; des files profondes montrent le débit/IOPS max mais peuvent détruire la latence tail. Choisissez l’iodepth qui correspond à la concurrence et aux SLO latence de votre application.

8) Comment savoir si je suis lié par le CPU dans ZFS ?

Si les disques ne sont pas occupés (iostat -x %util bas) mais la latence fio augmente et mpstat montre un %sys élevé ou beaucoup de cœurs saturés, vous êtes probablement lié par le CPU : checksums, compression, interruptions, chiffrement ou contention.

9) Dois-je benchmarker raidz vs mirrors comme si c’était juste « plus de disques » ?

Non. Les mirrors ont tendance à mieux pour les IOPS aléatoires et la latence ; raidz est efficient en capacité et pour les charges séquentielles, mais la parité et l’allocation peuvent affecter les petites écritures aléatoires. Benchmarkez avec votre profil de charge réel, surtout pour les écritures aléatoires et le comportement sync.

10) L2ARC peut-il améliorer les benchmarks ?

Oui, mais c’est toujours un cache. Si votre charge est en lecture et réutilise les données, L2ARC peut aider. Si votre charge est majoritairement écriture ou lectures streaming, non. Si vous benchmarkez sans indiquer les couches de cache, vos résultats sont incomplets.

Étapes suivantes que vous pouvez réellement faire

Si vous voulez des résultats de benchmark ZFS qui survivent au contact avec la production, faites trois choses cette semaine :

  1. Écrivez et versionnez vos définitions de job fio (même simples) et enregistrez les propriétés du dataset avec les résultats.
  2. Ajoutez une « vérification de vérité » standard à chaque run : zpool iostat + arcstat + iostat -x capturés pendant le benchmark. Si les couches ne s’accordent pas, le benchmark ne compte pas.
  3. Benchmarkez le comportement sync avec percentiles de latence, même si vous pensez ne pas en avoir besoin. Le sync, c’est là où les systèmes cessent d’être rapides et commencent à être honnêtes.

ZFS est capable d’excellentes performances. Il est aussi capable de produire des benchmarks spectaculairement trompeurs si vous le laissez faire. Les règles ci‑dessus ne servent pas à gagner. Elles servent à ne pas être surpris plus tard — quand la surprise vient avec un pager.

← Précédent
Le point de montage ZFS : le piège de montage qui fait « disparaître » les jeux de données
Suivant →
Le démon Docker ne démarre pas : lisez d’abord ce journal (puis réparez-le)

Laisser un commentaire