ZFS IOPS vs débit : cessez de lire le mauvais indicateur

Cet article vous a aidé ?

Votre stockage « a l’air correct ». Le tableau de bord indique 2 GB/s en lecture, le pool est vert, et pourtant la base de données suffoque comme si elle venait de courir un marathon en manteau de laine.
Tout le monde discute quand même. Quelqu’un pointe « la bande passante du disque » et décrète victoire. Pendant ce temps, votre graphique de latence p95 fait tranquillement de l’incendie criminel.

Les problèmes de performance ZFS ne commencent que rarement par un disque cassé. Ils commencent par un modèle mental cassé : lire le mauvais indicateur pour la charge réelle.
C’est la différence entre « nous sommes limités par les IOPS » et « nous sommes limités par le débit », et cela décide si vous achetez plus de disques, changez recordsize, ajoutez un vdev spécial, ou arrêtez de benchmarker des mensonges.

Le décalage des métriques : pourquoi 2 GB/s peut quand même être lent

Le débit est séduisant par sa simplicité. C’est un grand nombre. Il rend bien dans une présentation. C’est aussi la métrique la plus facile à mal utiliser.
Un système peut déplacer des gigaoctets par seconde et pourtant offrir une expérience utilisateur affreuse s’il effectue ce travail en gros flux séquentiels alors que votre charge réelle est constituée de petites lectures aléatoires avec des budgets de latence stricts.

La relation de base est la suivante :
Débit = IOPS × taille IO.
Ce n’est pas un slogan ; c’est la mathématique derrière la plupart des arguments de stockage.
Si votre taille IO est de 4 KiB et que vous pouvez faire 20 000 IOPS, cela fait environ 80 MiB/s. Peu impressionnant, mais peut-être exactement ce dont une base de données a besoin.
Si votre taille IO est de 1 MiB et que vous pouvez faire 2 000 IOPS, cela fait 2 GiB/s. Impressionnant, et complètement hors de propos pour cette même base de données.

Dans l’univers ZFS, ce décalage s’aggrave parce que la pile est honnête mais compliquée :
ZFS a la compression, les sommes de contrôle, le copy-on-write, un grand cache ARC, le préfetch, et les groupes de transactions.
Ces fonctionnalités ne sont pas « lentes ». Elles sont juste spécifiques. Elles récompensent certains schémas d’IO et punissent d’autres.

Si vous ne retenez qu’une chose, que ce soit celle-ci :
Arrêtez d’utiliser un seul graphique « MB/s » pour décider de la santé du stockage.
Quand la latence est le symptôme visible par l’utilisateur, mesurez la latence en premier. Ensuite, cartographiez cette latence vers IOPS vs débit et vers le goulot d’étranglement réel (CPU, files d’attente vdev, chemin sync, fragmentation, métadonnées, réseau ou application).

Blague #1 : Si vous avez benchmarké votre pool avec un seul test de lecture séquentielle et l’avez déclaré « rapide », félicitations — vous avez mesuré votre capacité à lire le fichier du benchmark.

IOPS, débit, latence : les trois chiffres qui comptent (et comment ils se relient)

IOPS

IOPS signifie « opérations d’IO par seconde ». Il compte les requêtes IO complétées, pas les octets.
Cela compte le plus quand l’application effectue beaucoup de petites lectures/écritures : bases de données, disques de VM, charges lourdes en métadonnées, boîtes mail, caches CI de petits fichiers.

Les IOPS ne sont pas gratuits. Chaque IO a un surcoût : appel système, tenue de compte du système de fichiers, somme de contrôle, allocation, ordonnancement vdev, accès au média, achèvement.
Votre pool peut avoir un débit élevé et des IOPS faibles parce qu’il est performant pour les transferts séquentiels volumineux mais médiocre pour beaucoup de petites opérations aléatoires.

Débit

Le débit (MB/s ou GB/s) est le nombre total d’octets déplacés par seconde. Il compte surtout pour le streaming : sauvegardes, traitement média, ETL de gros fichiers, réplication d’objets.
Pour l’IO séquentiel, une seule file d’attente avec une profondeur suffisante de requêtes en attente peut saturer le débit avec étonnamment peu d’IOPS.

Latence

La latence est le temps par IO (moyenne, p95, p99). C’est généralement la métrique ressentie par les humains.
Une base de données qui attend 15 ms par lecture se fiche que votre pool atteigne 3 GB/s si elle a demandé 8 KiB et l’a reçu en retard.

En termes pratiques : la latence vous dit si vous êtes en difficulté ; IOPS/débit vous disent pourquoi.

La relation que vous ignorez

Si la taille IO est petite, vous avez besoin de beaucoup d’IOPS pour obtenir un débit décent.
Si la taille IO est grande, vous pouvez obtenir un débit élevé avec des IOPS modérés.
ZFS complique la taille IO parce que la taille IO de l’application, le recordsize ZFS, et la taille de secteur physique ne correspondent pas toujours.

De plus, une charge peut être à la fois limitée par le débit et par les IOPS à différents moments.
Exemple : un hôte VM peut avoir des lectures aléatoires de 8 KiB en état stable (sensibles aux IOPS/latence), puis effectuer de larges écritures séquentielles lors des fenêtres de sauvegarde (sensibles au débit).
Un pool, deux goulots, une personne d’astreinte.

Où ZFS passe du temps : le chemin de performance en termes simples

Le diagnostic de performance ZFS devient plus simple quand vous pensez en couches. Pas « ZFS est lent ». Plutôt : « cette IO attend à quelle étape ? »

Chemin de lecture (simplifié)

  1. ARC hit ? Si oui, vous êtes principalement en RAM et CPU. La latence est en microsecondes à faibles millisecondes selon la charge système.
  2. ARC miss → lecture vdev. Vous êtes maintenant à la merci de la latence disque, de la profondeur de file d’attente et de l’ordonnancement.
  3. Sommess de contrôle & décompression. Coût CPU, parfois non négligeable avec NVMe rapide ou compression lourde.
  4. Prefetch peut aider les lectures séquentielles, ou nuire aux lectures aléatoires s’il pollue l’ARC.

