ZFS fio pour VM : profils qui reflètent la réalité (pas le marketing)

Cet article vous a aidé ?

Vos utilisateurs de VM n’envoient pas de tickets “les IOPS sont faibles.” Ils disent “la base de données a gelé”, “les mises à jour Windows prennent une éternité” ou “ce job CI est encore bloqué sur ‘copying artifacts’.”
Pendant ce temps, vous lancez un fio rapide, obtenez des chiffres héroïques, et tout le monde part content—jusqu’à lundi.

ZFS rend cela plus facile à mal mesurer que la plupart des systèmes de fichiers parce qu’il est honnête sur la durabilité et qu’il a plusieurs couches qui peuvent tricher (ARC, compression, coalescence d’écriture, groupes de transactions).
La solution n’est pas « lancer plus de fio ». La solution est de lancer fio qui ressemble au comportement réel des VM : I/O mixte, sémantiques synchrones, profondeurs de file réalistes et cibles de latence.

1) Vérification de réalité : à quoi ressemble vraiment l’I/O des VM

La plupart des charges de stockage VM ne sont pas des « gros écrits séquentiels » et elles ne sont pas des « lectures aléatoires 4K pures ».
Elles forment un cocktail agaçant :

  • Lectures et écritures aléatoires en petits blocs (4K à 64K) provenant de bases de données, métadonnées, gestionnaires de paquets et services d’arrière-plan Windows.
  • Écritures synchrones en rafales (journaux, WAL, tempêtes fsync, flushs du guest VM).
  • Ratios lecture/écriture mixtes qui varient au fil des heures (fenêtres de sauvegarde, rotation de logs, patchs du mardi).
  • Sensibilité à la latence plus qu’à la bande passante. Une VM peut “paraître lente” à 2 000 IOPS si le p99 passe de 2 ms à 80 ms.
  • La concurrence est inégale : quelques VM chaudes peuvent dominer, tandis que la majorité est calme mais nécessite une latence de queue constante.

Un profil fio réaliste pour VM ne vise pas à maximiser un chiffre accrocheur. Il vise à mesurer les bons modes de défaillance :
latence des écritures synchrones, mise en file d’attente, amplification d’écriture, et si votre « pool rapide » se transforme en citrouille lors du commit TXG.

Si votre test n’inclut pas fsync ou un équivalent en mode synchrone, il ne mesure pas le type de douleur qui réveille les gens à 03:00.
Votre pool peut être correct pour l’ingestion massive et toujours être catastrophique pour les VM.

Blague n°1 : Si vos résultats fio semblent trop beaux pour être vrais, ils viennent probablement de l’ARC, pas de vos disques—comme un CV rédigé par une couche de cache.

2) Faits intéressants (et un peu d’histoire) qui changent la façon de benchmarker

Ce ne sont pas des anecdotes. Chacun se traduit par une erreur de benchmark que j’ai vue en production.

  1. ZFS a été conçu autour du copy-on-write et des groupes de transactions : les écritures sont collectées et engagées par batchs, ce qui induit des pics de latence pendant le sync TXG.
  2. ARC (Adaptive Replacement Cache) est une performance centrée sur la mémoire : un ARC chaud peut rendre les tests de lecture fio semblables à du NVMe même si le pool est sur plateaux.
  3. Le ZIL existe même sans SLOG séparé : sans device séparé, le ZIL vit sur le pool principal et concurrence tout le reste.
  4. Le SLOG accélère les écritures synchrones, pas toutes les écritures : les écritures asynchrones contournent le chemin ZIL ; tester sans sémantique synchrone peut faire paraître un SLOG “inutile”.
  5. volblocksize est défini lors de la création du zvol : vous ne le “réglez pas plus tard” de manière pratique. Cela compte pour l’alignement des I/O VM et l’amplification d’écriture.
  6. recordsize est une propriété de dataset, pas de zvol : mélanger datasets et zvols en espérant le même comportement est une erreur classique de benchmark.
  7. La compression peut augmenter les IOPS (parfois de façon spectaculaire) en réduisant les écritures physiques—jusqu’à ce que vos CPUs deviennent le goulot ou que les données ne compressent pas.
  8. Les valeurs par défaut de fio peuvent être dangereusement “douces” : I/O bufferisées et profondeurs de file conciliantes produisent des chiffres qui ne résisteront pas à la concurrence réelle des VM.
  9. Les caches d’écriture NVMe et la protection contre la perte d’alimentation comptent : un NVMe grand public “rapide” peut mentir sous des charges synchrones s’il manque de PLP adéquat.

3) La pile ZFS + I/O VM : où votre benchmark vous trompe

L’I/O VM n’est pas un système unique. C’est une chaîne de décisions et de caches. fio peut tester n’importe quel maillon de cette chaîne, et si vous testez le mauvais,
vous publierez un benchmark pour un système que vous n’exploitez pas réellement.

Filesystem du guest vs disque virtuel vs ZFS hôte

L’OS invité a son propre cache et son comportement de writeback. L’hyperviseur a sa propre mise en file. ZFS a ARC/L2ARC et sa propre pipeline d’écriture.
Si vous exécutez fio à l’intérieur du guest avec I/O bufferisée, vous testez majoritairement la mémoire du guest et la bande passante mémoire de l’hôte.
Si vous exécutez fio sur l’hôte contre un fichier, vous testez les datasets et le comportement de recordsize, qui peut ne pas correspondre au comportement d’un zvol.

Sémantiques synchrones : où la vraie douleur se trouve

La différence déterminante entre “démo rapide” et “production stable” est la durabilité. Les bases de données et beaucoup de filesystems invités émettent des flushs.
Sur ZFS, les écritures synchrones passent par la sémantique ZIL ; un dispositif SLOG séparé peut réduire la latence en fournissant une cible de log à faible latence.
Mais le SLOG n’est pas magique : il doit être à faible latence, cohérent et sûr en cas de coupure de courant.

