Si vous avez migré ZFS vers des « NVMe rapides » en espérant que la latence disparaisse, vous avez probablement rencontré les deux ennemis du stockage moderne : la latence de queue et la contention invisible. Vos benchs ont l’air héroïques, mais la production se fige encore pendant 200–800 ms au pire moment possible. Les utilisateurs se moquent que votre médiane soit de 80 µs lorsque leur page de paiement se situe au 99,9e percentile.
Les pools uniquement NVMe suppriment un goulot d’étranglement et exposent le reste. Désormais, les limites sont l’ordonnancement CPU, la distribution des interruptions, la mise en file, le comportement des groupes de transactions ZFS, et la différence entre « périphérique rapide » et « système rapide ». Visons la réalité : une latence stable sous charge mixte, pas seulement de jolis graphiques fio.
Les limites réelles : NVMe met le reste en évidence
Sur des disques magnétiques ou même des SSD SATA, la latence du stockage domine et masque beaucoup de fautes. Sur NVMe, le stockage n’est plus « la partie lente » la plupart du temps. C’est une bonne et une mauvaise nouvelle.
Bonne nouvelle : vous pouvez obtenir des IOPS hallucinants et un excellent débit avec très peu de réglages, surtout pour les lectures et les écritures asynchrones. Mauvaise nouvelle : le goulot se déplace. Si vous observez des pics de latence sur des pools ZFS uniquement NVMe, vos premiers suspects sont :
- Ordonnancement CPU et gestion des interruptions : un cœur effectuant tout le travail pendant que les autres restent inactifs.
- Mise en file dans la couche bloc ou le pilote NVMe : des files profondes masquent la congestion jusqu’au moment où elles ne le peuvent plus.
- Cadence des groupes de transactions ZFS (TXG) : rafales d’écriture de writeback toutes les quelques secondes, plus la sémantique sync.
- Pression mémoire et comportement de l’ARC : tempêtes de reclaim, kswapd et threads bloqués.
- Hypothèses du « chemin rapide » : p. ex. charges axées sur les métadonnées, petites écritures aléatoires et applications sync-intensives.
Vous réglez un système distribué sur une seule machine : threads applicatifs, threads noyau, interruptions, étapes du pipeline ZFS, et un contrôleur avec son propre firmware. NVMe n’élimine pas la complexité ; il cesse simplement de la masquer.
Faits intéressants et histoire (qui comptent réellement)
- ZFS n’est pas né à l’ère NVMe. Le design d’origine supposait des disques rotatifs et une coalescence d’écritures sur plusieurs secondes ; le comportement TXG en porte encore la trace.
- NVMe a été conçu pour le parallélisme. Plusieurs files de soumission/complétion existent spécifiquement pour réduire la contention de verrou et les tempêtes d’interruptions comparé à AHCI/SATA.
- La coalescence d’interruptions est antérieure au NVMe. Les cartes réseau échangent latence contre débit en regroupant les interruptions ; les périphériques NVMe et leurs pilotes font des choses similaires.
- Les secteurs 4K n’ont pas toujours été la norme. Advanced Format puis les réalités des blocs d’effacement des SSD ont forcé les discussions d’alignement ; ashift de ZFS est essentiellement « je ne fais pas confiance au périphérique pour dire la vérité ».
- TRIM/Discard faisait peur autrefois. Les premiers SSD pouvaient se bloquer sévèrement sur discard ; « ne pas activer TRIM » était un conseil raisonnable. Les NVMe modernes gèrent généralement mieux, mais pas toujours sans douleur sous charge.
- Le checksum n’est pas une taxe qu’on peut ignorer. Sur des médias rapides, CRC et compression peuvent devenir des coûts CPU de premier ordre, surtout avec de petites IO et une forte concurrence.
- Le ZIL n’est pas un cache d’écriture. Le journal d’intention existe pour satisfaire la sémantique sync POSIX ; le mal comprendre mène à des réglages étranges et des équipes déçues.
- Les chiffres d’IOPS sont devenus des armes marketing. « Millions d’IOPS » a poussé certains à oublier la latence de queue et les charges mixtes ; les ops ont dû rattraper cela.
Modèle mental : d’où vient la latence avec ZFS sur NVMe
Quand la latence se dégrade, vous avez besoin d’une carte. Voici la carte.
ZFS write pipeline, simplifié
- Application write() touche le cache de pages / logique ARC et la DMU de ZFS.
- Si sync est requis, ZFS doit engager l’intention dans le ZIL (in-pool ou SLOG) avant d’accuser réception.
- Phase d’ouverture TXG : les écritures s’accumulent en mémoire.
- Phase sync TXG : les données sont écrites dans le pool principal, les metaslabs allouent l’espace, les checksums sont calculés, la compression est appliquée, etc.
- Flush/FUA/barrières : le NVMe sous-jacent doit promettre la durabilité pour les chemins sync.
Read pipeline, simplifié
- Hit ARC : peu coûteux, principalement comportement CPU/cache.
- Miss ARC : ZFS émet une IO ; le périphérique retourne les données ; checksums vérifiés ; décompression optionnelle.
NVMe path, simplifié
- Block IO mis en file dans le noyau.
- Le pilote NVMe soumet aux files du périphérique.
- Le périphérique complète ; interruption (ou polling) notifie le CPU ; la complétion est traitée.
- Les réveils et changements de contexte livrent les données aux threads en attente.
Les pics de latence se produisent quand une étape devient sérialisée. Sur NVMe, la sérialisation est généralement côté CPU : une file, un cœur, un verrou, un réglage malheureux. Ou c’est la cadence propre à ZFS : sync TXG en rafales, contention metaslab, ou flushs d’écritures sync.
Une citation pour rester honnête. « L’espoir n’est pas une stratégie. » — General Gordon R. Sullivan
Plan de diagnostic rapide (premier/deuxième/troisième)
Ceci est le plan « ouvrir l’ordinateur à 2h du matin ». Vous cherchez à répondre à une question : où passe le temps ?
Premier : confirmer que le symptôme est bien la latence du stockage (pas l’appli ou le réseau)
- Vérifiez si les threads applicatifs sont bloqués sur IO (état D) ou sur CPU.
- Vérifiez la latence de bout en bout autour de l’incident (p99/p999) et corrélez avec l’attente IO et la charge d’interruptions.
Deuxième : décider si c’est le chemin lecture, écriture asynchrone ou écriture synchrone
- Les charges sync-intensives (bases de données, NFS en sync, images VM) se comportent différemment des écrivains asynchrones orientés log.
- Cherchez l’activité ZIL et le comportement de flush si les pics sont périodiques ou alignés avec fsync.
Troisième : identifier si le goulot est CPU/IRQs, mise en file, ou TXG
- Si un CPU est saturé par les interruptions/softirqs : corrigez l’affinité IRQ et le mapping des files.
- Si les files IO sont profondes et les temps d’attente augmentent : vous faites de la mise en file ; réduisez la concurrence, corrigez les réglages du scheduler, ou traitez les limites du firmware/contrôleur.
- Si les blocages s’alignent sur TXG sync : vous n’êtes pas « à court de NVMe », vous êtes à court de capacité du pipeline ZFS (souvent metaslab/allocation ou pression mémoire).
Blague #1 : La latence de queue, c’est comme un chat domestique — tranquille toute la journée, puis à 3h du matin il fonce à travers votre dashboard sans raison.
Tâches pratiques : commandes, signification des sorties et décisions
Ce ne sont pas des « lancez des commandes pour le plaisir ». Chacune se termine par une décision. Lancez-les pendant la charge normale et de nouveau pendant la fenêtre d’incident si possible.
Task 1: Confirm pool topology and ashift (alignment)
cr0x@server:~$ sudo zpool status -v
pool: tank
state: ONLINE
scan: scrub repaired 0B in 00:18:21 with 0 errors on Mon Dec 23 02:10:01 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
errors: No known data errors
cr0x@server:~$ sudo zdb -C tank | egrep -i 'ashift|vdev_tree' -n | head
55: ashift: 12
Ce que cela signifie : ashift: 12 implique des secteurs 4K. Pour beaucoup de disques NVMe, 4K est correct ; certains préfèrent un alignement 8K/16K, mais ZFS ne change pas ashift après la création.
Décision : Si ashift est 9 (512B) sur SSD/NVMe, planifiez une reconstruction. Aucun sysctl magique ne vous évitera les pénalités read-modify-write.
Task 2: Check dataset properties that commonly drive latency
cr0x@server:~$ sudo zfs get -o name,property,value -s local,default recordsize,compression,atime,sync,logbias,primarycache,secondarycache xattr,dnodesize tank
NAME PROPERTY VALUE
tank recordsize 128K
tank compression lz4
tank atime off
tank sync standard
tank logbias latency
tank primarycache all
tank secondarycache all
tank xattr sa
tank dnodesize legacy
Ce que cela signifie : C’est la vérité terrain pour le comportement. sync=standard est généralement correct ; logbias=latency signifie « optimiser le sync ».
Décision : Ne changez les propriétés qu’en fonction de la charge, pas par superstition. Si vous hébergez des bases de données, envisagez un recordsize plus petit (16K/32K) et laissez compression=lz4 activé sauf si le CPU est manifestement saturé.
Task 3: Measure pool latency and queueing from ZFS’s perspective
cr0x@server:~$ sudo zpool iostat -v tank 1 5
capacity operations bandwidth
pool alloc free read write read write
---------- ----- ----- ----- ----- ----- -----
tank 1.20T 2.30T 8.1K 12.4K 410M 980M
mirror 1.20T 2.30T 8.1K 12.4K 410M 980M
nvme0n1 - - 4.0K 6.2K 205M 490M
nvme1n1 - - 4.1K 6.2K 205M 490M
Ce que cela signifie : Cela montre la distribution de charge et si un périphérique traîne. Ça ne montre pas la latence par IO, mais indique un déséquilibre.
Décision : Si un NVMe fait systématiquement moins de travail dans un miroir, suspectez un throttling firmware, des problèmes de lien PCIe ou une limitation thermique.
Task 4: Check NVMe health and throttling indicators
cr0x@server:~$ sudo nvme smart-log /dev/nvme0n1 | egrep -i 'temperature|warning|critical|media|percentage|thm'
temperature : 71 C
critical_warning : 0x00
percentage_used : 4%
media_errors : 0
warning_temp_time : 12
critical_comp_time : 0
Ce que cela signifie : La température et le temps accumulé au-dessus des seuils d’avertissement comptent. Un disque peut être « healthy » et throttler quand même sous écritures soutenues.
Décision : Si warning temp time grimpe pendant les incidents, corrigez le flux d’air/dissipateurs, réduisez l’amplification d’écritures soutenues (recordsize, patterns sync), ou déplacez le périphérique vers un emplacement plus frais.
Task 5: Verify PCIe link speed and width (the silent throughput cap)
cr0x@server:~$ sudo lspci -s 0000:5e:00.0 -vv | egrep -i 'LnkCap|LnkSta'
LnkCap: Port #0, Speed 16GT/s, Width x4
LnkSta: Speed 16GT/s, Width x4
Ce que cela signifie : Un lien Gen4 x4 est attendu pour beaucoup de NVMe. Si vous voyez x2 ou Gen3 de façon inattendue, vous avez trouvé votre moment « NVMe n’est pas rapide ».
Décision : Corrigez la bifurcation de lanes du BIOS, le choix du slot, les risers ou le câblage du backplane M.2. C’est du matériel, pas du tuning.
Task 6: Inspect block layer latency (await) and queue depth behavior
cr0x@server:~$ iostat -x -d 1 5 nvme0n1 nvme1n1
Device r/s w/s rKB/s wKB/s rrqm/s wrqm/s r_await w_await aqu-sz %util
nvme0n1 4100 6200 210000 505000 0.0 0.0 0.35 1.90 6.20 78.0
nvme1n1 4200 6200 210000 505000 0.0 0.0 0.36 1.85 6.05 77.5
Ce que cela signifie : await est la latence moyenne des requêtes. aqu-sz montre la taille moyenne de la file. Une aqu-sz élevée avec await en hausse signifie de la mise en file (congestion) plutôt que la latence brute du périphérique.
Décision : Si aqu-sz est élevé et que %util est proche de 100% avec await qui augmente, vous saturez un périphérique ou une file. Réduisez la concurrence, répartissez la charge ou ajoutez des vdevs.
Task 7: Check NVMe driver IRQ distribution (one core doing all the work)
cr0x@server:~$ grep -i nvme /proc/interrupts | head -n 12
98: 10293812 0 0 0 PCI-MSI 524288-edge nvme0q0
99: 1938120 1980221 2018830 1999012 PCI-MSI 524289-edge nvme0q1
100: 1912202 1923301 1899987 1901120 PCI-MSI 524290-edge nvme0q2
101: 1908821 1910092 1903310 1899922 PCI-MSI 524291-edge nvme0q3
Ce que cela signifie : Si une ligne IRQ s’envole sur CPU0 tandis que les autres restent plates, vous avez un goulot côté gestion des interruptions. La queue 0 (souvent admin + IO) peut être spéciale ; ne paniquez pas au sujet de nvme0q0 seul, mais surveillez les déséquilibres.
Décision : Si les interruptions ne sont pas réparties, activez irqbalance (si approprié), ou pincez les queues délibérément sur des CPUs alignés avec la localité NUMA.
Task 8: Confirm NUMA locality and whether NVMe is “far away” from the CPU doing the work
cr0x@server:~$ sudo cat /sys/class/nvme/nvme0/device/numa_node
1
cr0x@server:~$ numactl -H | sed -n '1,25p'
available: 2 nodes (0-1)
node 0 cpus: 0 1 2 3 4 5 6 7 8 9 10 11
node 1 cpus: 12 13 14 15 16 17 18 19 20 21 22 23
node distances:
node 0 1
0: 10 21
1: 21 10
Ce que cela signifie : Si le NVMe est sur le noeud NUMA 1 mais que la plupart des interruptions et des threads ZFS tournent sur le noeud 0, vous payez des pénalités de latence cross-node et de bande passante mémoire.
Décision : Alignez l’affinité IRQ et, pour les cas extrêmes, placez les processus (threads DB) sur le même noeud NUMA que le NVMe.
Task 9: Check ZFS thread states and look for TXG sync pressure
cr0x@server:~$ ps -eLo pid,tid,cls,rtprio,pri,psr,stat,wchan:20,comm | egrep 'txg|z_wr_iss|z_wr_int|arc_reclaim|dbu|zio' | head
1423 1450 TS - 19 12 S txg_sync_thread txg_sync
1423 1451 TS - 19 13 S txg_quiesce txg_quiesce
1423 1462 TS - 19 14 S zio_wait z_wr_int
1423 1463 TS - 19 15 S zio_wait z_wr_int
1423 1488 TS - 19 16 S arc_reclaim_thr arc_reclaim
Ce que cela signifie : Voir ces threads est normal. Voir beaucoup d’entre eux bloqués sur zio_wait pendant les pics suggère une pression backend ; arc_reclaim occupé suggère une pression mémoire.
Décision : Si le reclaim corrèle avec la latence, arrêtez de « tuner ZFS » et commencez à régler la mémoire : limites ARC, RSS des applications et comportement de reclaim du noyau.
Task 10: Watch memory pressure and reclaim storms
cr0x@server:~$ vmstat 1 5
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
4 0 0 52132 91244 8032100 0 0 2 180 8200 9900 22 9 66 3 0
7 1 0 11200 88010 7941200 0 0 0 2500 14000 22000 28 14 50 8 0
Ce que cela signifie : Une b croissante (bloqués), une mémoire libre qui diminue, et des commutations de contexte en hausse durant les incidents est un parfum classique de « le système thrash ». Sur NVMe, le périphérique est assez rapide pour rendre les boucles de reclaim « intéressantes ».
Décision : Si le reclaim corrèle, limitez l’ARC (ou augmentez la RAM), cessez la surallocation, et identifiez quel processus grossit.
Task 11: Confirm sync workload and whether flushes dominate
cr0x@server:~$ sudo zpool iostat -r -w tank 1 3
capacity operations bandwidth
pool alloc free read write read write
---------- ----- ----- ----- ----- ----- -----
tank 1.20T 2.30T 2.2K 18.1K 120M 1.10G
read write
pool ops bytes latency disk latency ops bytes latency disk latency
---------- --- ----- --------- ------------ --- ----- --------- ------------
tank 2.2K 120M 420us 160us 18.1K 1.10G 3.8ms 2.9ms
Ce que cela signifie : La latence au niveau ZFS vs la latence disque aide à séparer les coûts du pipeline ZFS du temps périphérique. Si la latence ZFS est bien supérieure à la latence disque, vous payez des coûts CPU/verrou/filtrage dans la pile.
Décision : Si la latence disque est faible mais la latence ZFS élevée, regardez le CPU, les IRQ et la concurrence ZFS ; n’achetez pas plus de NVMe.
Task 12: Measure per-CPU softirq time (storage completions show up here)
cr0x@server:~$ mpstat -P ALL 1 3 | egrep -A1 'CPU|Average| 0| 12'
CPU %usr %nice %sys %iowait %irq %soft %steal %guest %gnice %idle
0 3.12 0.00 6.01 0.21 0.90 28.44 0.00 0.00 0.00 61.31
12 15.90 0.00 10.82 0.12 0.10 3.20 0.00 0.00 0.00 69.86
Ce que cela signifie : Un CPU0 passant 28% en softirq est souvent « trop de travail de complétion IO sur un seul cœur ». C’est un coupable fréquent quand NVMe « devrait être rapide » mais ne l’est pas.
Décision : Corrigez l’affinité IRQ, activez plusieurs files, et alignez à la NUMA. Si nécessaire, envisagez le polling NVMe (avec prudence) pour des charges sensibles à la latence.
Task 13: Inspect queue configuration and scheduler choices
cr0x@server:~$ cat /sys/block/nvme0n1/queue/scheduler
[none] mq-deadline kyber bfq
cr0x@server:~$ cat /sys/block/nvme0n1/queue/nr_requests
1023
Ce que cela signifie : Pour NVMe, none est courant et souvent correct. Les schedulers peuvent aider la justice d’accès ; ils peuvent aussi ajouter de la surcharge. nr_requests affecte la profondeur de file au niveau bloc, ce qui peut affecter la latence de queue.
Décision : Si la latence de queue est votre ennemi, évitez des files énormes qui masquent la congestion. Envisagez mq-deadline pour une latence plus prévisible si none produit des p99 laids sous IO mixte.
Task 14: Check ZFS tunables you should treat as “last-mile,” not magic
cr0x@server:~$ sudo sysctl kstat.zfs.misc.arcstats.size kstat.zfs.misc.arcstats.c kstat.zfs.misc.arcstats.mfu_size kstat.zfs.misc.arcstats.mru_size
kstat.zfs.misc.arcstats.size = 68412985344
kstat.zfs.misc.arcstats.c = 73014444032
kstat.zfs.misc.arcstats.mfu_size = 51200901120
kstat.zfs.misc.arcstats.mru_size = 15600930816
Ce que cela signifie : La taille et la cible ARC comptent. Si l’ARC se dispute la mémoire avec l’application, vous obtenez du reclaim churn. Si l’ARC est trop petit, vous thrasherez les lectures et les métadonnées.
Décision : Définissez explicitement l’ARC max sur les machines partagées. Sur des appliances de stockage dédiées, laissez l’ARC respirer à moins d’une raison concrète contraire.
Task 15: Confirm TRIM/autotrim behavior and whether it lines up with spikes
cr0x@server:~$ sudo zpool get autotrim tank
NAME PROPERTY VALUE SOURCE
tank autotrim on local
cr0x@server:~$ sudo zpool trim -v tank
trim operation for tank has completed
trim rate: 0B/s
total trimmed: 0B
Ce que cela signifie : Autotrim est généralement bon sur SSD/NVMe, mais certains périphériques gèrent mal la désallocation en arrière-plan, surtout sous forte écriture.
Décision : Si vous corrélez des pics avec le trim, mettez autotrim=off et planifiez des trims en fenêtre calme. Si les performances se dégradent sur des semaines sans trim, le GC du périphérique a besoin d’aide : ne le désactivez pas indéfiniment.
Réglages de dataset et de pool utiles (et ce qui est superstition)
Compression : laissez lz4 activé, puis mesurez le CPU
Sur des pools uniquement NVMe, la compression aide souvent plus qu’elle ne nuit. Elle réduit les octets écrits et lus, ce qui réduit la pression sur le backend et peut améliorer la latence. Le coût est CPU. Sur les CPU modernes, lz4 est généralement rentable.
Quand ça fait mal ? Données à haute entropie (déjà compressées), petites écritures sync-intensives, ou lorsque vous êtes CPU-bound à cause des interruptions et des checksums déjà.
recordsize : arrêtez d’utiliser 128K comme position morale
recordsize concerne la forme des IO. Bases de données, images VM, et applications orientées logs préfèrent souvent 8K–32K. Les charges de streaming (sauvegardes, médias, scans analytiques) aiment 128K ou plus.
Sur NVMe, un grand recordsize peut encore convenir, mais de petites mises à jour aléatoires sur de gros records provoquent une amplification d’écriture. Le périphérique est assez rapide pour que vous ne remarquiez pas jusqu’à ce que votre p99 montre les dents.
atime et xattr : bascules ennuyeuses, gains réels
Désactiver atime réduit les écritures métadonnées. Stocker les xattrs comme SA (xattr=sa) réduit les IO pour les charges riches en ACL et métadonnées. Rien de glamour ; souvent la bonne option.
dnodesize : les charges métadonnées doivent préférer des valeurs meilleures
Les dnodes larges peuvent empaqueter plus de métadonnées en moins de lectures, ce qui compte pour les traversées de répertoires, les couches d’images de conteneurs, et les tempêtes de petits fichiers. L’inconvénient est une légère hausse de l’utilisation d’espace.
Faites-le par dataset quand vous avez une charge riche en métadonnées et que vous en avez assez de prétendre que NVMe résout tout le fan-out métadonnées.
sync=disabled : ne le faites pas à moins de pouvoir perdre des données et garder votre emploi
Désactiver sync transforme « durable » en « optimiste ». Les benchs ont l’air d’un upgrade des lois de la physique. Cela change aussi la signification de fsync. Si vous servez NFS, exécutez des bases de données ou hébergez des VMs, ce n’est pas un réglage : c’est une décision contractuelle.
Blague #2 : sync=disabled est l’équivalent stockage d’enlever le détecteur de fumée parce qu’il est bruyant.
IRQs, files et CPU : le goulot de contention ingrat
NVMe est construit pour le parallélisme, mais le système doit accepter l’invitation. Si vos interruptions tombent sur un seul CPU et que le traitement des complétions brule en softirq, votre « stockage rapide » devient une machine à latence mono-cœur.
Ce à quoi ressemble le « bon »
- Plusieurs files IO NVMe sont actives (
nvme0q1..qN), pas seulement la queue 0. - Les interruptions sont réparties sur des CPU du noeud NUMA correct.
- Le temps softirq est non négligeable mais pas concentré sur un seul cœur.
- Les threads applicatifs et ZFS ne se battent pas pour les mêmes quelques CPU.
Ce que vous pouvez réellement changer en sécurité
Affinité IRQ : épinglez les IRQ NVMe à un ensemble de CPUs local au noeud NUMA du périphérique. Ne pincez pas tout sur le CPU0 parce qu’un billet de blog de 2017 disait « CPU0 gère les interruptions ». Ce billet date d’une époque différente.
irqbalance : peut aider, peut nuire. C’est correct pour des serveurs généralistes. Pour du stockage critique latence, vous voulez souvent un pinning délibéré.
Scheduler : none convient pour le débit pur. Si vous avez de l’IO mixte et que vous tenez au p99, testez mq-deadline. Ne présumez pas ; mesurez.
Écritures synchrones, mythes sur le SLOG et ce que change NVMe
Les pools uniquement NVMe créent une tentation courante : « Si le pool est déjà NVMe, ai-je besoin d’un SLOG ? » La réponse honnête : généralement non. La réponse plus utile : ça dépend de votre pattern d’écritures sync et de ce que « durable » signifie pour vous.
Clarifier ce que fait le ZIL/SLOG
Le ZIL (ZFS Intent Log) existe pour enregistrer assez d’intention afin que les écritures synchrones puissent être reconnues en toute sécurité avant le commit complet du TXG. En cas de crash, la relecture rendra les dernières opérations sync engagées durables.
Un SLOG est un périphérique séparé utilisé pour stocker les entrées ZIL. Il aide quand le pool principal est lent pour les écritures sync ou les flushs. Sur un pool NVMe, le pool principal peut déjà bien faire le job. Ajouter un SLOG peut encore aider si :
- votre NVMe principal a une mauvaise latence de flush sous charge,
- vous avez des charges sync lourdes et voulez de l’isolation,
- vous voulez un périphérique de log avec protection contre la perte de puissance.
Le vrai limiteur : comportement de flush et durabilité
La performance sync concerne souvent la sémantique de flush de cache, pas la vitesse brute du média. Un disque peut faire 700k IOPS et avoir quand même une latence fsync affreuse si son firmware a un comportement conservateur de flush sous écritures soutenues.
Comportement TXG, blocages, et pourquoi le média rapide ne suffit pas
Les écritures ZFS sont organisées en groupes de transactions qui se synchronisent périodiquement. Même sur NVMe, ce comportement de “batching” peut créer des pics de latence périodiques, surtout si :
- vous avez des rafales d’écritures aléatoires causant une pression d’allocateur,
- des mises à jour de métadonnées s’accumulent (snapshots, petits fichiers, atime),
- le système manque de mémoire et ne peut pas tamponner de façon fluide.
Reconnaître les symptômes liés au TXG
- Les pics de latence apparaissent avec une cadence (souvent des secondes).
- L’utilisation CPU grimpe dans des threads noyau, pas en espace utilisateur.
- La latence du périphérique reste modeste, mais la latence au niveau ZFS augmente.
Il existe des réglages TXG, mais ce n’est pas par là qu’on commence. Commencez par vous assurer que le système ne s’étouffe pas sur les interruptions, le reclaim mémoire, ou des formes IO pathologiques.
Vdev spéciaux et métadonnées sur pools uniquement NVMe
Les vdevs spéciaux sont souvent vendus comme « mettre les métadonnées sur SSD ». Sur un pool uniquement NVMe, vous l’avez déjà fait. Alors pourquoi en parler ?
Parce que « NVMe-only » ne signifie pas « tous les NVMe se comportent de la même façon », et parce que les métadonnées et petits blocs ont des profils de performance différents des gros débits séquentiels. Un vdev spécial peut encore aider si :
- vous voulez isoler métadonnées et petits blocs sur un périphérique plus faible latence et protégé contre la perte de puissance,
- vous voulez réduire la pression de fragmentation sur les vdevs principaux pour des charges mixtes,
- vous avez besoin d’une latence petite-IO cohérente même sous de gros écritures en streaming.
Mais un vdev spécial est un engagement : si vous le perdez sans redondance, vous perdez le pool. Mirrorez-le ou ne le faites pas.
Trois mini-histoires d’entreprise sorties des tranchées de latence
Incident causé par une mauvaise hypothèse : « NVMe mirror signifie plus de latence »
Une société SaaS de taille moyenne a déplacé son cluster Postgres principal vers un pool ZFS en miroir NVMe. Le plan de migration était soigneux : scrub avant bascule, snapshots, rollback. Le graphe de stockage en staging était superbe. Tout le monde dormait bien.
En production, toutes les quelques minutes la base gelait juste assez pour déclencher des timeouts applicatifs. Pas une panne totale—pire. Un écoulement lent d’erreurs et de retries. L’astreinte regardait le débit disque, qui n’était jamais proche de la saturation, et le SMART NVMe, qui semblait propre.
La mauvaise hypothèse était que « disque rapide signifie que l’IO ne peut pas être le problème ». Le vrai problème était un seul cœur CPU coincé en softirq pendant le trafic de pointe. Les complétions NVMe tombaient sur un ensemble étroit de CPUs à cause d’une combinaison d’affinité IRQ par défaut et de placement NUMA. En charge normale, ça allait. En pointe, ce cœur devenait le goulot de complétion, la file grandissait, et les threads de la base se retrouvaient bloqués.
Ils ont corrigé en alignant les IRQ NVMe sur des CPUs locaux au noeud NUMA du NVMe, et en déplaçant le processus Postgres hors des CPUs les plus chauds en interruptions. Le pool n’a pas changé. Les disques n’ont pas changé. La latence a changé.
Optimisation qui s’est retournée contre eux : « On augmente la profondeur des files et on désactive sync »
Une autre société exploitait des hôtes de virtualisation sur ZFS NVMe. Ils ont eu un « push performance trimestriel », traduction corporate de « quelqu’un a vu un slide de bench ». Un ingénieur a augmenté la taille des files bloc et permis plus d’IO concurrentes, poursuivant de meilleurs chiffres sur un test synthétique.
Les chiffres se sont améliorés. Le débit a augmenté. Tout le monde a applaudi. Puis lundi est arrivé. Les charges réelles sont arrivées : lectures et écritures mixtes, métadonnées, et rafales d’activité sync depuis les files des guests.
La latence de queue a plongé. Les files profondes ont caché la congestion jusqu’à ce qu’il soit trop tard, et quand le système a pris du retard, il a pris du retard en gros morceaux laids. Ils avaient aussi testé sync=disabled sur des datasets VM « juste pour la perf ». Cela a amélioré les temps fsync des guests—jusqu’à ce qu’un crash hôte transforme « juste pour la perf » en une soirée de réparation de systèmes de fichiers multi-VM.
Ils ont restauré les profondeurs de file, réduit la concurrence, et rétabli la sémantique sync correcte. La perf n’était plus aussi impressionnante dans les slides, mais le pager a arrêté de hurler.
Pratique ennuyeuse mais correcte qui a sauvé la mise : pinning, observabilité et runbook
Une équipe entreprise gérant un store d’objets interne avait l’habitude de trois choses ennuyeuses : définir explicitement ARC max, documenter l’affinité IRQ, et exécuter une « drill latence » hebdomadaire où ils capturaient 10 minutes d’iostat/mpstat/zpool iostat pendant les heures de pointe.
Un semaine, la latence a commencé à grimper subtilement. Pas assez pour provoquer des pannes, mais assez pour déclencher des alarmes SLO. Leurs données de drill ont montré un nouveau pattern : le w_await d’un NVMe doublait sous writeback soutenu, tandis que son miroir restait stable. Le pool est resté en ligne ; rien n’a « cassé ».
Parce qu’ils avaient déjà des baselines, ils n’ont pas débattu pour savoir si c’était « normal ». Ils ont remplacé le périphérique lors de la prochaine fenêtre de maintenance. Le postmortem a suggéré un throttle thermique dû à un flux d’air partiellement obstrué après un changement de câblage courant.
Pas d’héroïsme. Pas de réglages magiques. Juste de la discipline ennuyeuse : baselines, pinning, et remplacement hardware avant l’incident.
Erreurs courantes : symptôme → cause racine → correction
-
Symptôme : Excellente latence moyenne, p99 catastrophique sous charge.
Cause racine : Mise en file + concentration d’interruptions ; les files profondes masquent la congestion.
Correction : Vérifieziostat -xpour uneaqu-szmontante ; redistribuez les IRQ NVMe ; envisagezmq-deadline; réduisez la concurrence quand possible. -
Symptôme : « Stutters » périodiques toutes les quelques secondes pendant de grosses écritures.
Cause racine : Rafales TXG sync + pression d’allocateur/metaslab, parfois aggravées par le churn de métadonnées.
Correction : Réduisez les écritures métadonnées (atime=off,xattr=sa), ajustez recordsize par charge, assurez suffisamment de RAM ; vérifiez la latence ZFS vs disque viazpool iostat -r -w. -
Symptôme : NVMe affiche une faible latence disque, mais l’appli voit une forte latence IO.
Cause racine : Goulot CPU dans checksum/compression/gestion d’interruptions ; contention de verrou.
Correction : Utilisezmpstatpour repérer les hotspots softirq ; répartissez les IRQ ; confirmez la marge CPU ; évitez des niveaux de compression coûteux ; ne pas sur-paralléliser les petites IO. -
Symptôme : Charge sync-intense (fsync) lente même sur NVMe.
Cause racine : Comportement flush/FUA sous charge ; firmware ; parfois couches de virtualisation.
Correction : Mesurez les IO sync séparément ; envisagez un dispositif de log PLP en miroir ; évitezsync=disabledsauf si la politique autorise la perte de données. -
Symptôme : Un périphérique dans un miroir sous-performe ou prend du retard.
Cause racine : Throttling thermique, downgrade du lien PCIe, bizarreries firmware.
Correction : Vérifiez SMART temp time,lspcilink status, et comparez par périphérique aveciostat -x; corrigez refroidissement ou configuration slot/BIOS. -
Symptôme : Pics de latence pendant les trims ou après de gros deletes.
Cause racine : Interaction TRIM/GC ; firmware du périphérique qui se bloque pendant la désallocation sous charge.
Correction : Désactivez temporairementautotrimet planifiez des trims ; validez que la perf long terme ne se dégrade pas.
Listes de contrôle / plan étape par étape
Étape par étape : stabiliser la latence sur un pool ZFS uniquement NVMe
- Base line d’abord : capturez
zpool iostat,iostat -x,mpstat, et/proc/interruptspendant la charge de pointe normale. - Confirmez la réalité hardware : vitesse/largeur lien PCIe ; températures NVMe ; versions firmware si votre org les suit.
- Corrigez les goulots CPU évidents : distribution IRQ ; alignement NUMA ; évitez d’affamer les threads ZFS.
- Validez la sémantique dataset : assurez-vous que
synccorrespond aux exigences de durabilité ; mettezatime=offoù approprié ; gardezcompression=lz4sauf si le CPU prouve le contraire. - Adaptez recordsize à la charge : DB/VM généralement plus petit ; streaming plus grand. Mesurez avec des tailles IO proches de la production.
- Surveillez la pression mémoire : définissez ARC max si la machine exécute des apps ; évitez les tempêtes de reclaim.
- Retestez le p99 : ne vous arrêtez pas au débit moyen. Lancez un test de charge mixte et regardez les queues.
- Ce n’est qu’ensuite que vous considérez des réglages exotiques : schedulers, profondeurs de files, polling. Chaque changement a un plan de rollback.
Checklist : avant d’accuser ZFS
- Le lien PCIe est à la vitesse/largeur attendue pour chaque NVMe.
- Aucun NVMe n’est en territoire d’avertissement thermique persistant.
- Les IRQ sont distribuées et locales au noeud NUMA correct.
- Le softirq CPU n’est pas concentré sur un seul cœur.
- Il y a assez de RAM et pas de swap/reclaim thrash.
- Vous pouvez expliquer si la douleur vient du sync, async, lecture ou métadonnées.
FAQ
1) Ai-je besoin d’un SLOG sur un pool NVMe-only ?
Généralement non. Ajoutez un SLOG seulement si vous avez des problèmes mesurables de latence d’écriture sync et un périphérique bas-latence et protégé contre la perte de puissance approprié. Autrement, vous ajoutez de la complexité pour l’apparence.
2) Dois‑je mettre sync=disabled pour la performance ?
Seulement si l’entreprise accepte la perte de données en cas de crash et vous pouvez prouver que l’application est sûre avec ce risque. Pour les bases de données, NFS et stockage VM, c’est généralement un mauvais compromis.
3) compression=lz4 est‑elle toujours utile sur NVMe ?
Oui, souvent. Elle réduit les octets déplacés et écrits, ce qui peut améliorer la latence de queue. Désactivez-la seulement après avoir confirmé que le CPU est le facteur limitant.
4) Pourquoi mon NVMe fait‑il « seulement » une fraction des IOPS annoncés ?
Parce que votre système n’est pas une machine de benchmark du vendeur. Gestion des IRQ, NUMA, mise en file, checksums ZFS, et mix de charge réduisent les chiffres. De plus, le comportement d’un vdev miroir et la sémantique sync comptent.
5) Dois‑je utiliser mq-deadline ou none pour NVMe ?
Commencez par none. Si vous avez de la laideur p99 sous IO mixte, testez mq-deadline. Utilisez la latence de queue mesurée, pas le débit, pour décider.
6) Comment savoir si je suis CPU-bound plutôt que storage-bound ?
Si la latence disque est faible mais la latence appli/ZFS est élevée, et que vous voyez un %soft élevé ou un cœur saturé, vous êtes CPU-bound. Confirmez avec mpstat et la distribution des interruptions.
7) Plus de profondeur de file améliore‑t‑elle toujours la performance ?
Non. Ça peut améliorer le débit et empirer la latence. Les files profondes troquent la prévisibilité contre le mouvement en masse. Si votre charge est orientée utilisateur, vous voulez souvent des files contrôlées et des queues stables.
8) Quelle est la cause la plus fréquente de « latence mystérieuse » NVMe sur ZFS ?
Le traitement des interruptions et des complétions concentré sur trop peu de CPUs, souvent aggravé par un mismatch NUMA. C’est embarrassantement fréquent, et corrigeable.
9) Dois‑je tuner les paramètres TXG pour corriger les stutters ?
Seulement après avoir écarté les problèmes IRQ/CPU, la pression mémoire et les formes IO pathologiques. Les réglages TXG aident dans des cas limites, mais sont faciles à mal appliquer et difficiles à raisonner.
Conclusion : prochaines étapes à faire cette semaine
Si vous voulez un pager plus calme et une meilleure latence p99 sur des pools ZFS uniquement NVMe, faites d’abord le travail pas sexy. Vérifiez les liens PCIe, stoppez le throttling thermique, répartissez les IRQ et alignez la NUMA. Ensuite, adossez les propriétés dataset à la charge : recordsize adapté, atime off où c’est du bruit, et garder lz4 sauf si vous avez prouvé que le CPU est le goulot.
Prochaines étapes :
- Capturez une baseline pendant la pointe :
iostat -x,zpool iostat -r -w,mpstat, et/proc/interrupts. - Corrigez toute évidence IRQ/NUMA trouvée. Re-mesurez le p99.
- Auditez les datasets : identifiez ceux qui sont DB/VM/log/streaming et ajustez recordsize en conséquence.
- Décidez explicitement si vous avez besoin de sémantiques sync strictes ; ne changez pas « accidentellement » la durabilité.
- Rédigez un petit runbook : quels graphs/commandes vérifier en premier, et ce que « bon » signifie pour votre parc.