Chemin d’écriture (simplifié, et là où des gens se font virer)

  1. Écritures asynchrones atterrissent en mémoire et sont engagées sur disque dans des groupes de transactions (TXGs). C’est généralement rapide… jusqu’à ce que la mémoire soit pleine ou que le pool ne puisse pas vider assez vite.
  2. Écritures synchrones doivent être engagées de façon sûre avant accusé de réception. Sans journal dédié (SLOG), cela signifie toucher les vdevs principaux avec des garanties de faible latence.
  3. Copy-on-write change l’histoire de la « réécriture ». ZFS alloue de nouveaux blocs et met à jour les métadonnées. La fragmentation peut devenir un impôt sur les charges aléatoires au fil du temps.

Les vdevs sont l’unité de parallélisme

ZFS stripe à travers les vdevs, pas à travers des disques individuels comme certaines personnes le supposent vaguement.
Un seul vdev RAIDZ a un profil d’IOPS limité (surtout pour les écritures aléatoires) comparé à plusieurs vdevs en miroir.
Si vous voulez plus d’IOPS aléatoires, vous ajoutez typiquement plus de vdevs, pas « des disques plus gros ».

La mise en file d’attente est l’endroit où votre latence meurt

La plupart des incidents de performance sont des incidents de mise en file d’attente.
Les disques ne sont pas tant « lents » que « occupés », et occupé signifie que les requêtes attendent.
Cette attente apparaît comme de la latence. Le pool peut encore afficher un débit respectable, parce que des octets continuent de bouger, mais chaque IO individuel attend en file.

Empreintes de charge : à quoi ressemblent « IOPS-bound » et « throughput-bound »

Limités par les IOPS (petits IO aléatoires)

Empreintes :

  • Latence élevée même si MB/s est modeste.
  • Taille IO petite (4–16 KiB typique).
  • Profondeur de file augmente sous charge ; les disques montrent des symptômes élevés de await ou de type svctm selon l’outil.
  • CPU souvent pas saturé ; vous attendez le stockage.
  • Datasets ZFS avec recordsize petit ou zvols avec volblocksize petit tendent à amplifier le phénomène.

Coupables typiques :
hôtes VM, bases OLTP, arbres de fichiers riches en métadonnées, conteneurs avec systèmes de fichiers en couches frappant de petits fichiers.

Limités par le débit (IO séquentiels volumineux)

Empreintes :

  • MB/s élevé et latence stable jusqu’à saturation.
  • Taille IO grande (128 KiB à plusieurs MiB).
  • CPU peut devenir le goulot si compression/sommes de contrôle chauffent.
  • Réseau devient souvent le plafond (10/25/40/100GbE), surtout avec des clients NFS/SMB.

Coupables typiques :
flux de sauvegarde, pipelines média, scans analytiques massifs, réplication et opérations de resilver.

Le piège de la « charge mixte »

Votre pool peut paraître excellent la nuit (graphiques de débit de sauvegarde) et terrible à midi (latence interactive).
Si vous dimensionnez pour le débit seul, vous livrerez un système qui fond sous IO aléatoire.
Si vous dimensionnez pour les IOPS seuls, vous pourriez acheter des SSD coûteux alors qu’il vous fallait surtout plus de réseau ou un meilleur agencement séquentiel.

Faits intéressants et contexte historique (qui a vraiment de l’utilité)

  • ZFS a été conçu chez Sun pour l’intégrité des données en priorité : sommes de contrôle de bout en bout et copy-on-write étaient des fonctionnalités centrales, pas des ajouts.
  • RAIDZ existe pour éviter le write hole de RAID-5, échangeant une partie de la performance des petites écritures contre des garanties plus fortes.
  • L’ARC n’est pas « juste un cache » ; c’est un cache algorithmique auto-ajustable avec conscience des métadonnées, et il peut changer le comportement de la charge de manière spectaculaire.
  • Les disques à secteurs 4K ont tout changé : des valeurs ashift mal alignées ont causé des effondrements de performance en conditions réelles et des amplifications d’écriture imprévisibles.
  • Prefetch a été conçu pour les lectures en streaming ; sur des charges riches en IO aléatoire il peut gaspiller l’ARC et le budget IO si vous ne comprenez pas quand il se déclenche.
  • Les dispositifs SLOG sont devenus populaires parce que les écritures sync sont devenues réelles quand la virtualisation et les bases de données ont commencé à appeler fsync massivement par défaut.
  • La compression est passée de « coût CPU » à « fonctionnalité de performance » à mesure que les CPU sont devenus plus rapides et le stockage relativement plus lent ; moins d’octets peut signifier moins d’IO.
  • Les vdevs spéciaux sont une réponse moderne à un vieux problème : les métadonnées et petits blocs sont sensibles à la latence tandis que les données en masse peuvent être plus lentes.
  • Le marketing IOPS vient de l’ère HDD, où l’accès aléatoire était brutalement lent et les « seeks per second » définissaient pratiquement les classes de performance.

Mode d’emploi pour un diagnostic rapide

Voici l’ordre que j’utilise quand on m’appelle. Il est biaisé vers obtenir le bon goulot d’étranglement avant de toucher aux réglages.

Premier : prouver s’il s’agit de latence, IOPS, débit, CPU ou réseau

  1. Vérifiez la latence et la mise en file sur le serveur (pas le client) : iostat, zpool iostat, et p95/p99 de l’application.
  2. Vérifiez la taille et le patron IO : aléatoire vs séquentiel, sync vs async, lecture vs écriture. Si vous ne connaissez pas le patron, votre benchmark est de la fan fiction.
  3. Vérifiez le taux de hit ARC : si l’ARC vous sauve, les disques peuvent être innocents.