Le pire péché fio en benchmarking VM est de lancer un test d’écriture séquentielle 1M, de voir 2–5 GB/s, et de déclarer la plateforme “prête pour les bases de données.”
Ce n’est pas un profil VM. C’est une diapositive marketing.

Profondeur de file et parallélisme : iodepth n’est pas un signe de vertu

Les charges VM ont souvent une profondeur de file modérée par VM mais une forte concurrence entre VMs. fio peut simuler cela de deux manières :
numjobs (plusieurs travailleurs indépendants) et iodepth (profondeur de file par travailleur).
Pour les VM, préférez plus de jobs avec iodepth modeste plutôt qu’un seul job avec iodepth=256 sauf si vous modélisez spécifiquement une grosse base de données.

Une façon fiable de créer une fausse performance est de choisir un iodepth qui force le device dans son meilleur chemin de fusion séquentielle.
C’est comme évaluer la conduite urbaine d’une voiture en la laissant rouler en descente.

4) Principes pour des profils fio qui reflètent la production

Principe A : testez ce que les utilisateurs ressentent (latence), pas seulement ce que les vendeurs vendent (débit)

Capturez la latence p95/p99, pas seulement la moyenne. Vos clients VM vivent dans la queue.
fio peut rapporter des percentiles ; utilisez-les et traitez-les comme des métriques de première classe.

Principe B : incluez des tests d’écriture synchrone

Utilisez --direct=1 pour éviter les effets du page cache invité (lorsque vous testez dans les guests) et utilisez un mécanisme synchrone :
--fsync=1 (ou --fdatasync=1) pour les workloads basés sur fichiers, ou --sync=1 pour certains moteurs.
Sur les périphériques bloc bruts, vous pouvez approcher cela avec --ioengine=libaio et forcer des flushs avec précaution,
mais le modèle le plus propre pour les “tempêtes de flush” VM est de tester avec un vrai filesystem + schémas fsync.

Principe C : assurez-vous que le working set bat l’ARC quand vous voulez tester les disques

Si vous voulez mesurer la performance du pool, la taille de test doit être largement supérieure à l’ARC.
Si l’ARC est de 64 GiB, ne lancez pas un test de lecture de 10 GiB en l’appelant « vitesse disque ».
Alternativement, testez de manière à vous concentrer sur les écritures (notamment synchrones) où l’ARC ne peut pas masquer entièrement le comportement physique.

Principe D : adaptez les tailles de blocs à ce que font les guests

L’I/O aléatoire VM tend à se concentrer sur 4K, 8K, 16K et 32K. Les gros blocs de 1M sont pour les flux de sauvegarde et médias.
Utilisez plusieurs tailles de blocs ou une distribution si vous le pouvez. Si vous devez en choisir une : 4K aléatoire et 128K séquentiel sont des valeurs courantes.

Principe E : utilisez des tests basés sur la durée avec une phase de montée en charge

Le comportement de ZFS change au fur et à mesure que les TXG se commitent, que l’ARC chauffe, que les métadonnées se créent et que l’espace libre se fragmente.
Lancez des tests assez longs pour voir quelques cycles TXG. Utilisez une période de ramp-up pour éviter de mesurer les 10 premières secondes où “tout est vide et heureux.”

Principe F : verrouillez l’environnement de test

Gouverneur CPU, équilibrage des interruptions, réglages virtio, propriétés de dataset et de zvol comptent tous.
La reproductibilité est une fonctionnalité. Si vous ne pouvez pas relancer le test un mois plus tard et expliquer les écarts, ce n’est pas du benchmarking — ce sont des impressions.

Une citation à garder sur un post‑it près de votre mur de supervision :
Tout échoue, tout le temps. — Werner Vogels

5) Profils fio réalistes (avec explications)

Ce ne sont pas des profils « meilleurs ». Ce sont des profils honnêtes. Utilisez-les comme blocs de construction et ajustez selon votre mix VM.
Pour chaque profil, décidez si vous testez à l’intérieur du guest, sur l’hôte contre un zvol, ou sur l’hôte contre un fichier de dataset.

Profil 1 : tempête de boot/login VM (lecture majoritaire, petits aléatoires, concurrence modeste)

Modélise des dizaines de VMs qui démarrent, services qui se lancent, lecture de nombreux petits fichiers. C’est surtout des lectures, mais pas purement aléatoires.

cr0x@server:~$ fio --name=vm-boot --filename=/dev/zvol/tank/vm-101-disk0 \
  --rw=randread --bs=16k --iodepth=8 --numjobs=8 --direct=1 \
  --time_based --runtime=180 --ramp_time=30 --group_reporting \
  --ioengine=libaio --percentile_list=95,99,99.9
vm-boot: (groupid=0, jobs=8): err= 0: pid=21233: Sat Dec 21 11:02:20 2025
  read: IOPS=42.1k, BW=658MiB/s (690MB/s)(115GiB/180s)
    slat (usec): min=3, max=2100, avg=12.4, stdev=18.9
    clat (usec): min=90, max=28000, avg=1480, stdev=2100
     lat (usec): min=105, max=28150, avg=1492, stdev=2102
    clat percentiles (usec):
     | 95.00th=[ 3600], 99.00th=[ 8200], 99.90th=[18000]

Ce que ça signifie : 42k IOPS semble excellent, mais le signal réel est la latence p99 et p99.9.
Les tempêtes de boot semblent pénibles quand le p99 atteint plusieurs dizaines de millisecondes.
Décision : si p99.9 est élevé, cherchez de la contention (autres workloads), des besoins spéciaux de vdev, ou des vdevs trop petits/lents.

Profil 2 : OLTP type base de données (mix aléatoire, écritures synchrones importantes)