Second : localiser le goulot à un vdev, un dataset/zvol ou un chemin

  1. Quel vdev est chaud ? Les mirrors vs RAIDZ se comportent très différemment sous écritures aléatoires.
  2. Les sync sont-ils le problème ? Recherchez la latence des écritures sync et l’état du SLOG.
  3. Les métadonnées sont-elles en cause ? Les charges sur petits fichiers meurent souvent sur les IOPS de métadonnées, pas sur le débit des données.

Troisième : décidez du levier

  • Si c’est IOPS-bound : ajoutez des vdevs, passez aux mirrors, utilisez un vdev spécial pour métadonnées/petits blocs, corrigez recordsize/volblocksize, réduisez la pression sync ou ajoutez un vrai SLOG.
  • Si c’est throughput-bound : vérifiez le réseau, vérifiez le CPU pour compression/sommes de contrôle, élargissez les stripes (plus de vdevs), et utilisez un recordsize plus grand pour les datasets de streaming.
  • Si c’est latence due à la mise en file : réduisez la concurrence, isolez les voisins bruyants, définissez primarycache/secondarycache sensés, et arrêtez de mélanger des charges qui se détestent.

Tâches pratiques : commandes, ce que signifie la sortie, et quelle décision prendre

Ce sont de véritables tâches « faites ça maintenant ». Chacune a une commande, une sortie d’exemple, ce que cela signifie, et la décision qu’elle entraîne.
Exécutez-les sur l’hôte de stockage autant que possible. Les métriques côté client sont utiles, mais elles mentent par omission.

Task 1: Identify pool topology (because vdev layout is destiny)

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

        NAME                        STATE     READ WRITE CKSUM
        tank                        ONLINE       0     0     0
          mirror-0                  ONLINE       0     0     0
            nvme0n1p2               ONLINE       0     0     0
            nvme1n1p2               ONLINE       0     0     0
          raidz1-1                  ONLINE       0     0     0
            sda2                    ONLINE       0     0     0
            sdb2                    ONLINE       0     0     0
            sdc2                    ONLINE       0     0     0

errors: No known data errors

Signification : Ce pool mélange un vdev mirror et un vdev RAIDZ1. C’est permis, mais les caractéristiques de performance diffèrent énormément ; l’allocation va striper à travers les vdevs et la partie la plus lente/contendée peut dominer la latence.

Décision : Si vous tenez à une latence cohérente, évitez de mélanger les types de vdev dans le même pool. Si c’est déjà en place, envisagez de séparer les charges par pool.

Task 2: Observe pool IO at a high level (IOPS vs MB/s)

cr0x@server:~$ sudo zpool iostat -v tank 1 5
               capacity     operations     bandwidth
pool         alloc   free   read  write   read  write
----------  -----  -----  -----  -----  -----  -----
tank         3.21T  5.89T  3.21K  1.87K   110M  42.3M
  mirror-0    920G  1.81T  2.95K    210  98.4M  3.21M
    nvme0n1p2     -      -  1.48K    105  49.2M  1.60M
    nvme1n1p2     -      -  1.47K    105  49.2M  1.61M
  raidz1-1    2.29T  4.08T    260  1.66K  11.7M  39.1M
    sda2          -      -     90    560  4.10M  13.0M
    sdb2          -      -     86    550  3.96M  13.1M
    sdc2          -      -     84    545  3.64M  13.0M

Signification : Le vdev RAIDZ prend la majorité des écritures (1.66K ops), tandis que les lectures sont dominées par le mirror. Cela signifie probablement que la charge est lourde en écritures et tombe là où la latence sera pire.

Décision : Si la latence compte, déplacez les datasets sensibles aux écritures vers un pool composé de mirrors ou de vdevs SSD ; ou redessinez les vdevs. Ne « tunez » pas pour compenser un mauvais topo.

Task 3: Check latency/queueing at the block layer

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

avg-cpu:  %user   %nice %system %iowait  %steal   %idle
          12.44    0.00    6.21    9.87    0.00   71.48

Device            r/s     w/s   rMB/s   wMB/s  rrqm/s  wrqm/s  %util  await  r_await  w_await
nvme0n1          480.0   42.0   62.0     1.9     0.0     8.0   38.2   1.20    1.10    2.30
nvme1n1          475.0   41.0   61.8     1.9     0.0     7.0   37.9   1.18    1.08    2.25
sda               22.0  180.0    1.2    20.5     0.0    15.0   96.8  24.40    9.20   26.10
sdb               21.0  178.0    1.2    20.2     0.0    14.0   95.9  25.10    9.50   26.70
sdc               20.0  175.0    1.1    19.9     0.0    13.0   95.1  26.00   10.10   27.20

Signification : Les HDD sont à ~95% d’utilisation avec ~25 ms d’attente. NVMe est correct (~1.2 ms). C’est du queueing classique : les petits IO vers des vdevs HDD seront punis.

Décision : Si votre charge nécessite une faible latence, arrêtez de l’envoyer vers des vdevs HDD saturés. Réduisez la concurrence, déplacez des datasets, ou ajoutez des vdevs/SSDs. Ne vous focalisez pas sur MB/s.

Task 4: Identify whether sync writes are involved

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

Signification : standard signifie que l’application décide sync vs async (fsync/O_DSYNC). Beaucoup de bases de données et hyperviseurs forcent les sémantiques sync.

Décision : Si vous voyez une forte latence d’écriture et que l’appli est fsync-heavy, investiguez SLOG et le chemin sync. Ne « corrigez » pas ça en mettant sync=disabled à moins d’aimer les postmortems de perte de données.

Task 5: Check presence and health of SLOG (separate log)

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

        NAME                        STATE     READ WRITE CKSUM
        tank                        ONLINE       0     0     0
          mirror-0                  ONLINE       0     0     0
            nvme0n1p2               ONLINE       0     0     0
            nvme1n1p2               ONLINE       0     0     0
          raidz1-1                  ONLINE       0     0     0
            sda2                    ONLINE       0     0     0
            sdb2                    ONLINE       0     0     0
            sdc2                    ONLINE       0     0     0
        logs
          mirror-2                  ONLINE       0     0     0
            nvme2n1p1               ONLINE       0     0     0
            nvme3n1p1               ONLINE       0     0     0

errors: No known data errors

Signification : Un SLOG en miroir existe. Bien : la latence des écritures sync peut être limitée par ces appareils, pas par le RAIDZ HDD.

Décision : Validez que les dispositifs SLOG ont une protection contre la perte d’alimentation et ne sont pas des NVMe grand public faisant semblant d’être fiables. Si SLOG est absent et que les sync sont lourds, envisagez d’en ajouter un — mais seulement après avoir confirmé que la charge est sync-bound.

Task 6: Check dataset recordsize (throughput vs random-read behavior)

cr0x@server:~$ sudo zfs get -o name,property,value recordsize tank/app
NAME      PROPERTY    VALUE
tank/app  recordsize  128K

Signification : 128K est une valeur par défaut solide pour des fichiers généraux. Pour des bases de données faisant des pages de 8K, cela peut causer une amplification de lecture (lire plus que nécessaire) et impacter la latence lors de misses du cache.

Décision : Pour les datasets de bases, envisagez recordsize=16K ou 8K selon la taille de page DB et le patron d’accès. Pour les datasets de streaming, gardez-le plus grand (128K–1M).

Task 7: Check zvol volblocksize (VMs live and die here)

cr0x@server:~$ sudo zfs get -o name,property,value volblocksize tank/vmstore/vm-001
NAME                  PROPERTY      VALUE
tank/vmstore/vm-001   volblocksize  8K

Signification : 8K correspond à de nombreux patrons d’IO aléatoire VM, mais peut réduire le débit séquentiel et augmenter l’overhead métadonnées. Trop petit signifie aussi plus d’IO pour les mêmes octets.

Décision : Choisissez volblocksize par workload avant d’écrire les données (c’est souvent fixe après création). Pour des charges VM mixtes, 8K–16K est courant ; pour du séquentiel massif, plus grand peut aider.

Task 8: Check ARC stats (are you disk-bound or cache-bound?)

cr0x@server:~$ arcstat 1 3
    time  read  miss  miss%  dmis  dm%  pmis  pm%  mmis  mm%  arcsz     c
12:10:01  3200   420     13   180    5   210    7    30    1   96G   110G
12:10:02  3400   600     17   210    6   360   11    30    1   96G   110G
12:10:03  3100   380     12   160    5   190    6    30    1   96G   110G

Signification : Taux de miss ~12–17%. Pas terrible, mais sous un incident de latence même « seulement » 15% de misses peut être catastrophique si les misses vont vers des vdevs lents.

Décision : Si les misses corrèlent avec des pics de latence, vous êtes lié par la latence disque. Envisagez plus de RAM, un vdev spécial, ou déplacer le working set chaud vers des vdevs plus rapides.

Task 9: Check compression ratio (it can be free throughput)

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

Signification : 1.62x signifie que vous écrivez moins d’octets que l’application ne le pense. Cela améliore souvent à la fois le débit et les IOPS (moins d’IO physique), au prix de CPU.

Décision : Si le CPU n’est pas saturé et que les données se compressent, laissez la compression activée. Si le CPU est saturé et que le stockage est inactif, évaluez changer le niveau de compression ou déplacer la compression hors des chemins chauds.

Task 10: Confirm ashift (alignment) to avoid silent write amplification

cr0x@server:~$ sudo zdb -C tank | sed -n '/ashift/,+2p'
        ashift: 12
        asize: 7.99T
        is_log: 0

Signification : ashift=12 signifie secteurs 4K. Bonne base. Si vous aviez ashift=9 sur des disques 4K, vous risqueriez des pénalités read-modify-write et un comportement d’écriture petit et moche.

Décision : Si ashift est incorrect, vous ne le « tunez » pas. Vous reconstruisez les vdevs correctement. Oui, c’est pénible. Non, ce n’est pas optionnel.

Task 11: Check metadata/small-block acceleration (special vdev)

cr0x@server:~$ sudo zpool status tank | sed -n '/special/,+15p'
        special
          mirror-3                  ONLINE       0     0     0
            nvme4n1p1               ONLINE       0     0     0
            nvme5n1p1               ONLINE       0     0     0

Signification : Un vdev spécial existe. Si configuré avec un seuil de petits blocs, les métadonnées et petits blocs peuvent vivre sur du média rapide, améliorant IOPS/latence pour les charges de petits fichiers.

Décision : Si votre charge est riche en métadonnées ou en IO aléatoire 4–16K, un vdev spécial peut tout changer. Mais traitez-le comme un vdev de première classe : redondance et surveillance sont non négociables.

Task 12: Measure dataset-level IO behavior (who is noisy?)

cr0x@server:~$ sudo zfs iostat -r -v tank 1 3
                              capacity     operations     bandwidth
pool                       alloc   free   read  write   read  write
-------------------------  -----  -----  -----  -----  -----  -----
tank                        3.21T  5.89T  3.20K  1.86K   110M  42.0M
  tank/app                  820G  1.20T  1.90K   920    52.0M  18.0M
  tank/vmstore               1.1T  2.20T  1.10K   910    58.0M  22.0M
  tank/backups               1.2T  1.50T   200     30   0.8M  2.0M
-------------------------  -----  -----  -----  -----  -----  -----

Signification : Le dataset app et le VM store dominent l’IO. Les backups sont calmes pour l’instant.