C’est le profil qui expose si votre SLOG est réel ou simplement cosmétique.
Exécutez‑le sur un filesystem à l’intérieur du guest si possible, car les guests font des fsync. Sur l’hôte, vous pouvez lancer contre un fichier sur un dataset pour modéliser fsync.

cr0x@server:~$ fio --name=oltp-mix-fsync --directory=/tank/vmtest \
  --rw=randrw --rwmixread=70 --bs=8k --iodepth=4 --numjobs=16 \
  --direct=1 --time_based --runtime=300 --ramp_time=60 \
  --ioengine=libaio --fsync=1 --group_reporting --percentile_list=95,99,99.9
oltp-mix-fsync: (groupid=0, jobs=16): err= 0: pid=21901: Sat Dec 21 11:12:54 2025
  read: IOPS=18.4k, BW=144MiB/s (151MB/s)(42.2GiB/300s)
    clat (usec): min=120, max=95000, avg=2900, stdev=5200
    clat percentiles (usec):
     | 95.00th=[ 8200], 99.00th=[22000], 99.90th=[62000]
  write: IOPS=7.88k, BW=61.6MiB/s (64.6MB/s)(18.0GiB/300s)
    clat (usec): min=180, max=120000, avg=4100, stdev=7800
    clat percentiles (usec):
     | 95.00th=[12000], 99.00th=[34000], 99.90th=[90000]

Ce que ça signifie : Avec fsync, les queues de latence explosent en premier. La moyenne peut paraître “correcte” alors que le p99.9 ruine les transactions.
Décision : si la latence p99.9 d’écriture est mauvaise, validez le SLOG, les réglages sync et le comportement du cache d’écriture du device.

Profil 3 : mises à jour Windows / gestionnaire de paquets (métadonnées lourdes, petits aléatoires)

C’est là où des vdevs spéciaux pour métadonnées et petits blocs peuvent valoir leur coût—si vous avez réellement le bon type de pool.

cr0x@server:~$ fio --name=metadata-chaos --directory=/tank/vmtest \
  --rw=randrw --rwmixread=60 --bs=4k --iodepth=16 --numjobs=8 \
  --direct=1 --time_based --runtime=240 --ramp_time=30 \
  --ioengine=libaio --group_reporting --percentile_list=95,99,99.9
metadata-chaos: (groupid=0, jobs=8): err= 0: pid=22188: Sat Dec 21 11:18:22 2025
  read: IOPS=55.0k, BW=215MiB/s (226MB/s)(50.4GiB/240s)
    clat percentiles (usec): 95.00th=[ 2400], 99.00th=[ 6800], 99.90th=[16000]
  write: IOPS=36.0k, BW=141MiB/s (148MB/s)(33.0GiB/240s)
    clat percentiles (usec): 95.00th=[ 3100], 99.00th=[ 9200], 99.90th=[24000]

Ce que ça signifie : Si ces percentiles se dégradent fortement quand le pool est à moitié plein ou fragmenté,
vous pouvez avoir un problème de layout/ashift, un vdev miroir surchargé, ou il vous manque des chemins rapides pour les métadonnées.
Décision : comparez les performances à différents niveaux de remplissage du pool et après des écritures aléatoires soutenues.

Profil 4 : flux de sauvegarde/restauration (séquentiel, grands blocs, vérifie “peut-on vidanger ?”)

Ce profil n’est pas un test de latence VM. Il répond à : “Peut-on déplacer de gros volumes sans tout détruire ?”
Utilisez‑le pour planifier les fenêtres de sauvegarde et décider s’il faut brider.

cr0x@server:~$ fio --name=backup-write --filename=/tank/vmtest/backup.bin \
  --rw=write --bs=1m --iodepth=8 --numjobs=1 --direct=1 \
  --size=50G --ioengine=libaio --group_reporting
backup-write: (groupid=0, jobs=1): err= 0: pid=22502: Sat Dec 21 11:24:10 2025
  write: IOPS=1450, BW=1450MiB/s (1520MB/s)(50.0GiB/35s)

Ce que ça signifie : Un excellent débit ne signifie pas que votre pool est sain pour les VM.
Décision : utilisez cela pour définir des throttles de sauvegarde ; puis relancez un profil sensible à la latence en parallèle pour observer l’interférence.

Profil 5 : test disque « sans triche » (working set plus grand que l’ARC, lectures aléatoires)

Utilisez‑le quand quelqu’un prétend que le pool est “lent” et que vous devez établir la capacité de lecture brute sans que l’ARC masque la vérité.
Vous devez dimensionner le fichier au‑delà de l’ARC et exécuter assez longtemps pour éviter les artefacts de cache chaud.

cr0x@server:~$ fio --name=arc-buster --filename=/tank/vmtest/arc-buster.bin \
  --rw=randread --bs=128k --iodepth=32 --numjobs=4 --direct=1 \
  --size=500G --time_based --runtime=240 --ramp_time=30 \
  --ioengine=libaio --group_reporting --percentile_list=95,99
arc-buster: (groupid=0, jobs=4): err= 0: pid=22791: Sat Dec 21 11:31:12 2025
  read: IOPS=3100, BW=387MiB/s (406MB/s)(90.7GiB/240s)
    clat percentiles (usec):
     | 95.00th=[ 16000], 99.00th=[ 32000]

Ce que ça signifie : IOPS plus bas et latence plus élevée sont normales ici ; vous touchez enfin les disques.
Décision : si c’est étonnamment mauvais, vérifiez le layout vdev, ashift et l’état des disques avant de discuter des flags fio.

6) Tâches pratiques : commandes, ce que signifie la sortie, et quoi décider

C’est la partie que vous utiliserez vraiment lors d’un incident ou d’un examen de capacité.
Chaque tâche inclut : une commande, une sortie d’exemple, ce que cela signifie et la décision qu’elle entraîne.
Supposons un hôte Linux exécutant ZFS avec un pool nommé tank.