Décision : Si vous avez besoin d’isolation, placez les VMs et bases sur des pools séparés ou au moins des classes de vdev séparées. Tenter de « fair share » la latence stockage dans un seul pool est une source de cheveux gris.

Task 13: Determine if you’re CPU-bound due to checksumming/compression

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

12:12:01 PM  CPU   %usr %nice  %sys %iowait  %irq  %soft  %steal  %idle
12:12:02 PM  all  62.0   0.0  25.0     1.0   0.0    1.0     0.0   11.0
12:12:02 PM   0  78.0   0.0  19.0     0.0   0.0    0.0     0.0    3.0
12:12:02 PM   1  80.0   0.0  17.0     0.0   0.0    0.0     0.0    3.0

Signification : Le CPU est fortement utilisé tandis que iowait est bas. C’est un indice que le média de stockage peut être correct et que vous brûlez du CPU sur la compression, les sommes de contrôle, le chiffrement, ou simplement la charge elle-même.

Décision : Avant d’acheter des disques, profilez le CPU et vérifiez le coût des fonctionnalités ZFS. Si vous êtes lié par le CPU, des disques plus rapides n’aideront pas ; plus de CPU ou des choix de compression/chiffrement différents pourraient.

Task 14: Run a truthful fio test for random read latency

cr0x@server:~$ fio --name=randread4k --filename=/tank/app/fio-testfile --size=8G --rw=randread --bs=4k --iodepth=32 --numjobs=4 --direct=1 --runtime=30 --time_based --group_reporting
randread4k: (groupid=0, jobs=4): err= 0: pid=27144: Thu Dec 25 12:13:40 2025
  read: IOPS=48210, BW=188MiB/s (197MB/s)(5640MiB/30001msec)
    slat (nsec): min=1100, max=220000, avg=5200.4, stdev=3400.1
    clat (usec): min=70, max=9200, avg=640.8, stdev=310.5
     lat (usec): min=80, max=9300, avg=650.9, stdev=312.0
    clat percentiles (usec):
     |  1.00th=[  160],  5.00th=[  240], 10.00th=[  290], 50.00th=[  610]
     | 90.00th=[ 1050], 95.00th=[ 1400], 99.00th=[ 2100], 99.90th=[ 4200]

Signification : Lectures aléatoires 4K : ~48K IOPS, ~188 MiB/s, p99 ~2.1 ms. C’est une histoire d’IOPS/latence, pas de débit.

Décision : Comparez cela aux besoins de l’application. Si l’appli nécessite un p99 sous la milliseconde et que vous n’y êtes pas, il vous faut des vdevs plus rapides, plus de parallélisme vdev, un meilleur cache, ou moins de pression sync — pas « plus de GB/s ».

Task 15: Run a sequential throughput fio test (separate from random)

cr0x@server:~$ fio --name=seqread1m --filename=/tank/backups/fio-testfile --size=32G --rw=read --bs=1m --iodepth=16 --numjobs=2 --direct=1 --runtime=30 --time_based --group_reporting
seqread1m: (groupid=0, jobs=2): err= 0: pid=27201: Thu Dec 25 12:15:12 2025
  read: IOPS=3020, BW=3020MiB/s (3168MB/s)(90615MiB/30005msec)
    clat (usec): min=230, max=12000, avg=820.3, stdev=310.4

Signification : Vous pouvez faire ~3 GiB/s en lecture séquentielle avec des IO de 1 MiB. Parfait pour les sauvegardes/flux.

Décision : Utilisez ceci pour dimensionner les fenêtres de réplication et la performance des sauvegardes. Ne l’utilisez pas pour prétendre que la base de données sera rapide.

Task 16: Check TXG pressure (are writes piling up?)

cr0x@server:~$ cat /proc/spl/kstat/zfs/txg | sed -n '1,80p'
13 1 0x01 7 336 4257451234 123456789
name                            type data
birth                           4    1756114458
state                           4    1
txg                             4    231948
g__active                       4    231949
g__opened                       4    231948
g__quiescing                    4    231947
g__syncing                      4    231946
ndirty                          4    1948723200
dirty_max                       4    4294967296
delay                           4    0

Signification : ndirty est grand mais en dessous de dirty_max, et delay est 0. Si delay augmente ou ndirty atteint le max, les applications peuvent être throttlées ; des pics de latence suivent souvent.

Décision : Si les TXG sont en peine, vous êtes limité par le flush d’écriture. Regardez la latence d’écriture vdev, la charge sync, et si un RAIDZ lent encombre la pipeline.

Trois mini-récits du monde de l’entreprise (anonymisés, plausibles et techniquement irritants)

1) L’incident causé par une mauvaise hypothèse : « Mais le pool fait 5 GB/s »

Une entreprise SaaS de taille moyenne a migré un service de paiement depuis un ancien SAN vers un bel appareil ZFS tout neuf.
La checklist de migration contenait les éléments habituels : calendrier de scrub, snapshots, réplication, alerting. La section performance avait une ligne : « débit validé avec dd ».
Ils ont lancé une grande lecture séquentielle, obtenu un chiffre héroïque, et mis en production.

Deux semaines plus tard, ils ont déployé une fonctionnalité qui a augmenté le volume de transactions et ajouté quelques index secondaires.
La base ne s’est pas écrasée. Elle est juste devenue lente. La latence a augmenté graduellement jusqu’à ce que l’API commence à expirer.
Le canal incident s’est rempli de graphiques montrant que le pool ne faisait que quelques centaines de MB/s. « Nous avons de la marge », a dit quelqu’un, et tout le monde a hoché la tête parce que MB/s paraissait bas.

Le vrai problème était la latence de lecture aléatoire. Le working set ne tenait plus dans l’ARC, donc les misses du cache allaient sur un vdev RAIDZ de HDD.
Chaque requête nécessitait des dizaines de lectures de 8–16 KiB, et ces lectures faisaient la queue derrière d’autres IO aléatoires.
Le débit restait modeste parce que la taille IO était petite ; les IOPS étaient le goulot et la mise en file multipliait la douleur.