Tâche 1 : identifier si vous benchmarkez un zvol ou un dataset (et quelles propriétés s’appliquent)

cr0x@server:~$ zfs list -o name,type,volblocksize,recordsize,compression,sync tank
NAME                     TYPE     VOLBLOCKSIZE  RECORDSIZE  COMPRESS  SYNC
tank                     filesystem       -        128K     lz4       standard
tank/vmdata              filesystem       -        128K     lz4       standard
tank/vm-101-disk0         volume        16K          -      lz4       standard

Ce que ça signifie : les zvols ont volblocksize ; les datasets ont recordsize.
Mélanger leurs résultats est la façon dont vous “optimisez” accidentellement la mauvaise chose.
Décision : choisissez la cible fio en conséquence : /dev/zvol/... pour les disques VM zvol, ou un fichier sur le dataset si votre stockage VM utilise des fichiers.

Tâche 2 : vérifier la topologie du pool (votre layout vdev est votre contrat de performance)

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

        NAME                        STATE     READ WRITE CKSUM
        tank                        ONLINE       0     0     0
          mirror-0                  ONLINE       0     0     0
            nvme0n1                 ONLINE       0     0     0
            nvme1n1                 ONLINE       0     0     0
          mirror-1                  ONLINE       0     0     0
            nvme2n1                 ONLINE       0     0     0
            nvme3n1                 ONLINE       0     0     0
        logs
          nvme4n1                   ONLINE       0     0     0

errors: No known data errors

Ce que ça signifie : Les mirrors se comportent différemment du RAIDZ sous I/O aléatoire. logs indique qu’un SLOG séparé existe.
Décision : si vous testez les écritures synchrones, vérifiez que logs est présent et sain ; si RAIDZ, attendez‑vous à des IOPS d’écritures aléatoires plus faibles et planifiez en conséquence.

Tâche 3 : vérifier l’espace libre du pool et les signaux de risque de fragmentation

cr0x@server:~$ zfs list -o name,used,avail,refer,mountpoint tank
NAME         USED  AVAIL  REFER  MOUNTPOINT
tank        42.8T  6.10T   192K  /tank

Ce que ça signifie : Les pools presque pleins montrent en général un pire comportement d’allocation et une pire latence en queue.
ZFS n’est pas particulièrement mauvais ici ; il est juste honnête quant aux conséquences.
Décision : si avail est serré, arrêtez de “benchmark” et lancez du travail de capacité. Tout test fio maintenant mesure un système déjà en détresse.

Tâche 4 : valider que les réglages sync ne vous mentent pas

cr0x@server:~$ zfs get -r -o name,property,value sync tank/vmdata
NAME        PROPERTY  VALUE
tank/vmdata sync      standard

Ce que ça signifie : sync=standard signifie que les requêtes sync sont respectées. sync=disabled rend les benchmarks jolis et les audits furieux.
Décision : si quelqu’un a mis sync=disabled “temporairement”, considérez chaque résultat de performance comme contaminé.

Tâche 5 : vérifier ashift (parce que les disques 4K ne pardonnent pas les fantasmes 512-byte)

cr0x@server:~$ zdb -C tank | grep -E "ashift|vdev_tree" -n | head
120:        ashift: 12

Ce que ça signifie : ashift=12 signifie secteurs 4K. Un ashift incorrect peut dégrader définitivement la performance via des RMW.
Décision : si ashift est incorrect, planifiez une migration. Vous ne vous en sortirez pas en “tuning”.

Tâche 6 : vérifier la taille de l’ARC vs taille du test (benchmarker la RAM ?)

cr0x@server:~$ arcstat 1 1
    time  read  miss  miss%  dmis  dm%  pmis  pm%  mmis  mm%  arcsz     c
11:40:22  128K   56K     43   10K   8%   40K  31%    6K   4%   64G   80G

Ce que ça signifie : L’ARC est de 64G avec cible 80G. Si votre fichier fio est plus petit, les lectures “s’amélioreront” avec le temps.
Décision : pour les tests disque, utilisez un fichier plusieurs fois plus grand que l’ARC, ou concentrez‑vous sur les écritures synchrones où l’ARC ne peut pas tout masquer.

Tâche 7 : surveiller l’I/O ZFS et la latence au niveau du pool pendant fio

cr0x@server:~$ zpool iostat -v tank 1 3
                              capacity     operations     bandwidth
pool                        alloc   free   read  write   read  write
--------------------------  -----  -----  -----  -----  -----  -----
tank                        42.8T  6.10T  8.20K  3.10K   410M   220M
  mirror-0                  21.4T  3.05T  4.10K  1.55K   205M   110M
    nvme0n1                      -      -  2.05K    780   102M    55M
    nvme1n1                      -      -  2.05K    770   103M    55M
  mirror-1                  21.4T  3.05T  4.10K  1.55K   205M   110M
    nvme2n1                      -      -  2.04K    780   102M    55M
    nvme3n1                      -      -  2.06K    770   103M    55M
--------------------------  -----  -----  -----  -----  -----  -----

Ce que ça signifie : Vous voyez si la charge est répartie entre les vdevs ou si un côté est chaud.
Décision : si un vdev est surchargé (ou un disque est plus lent), investiguez un déséquilibre, le firmware, ou un device en échec.

Tâche 8 : confirmer que le SLOG est réellement utilisé pour les écritures synchrones

cr0x@server:~$ zpool iostat -v tank 1 2 | sed -n '1,18p'
                              capacity     operations     bandwidth
pool                        alloc   free   read  write   read  write
--------------------------  -----  -----  -----  -----  -----  -----
tank                        42.8T  6.10T  2.10K  6.40K   120M   210M
  mirror-0                  21.4T  3.05T  1.05K  3.10K    60M   105M
  mirror-1                  21.4T  3.05T  1.05K  3.30K    60M   105M