Ils ont corrigé en déplaçant le dataset de la base vers un pool SSD mirror-only et en réglant un recordsize approprié.
Le graphique de débit a à peine changé. La latence p95 a chuté de façon spectaculaire. L’équipe a appris à la dure que les utilisateurs ne vous paient pas en gigaoctets par seconde.

2) L’optimisation qui a mal tourné : « Forçons des blocs plus gros pour la performance »

Une autre entreprise gérait une flotte d’hôtes VM sur ZFS avec des zvols.
Quelqu’un a remarqué que le débit séquentiel n’était pas incroyable pendant les fenêtres de sauvegarde et a décidé d’« optimiser » : ils ont standardisé tout sur des tailles de bloc supérieures.
Des datasets ont reçu un recordsize plus grand ; certains zvols ont été recréés avec un volblocksize plus grand ; la demande de changement clamait fièrement « moins d’IOs ».

Les sauvegardes sont devenues un peu plus rapides. Puis la file des tickets a commencé à se remplir de plaintes : les VMs étaient lentes, surtout lors des patches et des vagues de connexion.
Les graphiques montraient une latence plus élevée mais aussi un débit plus élevé aux heures de pointe, ce qui semblait être un succès pour quiconque restait en mode bande passante.

Le problème était l’amplification de lecture et l’inefficacité du cache. Beaucoup de VMs faisaient des lectures/écritures aléatoires de 4–8 KiB.
Des blocs plus gros signifiaient que chaque petit accès ramenait plus de données que nécessaire, polluant l’ARC et générant plus d’IO physique lors des misses du cache.
Pire, les écritures aléatoires provoquaient plus de travail par opération, et la capacité effective d’IOPS du pool a chuté.

Ils sont revenus en arrière pour les zvols VM : volblocksize plus petit, et ils ont séparé les flux de sauvegarde vers un dataset/pool différent.
La leçon est simple : on n’optimise pas le stockage en rendant tout « gros ». On l’optimise en adaptant le comportement des blocs à la charge.

3) La pratique ennuyeuse mais correcte qui a sauvé la mise : « Nous avons gardé sync honnête »

Une entreprise financière gérait du stockage NFS pour quelques systèmes critiques, incluant un cluster de bases et une file de messages.
L’équipe de stockage avait une politique qui agaçait les développeurs : les sémantiques sync restaient activées, et les « correctifs de performance » impliquant la désactivation de la sécurité nécessitaient une approbation de risque.
Ce n’était pas populaire, mais c’était cohérent.

Lors d’un rafraîchissement matériel, un vendeur a suggéré un gain rapide : régler sync=disabled sur les datasets chauds et « faire hurler » le système.
L’équipe de stockage a refusé et a plutôt ajouté un SLOG miroir construit sur des appareils protégés contre la perte d’alimentation, puis a mesuré la latence fsync sous charge.
Le résultat n’était pas magique, mais il était stable. Plus important : il était prévisible.

Des mois plus tard, ils ont eu un événement de coupure de courant qui a pris un PDU de baie d’une manière qui a réduit la durée d’alimentation des onduleurs plus que prévu.
Certains systèmes sont tombés brutalement. Le stockage est revenu propre.
Le postmortem était ennuyeux — pas de perte de données, pas de corruption, pas de comportement de replay bizarre — et l’ennui était le but.

Si vous voulez une citation sur la fiabilité à accrocher sur votre bureau, en voici une que je fais confiance parce qu’elle est opérationnellement actionnable :
« L’espoir n’est pas une stratégie. » — General Gordon R. Sullivan

Les réglages ZFS qui changent IOPS vs débit (et ceux qui ne changent rien)

Topologie : mirrors vs RAIDZ

Les mirrors gagnent généralement sur les IOPS de lecture aléatoire et souvent sur la latence des écritures aléatoires. RAIDZ tend à gagner sur l’efficacité de capacité et souvent sur le débit séquentiel par euro, mais il paye une taxe sur les petites écritures aléatoires (maths de parité + schémas d’IO).
Si votre charge est sensible aux IOPS/latence, les mirrors sont la réponse par défaut sauf raison forte contraire.

recordsize (datasets) et volblocksize (zvols)

Ces réglages influencent l’amplification IO et l’efficacité du cache.
Les blocs plus grands aident le débit séquentiel et réduisent l’overhead métadonnées. Les blocs plus petits peuvent réduire l’amplification de lecture pour les petites lectures aléatoires.
Définissez-les selon la taille IO et le patron d’accès de l’application. Si vous ne connaissez pas le patron d’appli, découvrez-le. Deviner est la façon de construire des déceptions coûteuses.

sync, SLOG, et la vraie signification de « écritures rapides »

Les écritures sync concernent les garanties de durabilité. Si l’application l’exige, ZFS doit engager de façon sûre avant l’accusé de réception.
Un bon SLOG peut réduire la latence des écritures sync en prenant ce coût de durabilité sur un appareil rapide à faible latence, puis en flushant plus tard vers le stockage principal.

Un mauvais SLOG (SSD grand public sans PLP, surchargé, ou partagé avec d’autres charges) est pire que pas de SLOG : il devient un goulot et ajoute un risque de défaillance.

vdev spécial pour métadonnées et petits blocs

Si votre douleur vient des IO métadonnées (beaucoup de petits fichiers, traversées de répertoires, boîtes mail) ou des lectures aléatoires de petits blocs, les vdevs spéciaux peuvent déplacer les parties les plus chaudes et sensibles à la latence vers des SSD.
C’est souvent une correction plus propre que d’ajouter de la RAM à l’ARC quand le working set est trop grand.

Compression