logs                             -      -     2  9.80K   512K   310M
  nvme4n1                         -      -     2  9.80K   512K   310M

Ce que ça signifie : Un grand nombre d’opérations d’écriture sur le device de log pendant un fio sync-heavy indique que le trafic ZIL atterrit sur le SLOG.
Décision : si les écritures sur le log ne bougent pas pendant les tests sync, soit la charge n’est pas synchrone, soit sync=disabled est défini quelque part, soit le SLOG n’est pas configuré.

Tâche 9 : vérifier les goulots CPU et la pression IRQ pendant les benchmarks “rapides”

cr0x@server:~$ mpstat -P ALL 1 2
Linux 6.8.0 (server)  12/21/2025  _x86_64_  (32 CPU)

12:01:10 PM  CPU   %usr  %nice   %sys %iowait  %irq  %soft  %idle
12:01:11 PM  all   18.2   0.0   22.9    0.8    0.0   6.1   52.0
12:01:11 PM   7    4.0   0.0   78.0    0.0    0.0   0.0   18.0

Ce que ça signifie : Un CPU saturé en %sys peut indiquer un goulot d’unique file/IRQ (NVMe, virtio, réseau, ou locking).
Décision : si vous êtes limité par le CPU, arrêtez de tuner les disques. Corrigez le queueing, l’affinité IRQ, ou augmentez le parallélisme.

Tâche 10 : vérifier le comportement de flush du guest (pourquoi “sync” revient sans cesse)

cr0x@server:~$ qemu-img info /tank/vmimages/vm-101.qcow2
image: /tank/vmimages/vm-101.qcow2
file format: qcow2
virtual size: 200 GiB (214748364800 bytes)
disk size: 36.1 GiB
cluster_size: 65536

Ce que ça signifie : qcow2 a ses propres métadonnées et comportement d’allocation. Il peut amplifier les écritures et introduire des flushs supplémentaires selon les réglages.
Décision : si la latence est mauvaise sous workloads sync, envisagez raw/zvol ou réglez soigneusement le cache/IO mode de qcow2 (et testez à nouveau).

Tâche 11 : vérifier le ratio de compression ZFS et décider si le CPU aide ou nuit

cr0x@server:~$ zfs get -o name,property,value,source compressratio,compression tank/vmdata
NAME        PROPERTY       VALUE  SOURCE
tank/vmdata compression    lz4    local
tank/vmdata compressratio  1.62x  -

Ce que ça signifie : Un vrai compressratio suggère que votre pool écrit moins sur le disque que ce que la VM croit.
Décision : si compressratio est élevé et que le CPU n’est pas saturé, la compression est un gain net. Si le CPU est saturé, benchmarkez avec et sans.

Tâche 12 : vérifier l’alignement des blocs zvol pour les I/O VM

cr0x@server:~$ lsblk -o NAME,PHY-SEC,LOG-SEC,MIN-IO,OPT-IO,ROTA /dev/zvol/tank/vm-101-disk0
NAME                     PHY-SEC LOG-SEC MIN-IO OPT-IO ROTA
zd0                        4096    4096   4096      0    0

Ce que ça signifie : 4K secteurs logiques/physiques alignés avec les attentes modernes. La désalignement cause des RMW et des pics de latence.
Décision : si vous voyez 512 secteurs logiques sur des devices 4K, corrigez‑le en conception (ashift/volblocksize). Sinon vous passerez votre temps à “tuner”.

Tâche 13 : mesurer les signaux de pression de sync TXG

cr0x@server:~$ cat /proc/spl/kstat/zfs/txgs
1 0x01 0x00000000 136 13440 105155148830 0

Ce que ça signifie : Ce fichier peut changer selon l’implémentation, mais si les temps de sync TXG ou l’arriéré augmentent sous charge, vous verrez des vagues de latence.
Décision : si la latence en queue corrèle avec le comportement TXG, investigatez les limites de données sales, la latence d’écriture vdev et l’efficacité du SLOG plutôt que de chasser les flags fio.

Tâche 14 : vérifier les compteurs d’erreur device et les outliers de latence avant de blâmer ZFS

cr0x@server:~$ smartctl -a /dev/nvme0n1 | sed -n '1,25p'
smartctl 7.4 2023-08-01 r5530 [x86_64-linux-6.8.0] (local build)
=== START OF INFORMATION SECTION ===
Model Number:                       ACME NVMe 3.2TB
Firmware Version:                   1.04
Percentage Used:                    2%
Data Units Read:                    19,442,112
Data Units Written:                 13,188,440
Media and Data Integrity Errors:    0
Error Information Log Entries:      0

Ce que ça signifie : Un seul device instable peut transformer un p99 en cauchemar alors que la moyenne paraît correcte.
Décision : si des erreurs ou une usure élevée apparaissent, remplacez le device avant de “tuner” autour d’un hardware défaillant.

7) Manuel de diagnostic rapide : trouver le goulot en quelques minutes

C’est le playbook “arrêtez de débattre, commencez à isoler”. Utilisez‑le quand la latence est élevée ou que les résultats fio ne correspondent pas à la production.
L’objectif est d’identifier si vous êtes limité par le guest, l’hyperviseur, ZFS, le layout vdev, ou un device malade isolé.

Première étape : décidez ce que vous testez réellement

  • fio dans le guest, I/O bufferisée → majoritairement comportement du cache du guest et de la mémoire.
  • fio dans le guest, I/O directe → plus proche du comportement du disque virtuel (toujours via les files d’attente de l’hyperviseur).
  • fio sur l’hôte contre un zvol → teste le chemin bloc ZFS, contourne le FS du guest.
  • fio sur l’hôte contre un fichier dans un dataset → teste le chemin dataset ZFS et le comportement de recordsize.

Si la cible du test ne correspond pas au chemin de stockage de vos VM, arrêtez. Corrigez le test.