La compression change la donne. Si vous compressez 2:1, vous divisez par deux les octets physiques et souvent le temps d’IO physique.
Mais vous dépensez du CPU. Sur des pools HDD c’est généralement un gain. Sur des pools NVMe en throughput extrême, le CPU peut devenir le plafond.

Ce sur quoi il ne faut pas s’obséder

De minuscules « tunables » ne corrigeront pas un mauvais appariement charge/topologie.
Si vous avez des RAIDZ HDD servant des écritures aléatoires 8K sync, aucun sysctl ne vous sauvera. Les disques devront quand même faire le travail, une opération douloureuse à la fois.

Blague #2 : Tuner sans mesurer, c’est comme déboguer les yeux fermés — techniquement possible, mais surtout une forme de danse interprétative.

Erreurs courantes : symptômes → cause racine → correction

1) « Le débit est faible, le stockage doit être correct » (alors que la latence est affreuse)

Symptômes : Timeouts applicatifs, latence p95/p99 élevée, mais les graphiques MB/s semblent modestes.

Cause racine : Charge limitée par les IOPS avec petite taille IO ; mise en file sur vdevs ; misses de cache touchant des medias lents.

Correction : Mesurez la taille IO et la latence ; ajoutez du parallélisme vdev (plus de mirrors), déplacez les datasets chauds vers SSD, ajoutez un vdev spécial, ou augmentez l’ARC si le working set tient vraiment.

2) « Nous avons acheté des disques plus rapides, c’est toujours lent »

Symptômes : NVMe partout, mais gains de performance faibles ; CPU chaud ; latence qui ne s’améliore pas comme prévu.

Cause racine : Lié CPU (compression/chiffrement/sommes de contrôle), ou le goulot est le réseau/NFS/SMB, ou l’application est sérialisée (IO mono-thread).

Correction : Vérifiez l’utilisation CPU et le réseau ; profilez la concurrence applicative ; validez que les clients peuvent émettre des IO en parallèle ; toquez au bon niveau.

3) « Les écritures aléatoires se sont effondrées après que le pool s’est rempli »

Symptômes : Pool neuf était rapide ; après des mois, pics de latence d’IO aléatoire ; libérer de l’espace aide un peu.

Cause racine : Fragmentation + pression d’allocation en copy-on-write, plus moins d’espace libre conduisant à une allocation de blocs moins efficace.

Correction : Gardez une marge d’espace libre saine, surtout sur RAIDZ HDD ; utilisez un vdev spécial pour métadonnées/petits blocs ; envisagez de réécrire ou migrer des datasets ; évitez les patterns d’écrasement pathologiques sur RAIDZ pour des workloads chauds aléatoires.

4) « Les écritures sync nous tuent, alors on a désactivé sync »

Symptômes : La performance « s’améliore » immédiatement ; plus tard vous voyez de la corruption après un crash, ou la réplication envoie des blocs cassés.

Cause racine : Échanger la durabilité contre la vitesse ; le système était sync-bound et avait besoin d’un vrai SLOG ou d’un changement de charge.

Correction : Restaurez sync=standard ; ajoutez un SLOG miroir protégé contre perte d’alimentation ; ou changez le comportement applicatif en connaissance de cause (batch fsync, group commit), pas en lui mentant.

5) « Nous avons changé recordsize et obtenu une pire performance »

Symptômes : Les workloads séquentiels s’améliorent, mais les workloads interactifs se dégradent ; le taux de hit du cache chute ; plus d’IO pour le même travail.

Cause racine : Taille de bloc mal alignée vs workload ; amplification de lecture ; pollution ARC.

Correction : Alignez recordsize/volblocksize au pattern d’accès : petit pour les lectures aléatoires DB, grand pour le streaming. Séparez les charges en datasets différents et appliquez des propriétés par dataset.

6) « Un seul pool pour tout »

Symptômes : Les sauvegardes ralentissent les bases ; les scrubs font jitter les VMs ; performance imprévisible.

Cause racine : Schémas IO concurrents ; pas d’isolation ; files vdev partagées.

Correction : Séparez les pools ou au moins les classes de vdev ; planifiez scrubs/resilvers ; limitez les jobs bulk ; envisagez des cibles de sauvegarde dédiées.

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

Étape par étape : decider si vous êtes IOPS-bound ou throughput-bound

  1. Collectez la latence (p95/p99) depuis l’appli et depuis l’hôte de stockage (iostat -x).
  2. Collectez la distribution des tailles IO en utilisant des tests fio qui correspondent à l’appli (4K aléatoire, 8K écritures sync, 1M lectures séquentielles, etc.).
  3. Calculez le débit implicite : IOPS × taille IO. Si votre MB/s observé correspond au calcul, les métriques sont cohérentes et vous pouvez raisonner proprement.
  4. Vérifiez le taux de miss ARC. Taux de miss élevé avec await/util disque élevé signifie que vous êtes lié par les disques ; taux de miss élevé avec await disque bas suggère autre chose (CPU/réseau/appli).
  5. Inspectez l’utilisation vdev via zpool iostat -v. Identifiez le vdev le plus chaud et pourquoi il est chaud.

Checklist : concevoir pour des IOPS aléatoires (bases, VMs)

  • Privilégiez plusieurs vdevs mirror plutôt qu’un petit nombre de vdevs RAIDZ larges.
  • Réglez recordsize/volblocksize pour correspondre aux tailles IO attendues.
  • Envisagez un vdev spécial pour métadonnées/petits blocs.
  • Gardez une marge d’espace libre (ne faites pas tourner les pools « presque pleins » puis soyez surpris).
  • Assurez-vous que le chemin sync est sain : soit un SLOG correct, soit acceptez la latence et concevez autour.

Checklist : concevoir pour le débit séquentiel (sauvegardes, ETL)

  • Utilisez un recordsize plus grand pour les gros fichiers.
  • Mesurez les plafonds réseau ; le stockage peut ne pas être votre goulot.
  • Confirmez la marge CPU si vous utilisez compression/chiffrement.
  • Séparez les jobs séquentiels de masse des datasets interactifs sensibles à la latence si possible.