Deuxième étape : demandez-vous “est-ce synchrone ?”

  • Lancez un profil fio sync-heavy (--fsync=1 ou équivalent) et regardez zpool iostat -v pour l’activité du log.
  • Vérifiez zfs get sync au niveau dataset/zvol.

Si la latence p99 d’écriture explose uniquement avec sync, votre problème est dans le comportement ZIL/SLOG, la sécurité du cache d’écriture du device, ou la latence d’écriture sur les vdevs.

Troisième étape : déterminer si l’ARC masque les lectures

  • Comparez un test de lecture “arc-buster” avec un petit test de lecture.
  • Surveillez les taux de miss d’arcstat pendant l’exécution.

Si les “lectures disque” sont rapides mais les miss faibles, vous ne lisez pas les disques. Vous lisez la RAM et appelez ça du stockage.

Quatrième étape : localisez le point d’étranglement avec des stats en direct

  • zpool iostat -v 1 montre la distribution par vdev et si les logs sont utilisés.
  • mpstat 1 montre la saturation CPU et la pression sur un seul cœur.
  • iostat -x 1 montre l’utilisation des devices et la latence au niveau bloc.

Si un seul device est saturé ou montre un await élevé, isolez‑le. Si le CPU est saturé, arrêtez de chercher des SSD plus rapides.

Cinquième étape : vérifier l’état du pool et la réalité d’allocation

  • Pool presque plein ? Attendez‑vous à un pire comportement.
  • Resilver récent ? Scrub en cours ? Attendez des interférences.
  • Erreurs ? Arrêtez le travail de performance et réglez l’intégrité d’abord.

8) Erreurs courantes : symptômes → cause racine → correction

Erreur 1 : “fio affiche 1M IOPS mais les VMs sont lentes”

Symptômes : IOPS de lecture massives dans fio, mais les applications réelles ont une latence élevée et des blocages.

Cause racine : benchmark sur ARC/page cache. Le fichier de test tient en RAM ; fio lit le cache, pas le stockage.

Correction : Utilisez --direct=1, rendez le working set plus grand que l’ARC, et surveillez le miss% d’arcstat pendant l’exécution.

Erreur 2 : “Le SLOG n’a rien fait”

Symptômes : Ajouter un SLOG ne montre aucune amélioration ; la latence d’écriture synchrone est inchangée.

Cause racine : La charge n’était pas synchrone (pas de fsync/flush), ou sync=disabled est défini, ou le device de log n’est pas actif.

Correction : Lancez un fio heavy fsync, vérifiez que zpool status montre logs, et confirmez les ops d’écriture du log dans zpool iostat -v.

Erreur 3 : “Nous avons augmenté iodepth et obtenu de meilleurs chiffres, donc c’est bon”

Symptômes : Les IOPS du benchmark ont augmenté avec iodepth=256 ; la production souffre toujours.

Cause racine : La mise en file artificielle masque la latence. Vous mesurez le débit de saturation, pas le temps de service.

Correction : Utilisez des valeurs iodepth qui correspondent au comportement des VM (souvent 1–16 par job) et suivez la latence p99/p99.9.

Erreur 4 : “Les écritures aléatoires sont terribles ; ZFS est lent”

Symptômes : Les tests d’écritures aléatoires petites sont mauvais, surtout sur RAIDZ.

Cause racine : Surcharge de parité RAIDZ plus coûts COW lors des petites écritures aléatoires. C’est de la physique attendue.

Correction : Pour des I/O aléatoires VM intenses, utilisez des mirrors (ou designs de vdev spéciaux) et dimensionnez le nombre de vdevs pour les IOPS, pas pour la capacité brute.

Erreur 5 : “Des pics de latence apparaissent périodiquement comme un battement de coeur”

Symptômes : Le p99 saute périodiquement pendant une charge stable.

Cause racine : Comportement de sync TXG, throttling des données sales, ou un device lent créant des pauses périodiques.

Correction : Corrélez les pics avec les stats ZFS et l’await disque ; validez le firmware du device et envisagez des améliorations de latence d’écriture (meilleurs vdevs, meilleur SLOG).

Erreur 6 : “Nous avons tuné recordsize pour les disques VM”

Symptômes : Les changements de recordsize n’ont aucun effet sur la performance des zvols VM.

Cause racine : recordsize ne s’applique pas aux zvols ; volblocksize le fait.

Correction : Créez les zvols avec un volblocksize approprié dès le départ ; migrez si nécessaire.

Erreur 7 : “La compression a rendu fio plus rapide, donc c’est forcément mieux”

Symptômes : Les IOPS montent avec la compression activée ; le CPU s’élève ; sous charge réelle, la latence empire.

Cause racine : Goulot CPU ou données non compressibles. La compression peut aider, mais elle n’est pas gratuite.

Correction : Mesurez la marge CPU pendant une concurrence réaliste ; vérifiez compressratio ; conservez la compression si elle réduit réellement les écritures sans saturer les CPUs.

Blague n°2 : Changer sync=disabled pour “améliorer la performance” revient à enlever le détecteur de fumée parce qu’il vous réveille.

9) Trois mini-histoires d’entreprise issues du terrain

Histoire A : Un incident causé par une mauvaise hypothèse (cache ≠ disque)

Une société SaaS de taille moyenne a déployé un nouveau cluster VM pour le CI interne et quelques bases de données clientes.
Le stockage était ZFS sur de bons miroirs NVMe. La preuve de préparation était un test fio qui montrait des IOPS de lecture absurdes.
Tout le monde s’est détendu. Les achats ont eu une étoile d’or.

Deux semaines plus tard, le canal incident s’est enflammé : pics de latence base de données, runners CI qui atteignent des timeouts, avertissements “tâche bloquée” dans les kernels invités.
L’on‑call a relancé le même job fio et a encore obtenu les gros chiffres. Cela a créé un type particulier de misère : quand les métriques disent “rapide”
mais que les humains disent “lent”, vous perdez des heures à débattre de quelle réalité compte.

La mauvaise hypothèse était simple : “IOPS de lecture fio équivalent performance disque.” Le fichier de test était petit.
L’ARC était énorme. Sous charge VM soutenue, le working set chaud n’était pas stable et les écritures synchrones poussaient le comportement TXG dans des vagues de latence visibles.
fio benchmarkait la mémoire.

La correction n’était pas exotique. Ils ont reconstruit la suite fio : tests basés sur la durée, fichiers bien au‑delà de l’ARC, et workload mixte avec fsync.
Les chiffres sont devenus “pires”, ce qui fut la meilleure chose—ils correspondaient maintenant à la production. Ils ont ensuite trouvé un NVMe unique avec une latence d’écriture incohérente.
Le remplacer a stabilisé le p99.9 et “amélioré l’application” de façon presque magique, ce qui est en fin de compte le seul benchmark qui importe.

Histoire B : Une optimisation qui a échoué (le raccourci sync)

Une plateforme proche de la finance avait une ferme de VM exécutant un bus de messages et quelques clusters PostgreSQL.
Lors d’une répétition de pic, ils ont observé une latence de commit élevée. Quelqu’un a suggéré un changement ZFS “temporaire” :
définir sync=disabled sur le dataset contenant les disques VM pour rendre les commits plus rapides.

Ça a marché immédiatement. Les graphiques de latence sont tombés. La répétition a réussi. Le changement est resté.
L’équipe n’était pas imprudente ; elle était occupée, et la plateforme n’avait pas de culture de revue de dérive de config.
Des mois plus tard, un événement d’alimentation a touché un rack. Les hôtes ont redémarré proprement. Les VMs sont revenues. Quelques services ne l’ont pas fait.

Ce qui a suivi fut une semaine d’investigations médico‑légales que personne n’aime : motifs subtils de corruption de base, messages reconnus manquants, et une lente reconstruction de la confiance.
Il n’y avait pas de ligne de log évidente. Il y en a rarement. L’“optimisation” avait transformé la durabilité d’un contrat en suggestion.
ZFS a fait ce qu’on lui avait demandé. Le système a échoué exactement comme configuré.

L’échec ne fut pas seulement la coupure. Ce fut la dette opérationnelle à long terme :
ils ont dû auditer chaque dataset, re-baseliner les performances avec sync activé, valider le hardware SLOG, et re‑former les équipes à traiter les réglages de durabilité comme des contrôles de sécurité de production.
La correction finale de performance a impliqué de meilleurs dispositifs de log et plus de mirrors—pas de mentir au stack de stockage.

Histoire C : Une pratique ennuyeuse mais correcte qui a sauvé la mise (baselines reproductibles)

Une autre organisation—avec un processus de changement douloureusement mature—gardait une petite suite fio versionnée aux côtés de leur code d’infra.
Même versions de fio. Même fichiers job. Même runtime. Même datasets cibles. Chaque changement lié au stockage nécessitait un run et un rapport attaché.
Personne n’aimait ça. Ce n’était pas glamour.

Un trimestre, ils ont réinstallé une version de firmware HBA pendant une fenêtre de maintenance. Rien d’autre n’a changé.
Le lendemain, quelques VMs ont commencé à signaler des stalls occasionnels. Pas assez pour un incident complet, juste de quoi inquiéter.
L’équipe a lancé sa suite fio standard et l’a comparée à la baseline du mois précédent. La latence p99 d’écriture était significativement pire sur les profils sync-heavy.

Parce que la suite baseline existait déjà, ils n’ont pas débattu de la méthodologie. Ils n’ont pas tergiversé sur l’iodepth.
Ils avaient une “sensation du système” connue capturée en chiffres importants.
Ils ont rollbacké le firmware, et les rapports de stalls ont disparu.

Le geste salvateur ici fut ennuyeux : des tests contrôlés et reproductibles avec des percentiles de latence et des sémantiques sync.
Cela leur a permis de traiter la performance comme un problème de régression, pas comme un argument philosophique.

10) Listes de contrôle / plan pas à pas

Étape par étape : construire une suite fio réaliste VM pour ZFS

  1. Inventoriez votre chemin de stockage VM. Les disques VM sont‑ils des zvols, fichiers raw, qcow2, ou autre ?
  2. Capturez les propriétés ZFS pour les datasets/zvols concernés : compression, sync, recordsize/volblocksize.
  3. Choisissez trois profils de base :
    • 4K/8K random mix avec fsync (axé latence)
    • 16K random read storm (comportement boot/login)
    • 1M écriture séquentielle (débit backup/restore)
  4. Décidez de la structure des jobs : préférez numjobs pour la concurrence et gardez iodepth modéré.
  5. Utilisez des runs basés sur la durée (3–10 minutes) avec ramp time (30–60 secondes).
  6. Mesurez les percentiles (95/99/99.9) et traitez p99.9 comme le “proxy douleur utilisateur.”
  7. Dimensionnez les fichiers de test au‑delà de l’ARC si vous voulez mesurer les lectures disque.
  8. Lancez les tests en trois modes :
    • Hôte → zvol
    • Hôte → fichier dataset
    • Guest → filesystem (I/O direct et fsync)
  9. Enregistrez l’environnement : versions kernel/ZFS, gouverneur CPU, topologie pool, et s’il y avait des scrubs/resilvers.
  10. Répétez au moins deux fois et comparez. Si les résultats varient énormément, cette variabilité est en elle‑même un constat.