Checklist opérationnelle : avant de toucher aux tunables

  • Capturez zpool status, zpool iostat -v, iostat -x pendant l’incident.
  • Capturez les stats ARC et les indicateurs de pression mémoire.
  • Notez le patron de workload (sync ? aléatoire ? taille IO ?). Si vous ne pouvez pas, arrêtez et instrumentez.
  • Faites un changement à la fois et re-mesurez. Le stockage n’est pas une discipline basée sur les vibes.

FAQ

1) Quelle métrique dois-je surveiller en premier sur ZFS : IOPS ou débit ?

Surveillez la latence en premier (p95/p99), puis mappez-la aux IOPS et à la taille IO. Le débit seul est un chiffre réconfortant et souvent le mauvais.

2) Pourquoi mon pool affiche un débit élevé mais ma base est lente ?

Votre pool peut être bon en lectures/écritures séquentielles (MB/s élevés) tandis que la base a besoin de IO aléatoires petits et à faible latence.
Si les lectures aléatoires manquent l’ARC et frappent un RAIDZ HDD, la latence domine et le débit ne reflète pas la douleur utilisateur.

3) Les mirrors sont-ils toujours plus rapides que RAIDZ ?

Pour les IOPS aléatoires et la latence, les mirrors gagnent habituellement.
Pour l’efficacité de capacité et souvent le débit séquentiel par euro, RAIDZ peut être attractif.
Choisissez selon la charge. « Toujours » est pour le marketing, pas pour l’ingénierie.

4) Ajouter des disques à un vdev RAIDZ augmente-t-il les IOPS ?

Cela peut augmenter le débit séquentiel, mais les IOPS aléatoires ne s’échelonnent pas linéairement comme les gens l’espèrent.
Si vous avez besoin d’IOPS aléatoires, vous ajoutez typiquement plus de vdevs (plus de files indépendantes), pas seulement un RAIDZ plus large.

5) Quand un SLOG aide-t-il ?

Quand vous avez une charge avec des écritures sync significatives (DB fsync-heavy, NFS avec sync, certains patterns VM) et que vos vdevs principaux ont une latence plus élevée.
Il n’aide pas les écritures async, et il ne résoudra pas la latence de lecture aléatoire.

6) sync=disabled est-il acceptable ?

C’est acceptable quand vous choisissez délibérément de mentir sur la durabilité et pouvez tolérer une perte de données en panne ou en crash.
La plupart des environnements de production ne le peuvent pas, et ceux qui prétendent pouvoir souvent changent d’avis après la première panne.

7) Dois-je changer recordsize pour ma base de données ?

Souvent oui, mais seulement en connaissance de cause. Si la taille de page DB est 8K et l’accès est aléatoire, un recordsize plus petit peut réduire l’amplification de lecture sur les misses de cache.
Benchmarquez avec des charges représentatives et surveillez la latence p95/p99, pas seulement MB/s.

8) Comment savoir si je suis lié par l’ARC ou par le disque ?

Si le taux de hit ARC est élevé et que les disques montrent un await/util bas, vous êtes probablement lié au cache/CPU/appli.
Si les misses ARC corrèlent avec un await/util disque élevé et une latence croissante, vous êtes lié par le disque et avez besoin de média plus rapide ou plus de parallélisme vdev (ou d’un working set plus petit).

9) La compression peut-elle améliorer les IOPS ?

Oui. Si les données se compressent, ZFS écrit moins d’octets et peut satisfaire les lectures plus vite (moins d’IO physique), ce qui peut améliorer à la fois le débit et les IOPS effectifs.
Si le CPU devient le goulot, le gain disparaît.

10) Pourquoi les benchmarks divergent-ils de la production ?

Parce que les benchmarks sont souvent séquentiels, amicaux au cache, et irréalistement profonds en file d’attente, tandis que la production est mixte, en rafales, et sensible à la latence.
Si votre benchmark ne correspond pas à la taille IO, au comportement sync, à la concurrence, et au working set, il mesure un univers différent.

Prochaines étapes que vous pouvez faire cette semaine

Si vous voulez arrêter de vous disputer sur le « stockage rapide » et commencer à livrer une performance prévisible, faites ceci dans l’ordre :

  1. Choisissez deux profils fio représentatifs : un petit bloc aléatoire (4K ou 8K) avec concurrence réaliste, et un gros bloc séquentiel (1M). Lancez-les sur les mêmes datasets que vos applis utilisent.
  2. Instrumentez la latence : capturez p95/p99 à l’application et à l’hôte de stockage pendant la charge de pointe.
  3. Auditez la topologie vs la charge : si vous exécutez des charges sensibles à la latence sur des vdevs RAIDZ HDD, acceptez la physique ou redessinez avec mirrors/SSD/vdev spécial.
  4. Corrigez la taille des blocs : alignez recordsize/volblocksize à la façon dont l’appli fait réellement des IO, pas à ce que vous voudriez.
  5. Validez le comportement sync : ne désactivez pas la sécurité en production ; ajoutez un SLOG approprié si les écritures sync sont le facteur limitant.
  6. Séparez les voisins bruyants : sauvegardes, scrubs, et réplication sont nécessaires. Ils ratissent aussi la latence interactive si vous les laissez partager les files sans contrôle.

Ensuite re-mesurez. Si vous ne pouvez pas montrer « avant vs après » en percentiles de latence et forme d’IO, vous n’avez pas fait de l’ingénierie — vous avez fait du wishful thinking avec des privilèges root.

← Précédent
Ubuntu 24.04 « grub rescue> » : revenir à un système amorçable en limitant les dégâts
Suivant →
L’IA partout : quand les étiquettes sont plus absurdes que les fonctionnalités

Laisser un commentaire