Check‑list opérationnelle : avant de faire confiance à un chiffre fio

  • Le --direct=1 est‑il utilisé quand il le faut ?
  • Le profil inclut‑il fsync/flush pour modéliser bases de données ou durabilité VM ?
  • Le fichier de test est‑il plus grand que l’ARC (pour les tests de lecture) ?
  • Suivez‑vous la latence p99/p99.9 ?
  • Surveillez‑vous zpool iostat -v et le CPU pendant le test ?
  • Le pool est‑il sain (pas d’erreurs, pas de vdev dégradé) ?
  • Le pool n’est‑il pas presque plein ?
  • Avez‑vous exécuté le test sur le vrai chemin de stockage utilisé par les VMs ?

Check‑list de changement : lors du tuning ZFS pour workloads VM

  • Ne touchez pas à la durabilité en premier. Laissez sync tranquille sauf si vous aimez les retrospectives d’incident.
  • Préférez les décisions de layout plutôt que le micro‑tuning. Mirrors vs RAIDZ est un choix de conception, pas un sysctl.
  • Validez le SLOG avec fio sync‑heavy et confirmez qu’il est utilisé.
  • Alignez le volblocksize sur la réalité du guest à la création du zvol.
  • Mesurez le risque de régression avec une suite baseline après chaque changement significatif.

11) FAQ

Q1 : Dois‑je lancer fio dans la VM ou sur l’hôte ?

Les deux, mais pour des raisons différentes. À l’intérieur du guest vous voyez ce que vit le guest (y compris les files d’attente de l’hyperviseur et le comportement du filesystem invité).
Sur l’hôte vous isolez le comportement ZFS. S’ils diffèrent, c’est un indice : votre goulot est dans la couche de virtualisation ou du caching.

Q2 : Quels flags fio importent le plus pour le réalisme VM ?

--direct=1, des --bs réalistes, un --iodepth modéré, plusieurs --numjobs, des runs basés sur la durée,
et --fsync=1 (ou équivalent) pour les workloads sensibles à la durabilité. Aussi : --percentile_list pour arrêter de regarder les moyennes.

Q3 : Pourquoi mon test de lecture aléatoire s’accélère avec le temps ?

L’ARC (ou le page cache du guest) se réchauffe. Vous passez du disque à la mémoire. Si vous essayez de tester les disques, augmentez le working set et surveillez les misses ARC.

Q4 : Comment savoir si mon SLOG aide ?

Lancez un profil fio sync-heavy et regardez les ops d’écriture du device de log dans zpool iostat -v. Comparez aussi la latence p99 d’écriture avec et sans SLOG.
Si votre workload n’est pas synchrone, le SLOG ne devrait pas aider—et ce n’est pas un échec.

Q5 : Le RAIDZ est‑il “mauvais” pour le stockage VM ?

RAIDZ n’est pas mauvais ; il n’est juste pas un monstre d’IOPS pour les petites écritures aléatoires. Pour les charges VM de type OLTP, les mirrors sont généralement un choix plus sûr.
Si vous avez besoin de RAIDZ pour l’efficacité de capacité, planifiez la réalité de performance et testez avec sync + écritures aléatoires.

Q6 : Dois‑je changer recordsize pour la performance VM ?

Seulement pour les datasets utilisés comme fichiers (comme qcow2/raw files). Pour des disques VM basés sur zvol, recordsize ne s’applique pas ; volblocksize le fait.

Q7 : Quelle est une bonne cible pour la latence p99 ?

Cela dépend du workload, mais en règle générale : si la latence p99 d’écriture synchrone entre régulièrement dans des dizaines de millisecondes, les bases de données vont se plaindre.
Utilisez les SLOs de votre application pour fixer un seuil ; puis adaptez la conception (vdevs, SLOG) pour l’atteindre.

Q8 : Comment éviter que fio détruise les performances du pool pour tout le monde ?

Exécutez en fenêtre de maintenance, bridez avec moins de jobs/iodepth, et surveillez. fio est un générateur de charge, pas un invité poli.
Si vous devez tester en production, faites des runs plus courts et privilégiez les profils de latence plutôt que de saturer le débit.

Q9 : Activer la compression aide‑t‑il toujours les workloads VM ?

Souvent oui, parce que les données VM (fichiers OS, logs) compressent et réduisent les écritures physiques. Mais si le CPU devient goulot ou que les données sont incompressibles,
la compression peut nuire à la latence en queue. Vérifiez compressratio et le CPU pendant une charge réaliste.

Q10 : Pourquoi mes résultats fio diffèrent entre zvols et fichiers dataset ?

Chemins de code et propriétés différents. Les datasets utilisent recordsize et les métadonnées de fichier ; les zvols utilisent volblocksize et présentent un device bloc.
Les plateformes VM se comportent aussi différemment selon que vous utilisez des fichiers raw, qcow2, ou des zvols.

12) Prochaines étapes pratiques

Si vous voulez que vos résultats fio prédisent la réalité des VM, faites ceci ensuite, dans cet ordre :

  1. Choisissez un disque VM (zvol ou fichier) et construisez trois profils fio : tempête de boot, OLTP mix avec fsync, flux de sauvegarde.
  2. Exécutez‑les basés sur la durée avec percentiles, et consignez p95/p99/p99.9, pas seulement les IOPS.
  3. Pendant chaque run, capturez zpool iostat -v, arcstat, et les statuts CPU.
  4. Validez le chemin sync : confirmez l’activité SLOG (si présent) et vérifiez qu’aucun dataset n’a sync=disabled masquant les problèmes.
  5. Transformez les résultats en baseline et relancez après chaque changement significatif : firmware, kernel, version ZFS, topologie, et format de stockage VM.

L’objectif n’est pas d’obtenir de jolis chiffres. L’objectif est de ne plus être surpris en production.
Une fois que votre suite fio fait mal aux mêmes endroits que ceux que les utilisateurs se plaignent, vous benchmarkez enfin le système que vous exploitez réellement.

← Précédent
xattr ZFS : le choix de compatibilité qui change les performances
Suivant →
Alerte 0-day : pourquoi une seule vulnérabilité peut déclencher la panique instantanée

Laisser un commentaire