ZFS Petites écritures aléatoires : pourquoi les miroirs surpassent souvent la parité

Cet article vous a aidé ?

Votre pool a beaucoup de « débit » sur le papier. Pourtant la base de données se bloque sur de petites mises à jour, l’hôte VM signale des pics de latence du stockage, et le p99 de votre appli passe de « acceptable » à « pourquoi le PDG appelle ». C’est le piège classique des petites écritures aléatoires sur ZFS : la charge est liée aux IOPS et à la latence, alors que votre conception est optimisée pour la bande passante.

Quand on dit « ZFS est lent », on veut souvent dire « j’ai construit des vdevs parité puis je leur ai demandé de se comporter comme des miroirs pour de petites écritures aléatoires ». ZFS ne vous a pas trahi. La physique l’a fait. Et l’arithmétique de la parité s’y est mise pour le projet de groupe.

Ce que « petites écritures aléatoires » signifient réellement pour ZFS

Les petites écritures aléatoires sont la charge qui pénalise les architectures conçues pour la bande passante séquentielle. Pensez à des écritures de 4K–32K dispersées sur un grand working set, typiquement sous concurrence. Bases de données, images VM, systèmes de fichiers overlay de conteneurs, files d’attente de mail et arbres de fichiers riches en métadonnées en font partie.

Pour ZFS, « petit » et « aléatoire » n’est pas seulement une caractéristique applicative. C’est aussi une caractéristique de mise en page et de transaction. ZFS est copy-on-write (CoW). Cela signifie qu’écraser un bloc existant n’est pas une mise à jour in-place ; c’est « allouer de nouveaux blocs, les écrire, puis mettre à jour les métadonnées pour y pointer ». Donc l’E/S réelle d’écriture est souvent plus que « une seule écriture 8K ».

Ajoutez maintenant des vdevs parité (RAIDZ1/2/3). Chaque écriture peut nécessiter de toucher plusieurs disques plus le calcul de la parité, et parfois suivre un schéma read-modify-write si l’écriture n’est pas alignée sur des frontières de stripe complètes. C’est là que les IOPS et la latence sont jugées.

Deux définitions à garder bien distinctes

  • IOPS-bound : le débit est faible parce que chaque IO est petit et chaque opération coûte en latence. Plus de bande passante n’aidera pas.
  • Latency-bound : la profondeur de file augmente parce que chaque opération met trop de temps à se terminer ; les applications expirent, les logs montrent des pics, et « disque occupé » est trompeur.

Les miroirs et les arrays parité peuvent offrir beaucoup de capacité brute et un débit séquentiel acceptable. Seul l’un d’eux fournit typiquement une latence de petites écritures prévisible sans tuning héroïque : les miroirs.

Miroirs vs parité en une phrase (et la phrase plus longue dont vous avez besoin)

Une phrase : Les miroirs battent généralement les vdevs parité sur les petites écritures aléatoires car une écriture en miroir est « écrire à deux endroits », tandis que la parité est souvent « lire certains blocs, calculer la parité, écrire plusieurs blocs », avec plus d’opérations I/O et plus d’attente.

La phrase plus longue dont vous avez réellement besoin : Les vdevs parité de ZFS excellent quand les écritures sont larges, alignées et en flux continu, mais se dégradent fortement quand les écritures sont petites, fragmentées ou orientées sync, car elles amplifient l’I/O, augmentent la latence par opération et réduisent la flexibilité d’ordonnancement au niveau du vdev.

Le chemin d’écriture en parité : où vos IOPS partent en retraite anticipée

La parité (RAIDZ) est parfaite si vous voulez efficacité de capacité et tolérance aux pannes. Elle n’est pas idéale si vous voulez des écritures aléatoires à faible latence. La raison n’est pas mystique ; c’est de l’arithmétique plus de la mécanique.

Écritures parité et le problème de « full-stripe »

Dans un vdev RAIDZ, les données sont réparties sur les disques avec parité. Une écriture full-stripe signifie que vous écrivez un ensemble complet de colonnes de données plus les colonnes de parité pour une stripe. Si vous pouvez faire des écritures full-stripe de manière régulière, la parité est assez efficace : pas besoin de lire les anciennes données pour recalculer la parité ; vous avez déjà toutes les nouvelles données.

Les petites écritures aléatoires sont généralement pas full-stripe. Ce sont des partial-stripe et souvent désalignées par rapport au recordsize du vdev, à la taille de secteur (ashift) et à la géométrie RAIDZ. Quand vous faites une mise à jour partial-stripe, la parité peut exiger un read-modify-write :

  • Lire les anciens blocs de données qui font partie de la stripe (ou la parité) pour calculer le delta
  • Calculer la nouvelle parité
  • Écrire les nouveaux blocs de données et les nouveaux blocs de parité

Donc une petite écriture logique peut se transformer en plusieurs E/S physiques sur plusieurs disques. Chaque disque a sa propre latence. Votre opération se termine quand la plus lente des E/S requises se termine.

Pourquoi cela fait plus mal avec des HDD

Avec des HDD, les IOPS aléatoires sont limités par les temps de recherche et la latence de rotation. Si votre opération parité se répartit sur plusieurs disques, vous multipliez la probabilité de subir une recherche lente. Les miroirs répartissent aussi les écritures (deux disques), mais la parité peut impliquer plus de disques plus des lectures, et elle a typiquement une pire « latence de queue » sous charge.

Pourquoi cela importe encore avec des SSD

Les SSD réduisent la douleur des seeks, mais ne l’éliminent pas. Les SSD ont une gestion interne des blocs d’effacement, du garbage collection et une amplification d’écriture. Les écritures amplifiées et dispersées de la parité peuvent augmenter l’amplification d’écriture au niveau du périphérique et provoquer de longues latences de queue lors du GC. Les miroirs écrivent plus d’octets que un seul disque, mais la parité peut créer des écritures plus fragmentées et plus petites sur plus de périphériques.

Mise en file d’attente : le tueur silencieux

ZFS émet des I/O par vdev. Un vdev RAIDZ se comporte comme un seul vdev logique soutenu par plusieurs disques. Avec les petites écritures aléatoires, le mappage interne du vdev et le calcul de la parité limitent combien d’opérations indépendantes il peut satisfaire sans contention. Les miroirs, en revanche, peuvent distribuer les lectures entre disques et, avec plusieurs vdevs miroir, répartir les écritures entre vdevs avec moins de surcharge de coordination.

Blague n°1 : Les arrays parité sont comme des équipes de projet — tout le monde doit être impliqué, donc rien ne finit dans les délais.

Le chemin d’écriture en miroir : ennuyeux, direct, rapide

Une écriture en miroir est simple : ZFS alloue un bloc, l’écrit sur les deux côtés du miroir, et la considère terminée quand l’écriture est durable selon votre politique sync. Il n’y a pas de parité à calculer et, en général, pas besoin de lire les blocs existants pour mettre à jour la parité.

Les miroirs évoluent comme l’I/O aléatoire évolue réellement

L’unité de performance pratique dans ZFS est le top-level vdev. Les IOPS d’un pool sont à peu près la somme des IOPS de ses top-level vdevs, spécialement pour les charges aléatoires. Dix vdevs miroir signifient dix endroits où ZFS peut planifier des écritures indépendantes (chaque paire miroir faisant son travail). Un grand vdev RAIDZ signifie un domaine d’ordonnancement unique.

Donc oui : un seul miroir 2 disques n’est pas magique. Un pool composé de plusieurs miroirs l’est.

Différences d’amplification d’écriture que vous sentez en production

  • Miroir : écrire les nouvelles données deux fois (ou trois fois pour les miroirs 3-way), plus les mises à jour de métadonnées dues au CoW.
  • RAIDZ : écrire les nouvelles données plus la parité, parfois lire les anciennes données/parité d’abord, plus les mises à jour de métadonnées dues au CoW.

Quand votre charge est dominée par de nombreuses petites mises à jour, le système vit ou meurt selon le surcoût par opération et la latence tail. Les miroirs gardent ce surcoût plus bas et plus prévisible.

Multiplicateurs spécifiques à ZFS : CoW, txg, métadonnées et sync

Même avec des miroirs, ZFS peut devenir grincheux sous de petites écritures aléatoires si vous ignorez les parties spécifiques à ZFS. Voici ce qui compte quand vous discutez miroirs vs parité dans le monde réel.

Copy-on-write : le bloc que vous avez écrasé n’est pas le bloc que vous avez écrit

ZFS écrit de nouveaux blocs puis met à jour des pointeurs. Cela signifie que les charges d’écrasement aléatoire génèrent des écritures de métadonnées supplémentaires. Sur des vdevs parité, ces écritures de métadonnées sont tout aussi sujettes aux pénalités partial-stripe que les écritures de données.

Transaction groups (txg) et rafales

ZFS regroupe les écritures en transaction groups et les vide périodiquement. Sous une charge importante de petites écritures, vous pouvez observer un motif : le système tamponne, puis vide, et pendant le vidage la latence grimpe. Les miroirs tendent à vidanger plus en douceur ; la parité peut devenir « saccadée » si le flush se transforme en tempête d’opérations RMW.

Écritures sync : là où la latence devient une politique

Si votre charge émet des écritures sync (les bases de données le font souvent, les hôtes VM aussi), ZFS doit engager les données sur un stockage stable avant d’accuser réception — sauf si vous relaxez explicitement avec sync=disabled (ne le faites pas, sauf si vous aimez expliquer une perte de données).

Sur des HDD, les écritures sync sont brutales sans un SLOG approprié (périphérique de log séparé) car chaque opération sync force un commit lié à la latence. La parité n’aide pas. Les miroirs n’aident pas magiquement non plus, mais les miroirs réduisent le surcoût parité afin que votre SLOG ait réellement une chance d’être le goulet d’étranglement plutôt que l’array.

Métadonnées et le levier du vdev « special »

Les I/O de métadonnées peuvent dominer les charges de petits fichiers et de mises à jour aléatoires. Un vdev special (typiquement des SSD en miroir) peut placer les métadonnées (et optionnellement les petits blocs) sur un média plus rapide, réduisant drastiquement la latence. Cela aide les miroirs comme les RAIDZ — mais cela sauve souvent les pools RAIDZ d’une sensation d’inutilisabilité lors d’un churn de métadonnées.

Recordsize, volblocksize et taille réelle d’I/O

Le recordsize (pour les filesystems) et le volblocksize (pour les zvols) influencent la manière dont ZFS découpe les données. Si votre DB écrit des pages de 8K dans un dataset avec recordsize à 128K, ZFS peut faire plus de travail que nécessaire, surtout en cas de fragmentation. Mais ne réduisez pas aveuglément recordsize partout ; cela peut augmenter les métadonnées et réduire l’efficacité séquentielle. Ajustez-le quand la charge le justifie.

Faits intéressants et contexte historique (ce que les gens oublient)

  • Fait 1 : ZFS est né chez Sun Microsystems au milieu des années 2000 avec l’intégrité bout-en-bout et CoW comme idées premières — des années avant que « intégrité des données » devienne un argument marketing par défaut.
  • Fait 2 : RAIDZ a été conçu pour éviter le classique « write hole » de RAID-5 en intégrant la parité au modèle transactionnel du système de fichiers.
  • Fait 3 : La consigne « le top-level vdev est l’unité de performance » précède le NVMe moderne ; elle importait encore plus sur HDD où la latence de seek dominait.
  • Fait 4 : Les premières déploiements ZFS étaient souvent conçus pour des charges en flux (répertoires personnels, médias, sauvegardes). Les bases de données et la virtualisation sont devenues courantes plus tard, et c’est là que la douleur de la parité est devenue grand public.
  • Fait 5 : Le ZIL (ZFS Intent Log) existe même sans SLOG dédié. Sans SLOG, le ZIL vit sur les disques du pool, et les écritures sync rivalisent avec tout le reste.
  • Fait 6 : OpenZFS moderne a introduit des fonctionnalités comme les vdevs special et L2ARC persistante pour cibler précisément le problème des « métadonnées et petites I/O aléatoires » qui gêne les arrays parité.
  • Fait 7 : Les disques 4K-native et les SSD ont fait de la sélection d’ashift un piège permanent ; un ashift incorrect peut entraver silencieusement la performance I/O petite pour la durée de vie du vdev.
  • Fait 8 : L’expansion RAIDZ (ajouter un disque à un RAIDZ existant) a été historiquement difficile ; opérationnellement, les miroirs étaient souvent favorisés simplement parce qu’ils étaient plus faciles à étendre sans migration.
  • Fait 9 : « La parité est plus lente » n’est pas une loi. Pour les écritures séquentielles larges, RAIDZ peut être très compétitif — parfois plus rapide que des miroirs grâce à une meilleure bande passante utilisable par disque.

Mode opératoire de diagnostic rapide

Voici l’ordre dans lequel je vérifie les choses quand quelqu’un dit « les petites écritures ZFS sont lentes ». L’objectif est de trouver le goulot d’étranglement en quelques minutes, pas de gagner un argument théorique.

Premier : confirmer que la charge est réellement de petites écritures aléatoires

  • Parle-t-on d’un WAL de DB + pages de données ? d’écritures aléatoires VM ? d’un storm de métadonnées ?
  • La plainte concerne-t-elle la latence (p95/p99), le débit, ou les deux ?

Deuxième : identifier le type de vdev et combien de top-level vdevs vous avez

  • Un grand vdev RAIDZ est un animal très différent de huit miroirs.
  • Pour l’I/O aléatoire, « plus de vdevs » bat généralement « vdevs plus larges ».

Troisième : vérifier le comportement sync et la santé du SLOG

  • Si les écritures sync sont activées et qu’il n’y a pas de SLOG rapide (ou qu’il est mal configuré), vous êtes lié en latence par les commits sur stockage stable.
  • Si le SLOG existe mais est saturé, il devient votre goulot d’étranglement.

Quatrième : vérifier le taux de remplissage du pool et la fragmentation

  • Des pools à plus de ~80% plein et très fragmentés peuvent transformer les petites écritures en misère pour l’allocateur.
  • Les pools parité en souffrent davantage car ils ont besoin d’un meilleur alignement pour rester efficaces.

Cinquième : vérifier ashift, recordsize/volblocksize et vdevs special

  • Mauvais ashift : taxe permanente.
  • Mauvais dimensionnement des blocs : taxe évitable.
  • Métadonnées sur disques lents : taxe auto-infligée.

Sixième : valider que le goulot est le stockage, pas la pression CPU ou mémoire

  • La parité peut brûler du CPU sur checksumming/compression/calculs de parité sous IOPS élevé.
  • La pression ARC peut provoquer des lectures supplémentaires et des ratés de métadonnées.

Tâches pratiques : commandes, sorties et décisions à prendre

Voici des vérifications réelles que vous pouvez exécuter sur un système Linux/OpenZFS typique. Chaque tâche inclut : commande, sortie exemple, ce que ça signifie et la décision à prendre.

Task 1: Identify vdev layout and count top-level vdevs

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

        NAME                        STATE     READ WRITE CKSUM
        tank                        ONLINE       0     0     0
          raidz2-0                  ONLINE       0     0     0
            sda                     ONLINE       0     0     0
            sdb                     ONLINE       0     0     0
            sdc                     ONLINE       0     0     0
            sdd                     ONLINE       0     0     0
            sde                     ONLINE       0     0     0
            sdf                     ONLINE       0     0     0
        logs
          nvme0n1p2                 ONLINE       0     0     0
        special
          mirror-1                  ONLINE       0     0     0
            nvme1n1p1               ONLINE       0     0     0
            nvme2n1p1               ONLINE       0     0     0

errors: No known data errors

Ce que ça signifie : Vous avez un vdev RAIDZ2 top-level (une « voie » de performance pour les écritures aléatoires), plus un SLOG et un vdev special en miroir.

Décision : Si la latence des écritures aléatoires est le problème, vous avez soit (a) accepter les limites de la parité et atténuer avec special vdev/SLOG/tuning, soit (b) redesign vers plusieurs vdevs miroir.

Task 2: Watch per-vdev IOPS and latency during the complaint

cr0x@server:~$ sudo zpool iostat -v tank 1 5
                              capacity     operations     bandwidth
pool                        alloc   free   read  write   read  write
--------------------------  -----  -----  -----  -----  -----  -----
tank                         6.20T  1.80T    120    980  3.2M  12.4M
  raidz2-0                   6.20T  1.80T    120    980  3.2M  12.4M
    sda                          -      -     20    165  520K  2.1M
    sdb                          -      -     18    170  510K  2.0M
    sdc                          -      -     22    160  540K  2.1M
    sdd                          -      -     21    162  530K  2.0M
    sde                          -      -     19    163  510K  2.1M
    sdf                          -      -     20    160  520K  2.1M
--------------------------  -----  -----  -----  -----  -----  -----

Ce que ça signifie : Les écritures sont distribuées, mais le pool effectue ~980 ops d’écriture/s au total. Sur HDD, c’est déjà flirter avec la limite « seek-limited ».

Décision : Si l’appli a besoin de plus d’IOPS ou d’un p99 plus bas, RAIDZ sur HDD est probablement le mauvais outil. Envisagez des miroirs, des SSD, ou déplacer le hot set sur un vdev special / pool SSD.

Task 3: Check sync settings at the dataset/zvol level

cr0x@server:~$ sudo zfs get -r sync tank
NAME            PROPERTY  VALUE  SOURCE
tank            sync      standard  default
tank/db         sync      standard  inherited from tank
tank/vmstore    sync      standard  inherited from tank

Ce que ça signifie : Les écritures sync sont honorées normalement.

Décision : Bien. Assurez-vous maintenant d’avoir un SLOG rapide et protégé contre les pertes de puissance si la latence sync est élevée. Ne « corrigez » pas cela en désactivant sync sauf si les données peuvent être perdues.

Task 4: Confirm SLOG presence and whether it’s actually being used

cr0x@server:~$ sudo zpool iostat -v tank 1 3
                              capacity     operations     bandwidth
pool                        alloc   free   read  write   read  write
--------------------------  -----  -----  -----  -----  -----  -----
tank                         6.20T  1.80T    200   1100  4.1M  13.0M
  raidz2-0                   6.20T  1.80T    150    700  3.5M  10.8M
  nvme0n1p2                      -      -     50    400  620K  2.2M
--------------------------  -----  -----  -----  -----  -----  -----

Ce que ça signifie : Le périphérique de log montre une activité d’écriture. Le trafic sync y atterrit.

Décision : Si le périphérique log est lent ou saturé, upgradez-le. S’il est rapide et que la latence est toujours mauvaise, ce sont les écritures pool le problème (souvent parité + fragmentation).

Task 5: Check pool fullness (allocation pressure gets ugly)

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

Ce que ça signifie : Environ 77% d’utilisation. Pas encore catastrophique.

Décision : Si vous êtes au-dessus de ~80–85% et que vous constatez une douleur sur les écritures aléatoires, planifiez un projet de soulagement de capacité. Les miroirs tolèrent mieux « plein » que RAIDZ, mais aucun pool n’aime la famine d’allocation.

Task 6: Check fragmentation (a proxy for allocator pain)

cr0x@server:~$ sudo zpool list -o name,size,alloc,free,frag,health tank
NAME  SIZE  ALLOC   FREE  FRAG  HEALTH
tank  8T    6.2T   1.8T   41%   ONLINE

Ce que ça signifie : 41% de fragmentation n’est pas un crime, mais sous des overwrites aléatoires cela peut produire de la latence réelle.

Décision : Si la frag est élevée et la charge écriture-aléatoire lourde, planifiez une migration/réplication vers un pool neuf ou ajoutez des vdevs (les miroirs aident davantage) pour donner de l’espace à l’allocateur.

Task 7: Verify ashift (misalignment tax is forever)

cr0x@server:~$ sudo zdb -C tank | egrep -i "ashift|vdev_tree" -n | head
45:    vdev_tree:
78:            ashift: 12

Ce que ça signifie : ashift=12 (secteurs 4K). Généralement correct pour les disques modernes.

Décision : Si vous voyez ashift=9 sur des disques 4K, vous avez construit un problème de performance que vous ne pouvez pas régler par tuning. La solution est rebuild/migrate.

Task 8: Check recordsize (filesystems) and volblocksize (zvols)

cr0x@server:~$ sudo zfs get recordsize,compression tank/db
NAME     PROPERTY     VALUE     SOURCE
tank/db  recordsize   128K      local
tank/db  compression  lz4       local

Ce que ça signifie : Le dataset DB utilise recordsize 128K. Si la DB écrit des pages 8K, cela peut être trop grand selon les patterns d’accès.

Décision : Envisagez de définir recordsize pour correspondre à la taille de page DB uniquement quand le dataset contient réellement des fichiers de données DB et que la charge est fortement orientée overwrite aléatoire. Mesurez avant/après.

Task 9: Check whether compression is helping or hurting CPU

cr0x@server:~$ sudo zfs get compression,compressratio tank/db
NAME     PROPERTY       VALUE  SOURCE
tank/db  compression    lz4    local
tank/db  compressratio  1.68x  -

Ce que ça signifie : La compression est efficace ; moins d’octets atteignent le disque, ce qui aide souvent la performance des petites écritures aléatoires.

Décision : Gardez lz4 sauf si le CPU est saturé. La compression aide fréquemment les pools parité plus que les miroirs car elle réduit le travail de parité par écriture logique.

Task 10: Check whether sync latency is dominated by the log device

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

Device            r/s   w/s   r_await   w_await  aqu-sz  %util
sda              20.0 165.0    12.1     35.8     3.2    92.0
sdb              18.0 170.0    11.7     36.5     3.3    93.5
sdc              22.0 160.0    12.8     34.2     3.1    90.1
nvme0n1          50.0 400.0     0.3      0.8     0.2    18.0

Ce que ça signifie : Les HDD montrent un w_await élevé (~35 ms) et une forte utilisation ; le NVMe log device est correct. Le goulot est le pool, pas le SLOG.

Décision : C’est là où la parité sur HDD perd généralement face aux petites écritures aléatoires. Déplacez la charge vers des miroirs/SSD, ajoutez des vdevs miroir, ou répartissez les charges sur plusieurs pools.

Task 11: Check ARC pressure (memory misses can trigger extra I/O)

cr0x@server:~$ cat /proc/spl/kstat/zfs/arcstats | egrep "c_max|c |size|hits|misses" | head
c                            4    51539607552
c_max                        4    51539607552
size                         4    49821667328
hits                         4    182736455
misses                       4    32736455

Ce que ça signifie : L’ARC est proche du max et a des misses. Les misses ne sont pas automatiquement mauvaises ; le ratio et la charge importent.

Décision : Si l’ARC thrash (pics de misses) et que vous refaites des lectures-modify-write des stripes parité, la latence peut empirer. Ajoutez de la RAM, ajustez les charges, ou isolez les datasets.

Task 12: Look for slow I/O and vdev-level timeouts in kernel logs

cr0x@server:~$ sudo dmesg | egrep -i "zfs|I/O error|blk_update_request|timeout" | tail -n 8
[12345.678901] zfs: vdev disk sdb: slow I/O, 35 sec, zio 0x0000000abcd123
[12347.112233] blk_update_request: I/O error, dev sdb, sector 123456789
[12347.445566] zfs: vdev disk sdb: repairing

Ce que ça signifie : Un disque est lent ou en erreur. Les vdevs parité sont particulièrement sensibles à un lagueur ; la latence tail du vdev entier se dégrade.

Décision : Remplacez ou réparez le disque lent. Ne faites pas du tuning pour contourner un matériel défaillant.

Task 13: Check ZFS write throttle behavior (txg pressure)

cr0x@server:~$ cat /proc/spl/kstat/zfs/dmu_tx | head -n 15
dmu_tx statistics:
  dmu_tx_assigned:  9876543
  dmu_tx_delay:     12345
  dmu_tx_error:     0

Ce que ça signifie : dmu_tx_delay augmente quand ZFS doit throttler les writers parce que le pool ne peut pas flush assez vite.

Décision : Si le delay augmente pendant l’incident, vous êtes lié par le stockage sur les écritures. Les miroirs avec plus de vdevs peuvent réduire cela ; des medias plus rapides ou du shaping de charge aussi.

Task 14: Validate special vdev is actually taking metadata/small blocks

cr0x@server:~$ sudo zpool list -v tank
NAME         SIZE  ALLOC   FREE  CKPOINT  EXPANDSZ   FRAG    CAP  DEDUP  HEALTH  ALTROOT
tank         8T    6.2T   1.8T        -         -    41%    77%  1.00x  ONLINE  -
  raidz2-0   8T    6.2T   1.8T        -         -    41%    77%
special      1T     220G   804G       -         -     9%    21%

Ce que ça signifie : Le vdev special a des allocations, ce qui suggère que les métadonnées (et peut-être les petits blocs) y atterrissent.

Décision : Si le special montre une allocation quasi nulle malgré la configuration, vérifiez les propriétés du dataset comme special_small_blocks. Si les métadonnées sont sur HDD, vous payez une latence évitable.

Task 15: Confirm dataset properties that often sabotage small-write latency

cr0x@server:~$ sudo zfs get atime,logbias,primarycache,secondarycache tank/db
NAME     PROPERTY        VALUE     SOURCE
tank/db  atime           on        default
tank/db  logbias         latency   default
tank/db  primarycache    all       default
tank/db  secondarycache  all       default

Ce que ça signifie : atime est activé (écritures métadonnées supplémentaires), logbias est latency (bon pour sync-heavy), le caching est par défaut.

Décision : Pour les datasets DB/VM, mettez atime=off sauf si vous en avez vraiment besoin. Envisagez logbias=throughput seulement si vous comprenez votre profil sync et avez une bonne raison.

Task 16: Determine whether you’re using zvols for VMs and what block size they use

cr0x@server:~$ sudo zfs list -t volume -o name,volsize,volblocksize,used tank/vmstore
NAME           VOLSIZE  VOLBLOCKSIZE  USED
tank/vmstore   2T       8K            1.4T

Ce que ça signifie : Le zvol utilise des blocs 8K, ce qui peut être raisonnable pour des VMs qui font de petites écritures aléatoires.

Décision : Si volblocksize est énorme (par ex. 128K) pour des images VM aléatoires, vous vous créez une amplification d’écriture. Changer volblocksize nécessite de recréer le zvol — planifiez une migration.

Trois mini-histoires d’entreprise issues du terrain

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

L’entreprise : une SaaS de taille moyenne, un cluster PostgreSQL primaire, quelques replicas de lecture, et un système de jobs background qui aimait les petites mises à jour. Ils ont un nouveau nœud de stockage avec un grand pool RAIDZ2 parce que « nous avons besoin de capacité et redondance ». Sur la feuille de calcul, le débit séquentiel semblait fantastique.

L’hypothèse : « IOPS c’est IOPS, et RAIDZ2 a plus de disques, donc ce sera plus rapide. » Personne ne l’a dit aussi crûment, mais c’était présent dans l’architecture.

Le jour du lancement s’est bien passé. Puis le dataset a commencé à se fragmenter et le working set a grossi. Le symptôme n’était pas « faible débit ». Le symptôme était des blocages périodiques de 1–3 secondes sur les commits. Les threads applicatifs s’accumulaient, le p99 partait en vrille, et l’astreinte avait le schéma d’alerte familier : CPU bas, réseau bas, disque occupé, mais peu de données transférées.

Le postmortem a révélé le coupable réel : écritures sync petites tombant sur un vdev parité avec HDD. Le ZIL était sur le pool (pas de SLOG), donc chaque fsync traînait les disques dans la bagarre. Sous concurrence, l’array est devenu un générateur de latence tail.

La solution n’était pas futée. Ils ont migré la DB primaire vers un pool construit à partir de plusieurs vdevs miroir sur SSD, ont gardé RAIDZ2 pour les sauvegardes et les données volumineuses, et soudain le même code applicatif semblait « optimisé ». L’architecture de stockage était le bug.

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

Autre endroit, autre problème : cluster de virtualisation exécutant des dizaines de charges mixtes. Quelqu’un a remarqué que le pool parité « gaspillait » du potentiel et a proposé un tweak simple : désactiver sync sur le dataset VM et augmenter recordsize pour réduire l’overhead. C’était vendu comme « sûr parce que l’hyperviseur met déjà en cache les écritures ».

Pendant une semaine, tout semblait parfait. Les graphiques de latence se sont lissés. On a félicité le ticket de changement. Puis un hôte a subi un crash brutal — coupure d’alimentation + reboot sale. Plusieurs VMs sont revenues avec des erreurs système de fichiers. Une a eu une base de données qui ne démarrait pas proprement.

La leçon inconfortable : sync=disabled ne signifie pas « un peu moins sûr ». Cela signifie « ZFS est autorisé à mentir sur la durabilité ». Pour images VM et bases de données, ce mensonge devient un incident, et il survient toujours au pire moment.

Ils sont revenus à sync=standard, ont ajouté des SLOG miroirs avec protection contre la perte de puissance, et ont ajusté proprement : volblocksize adapté pour les zvols, miroirs pour les niveaux sensibles à la latence, et parité pour les niveaux de capacité.

Blague n°2 : Désactiver sync, c’est comme enlever les détecteurs de fumée parce qu’ils bipent — maison plus calme, futur excitant.

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

Une organisation finance avec des exigences d’audit strictes utilisait ZFS pour un mix de services de fichiers et un système transactionnel. Ils n’étaient pas fancy. Ils étaient disciplinés. Chaque changement de pool demandait une baseline de performance et un plan de rollback. Chaque trimestre ils testaient restauration et basculement. Chaque mois ils examinaient la santé des pools et les résultats de scrub.

Un matin, un pool parité a commencé à montrer une latence d’écriture plus élevée et des erreurs de checksum occasionnelles. Rien n’était « down », mais l’équipe l’a traité comme un indicateur précoce, pas comme du bruit de fond. Ils ont vérifié zpool status, vu un disque avec des erreurs croissantes, et l’ont remplacé dans une fenêtre contrôlée.

Pendant le resilver, ils ont throttlé les jobs batch et déplacé la charge transactionnelle vers un pool basé sur miroirs qu’ils gardaient spécifiquement pour les datasets « hot ». Parce que c’était planifié, pas improvisé, l’entreprise a à peine remarqué.

Après l’incident, l’équipe a eu le type de postmortem que les SRE apprécient : court, calme, et principalement sur ce qui a bien fonctionné. La pratique ennuyeuse — scrubs, baselines et ségrégation des charges — était la raison entière pour laquelle cela n’est pas devenu un titre dans les médias.

Erreurs courantes : symptômes → cause racine → correctif

Mistake 1: “RAIDZ has more disks, so it must have more IOPS”

Symptômes : pics de latence d’écriture p99, faible MB/s mais %util disque élevé, commits DB en pause.

Cause racine : Un seul vdev RAIDZ est une seule voie d’I/O aléatoire ; les petites écritures déclenchent l’overhead partial-stripe et des patterns RMW.

Fix : Utilisez plusieurs vdevs miroir pour les couches écriture-aléatoire. Gardez RAIDZ pour la capacité/le streaming. Si vous êtes coincé avec RAIDZ, ajoutez un vdev special et assurez-vous que sync/SLOG est correct.

Mistake 2: Pool trop plein pour que la géométrie parité reste heureuse

Symptômes : dégradation progressive des performances, CPU d’allocateur élevé, dmu_tx delays croissants, fragmentation augmentée.

Cause racine : Une forte utilisation du pool réduit les choix d’allocation ; la fragmentation augmente, les écritures partial-stripe deviennent plus fréquentes.

Fix : Gardez les pools sous ~80% pour les écritures aléatoires lourdes. Ajoutez de la capacité (de préférence de nouveaux vdevs), ou migrez vers un pool frais.

Mistake 3: Pas de SLOG pour les charges sync-heavy (ou utilisation d’un SSD bon marché comme SLOG)

Symptômes : latence fsync atroce, WAL DB en pause, invités VM signalent latence de flush disque.

Cause racine : Les écritures ZIL atterrissent sur le pool principal ou sur un périphérique log avec mauvaise latence ou sans protection power-loss.

Fix : Ajoutez un SLOG en miroir, protégé contre la perte de puissance, pour les charges sync. Validez avec zpool iostat -v et les métriques de latence du périphérique.

Mistake 4: Mauvais ashift

Symptômes : performance chronique des petits I/O mauvaise malgré le tuning ; amplification d’écriture mystérieuse.

Cause racine : ashift trop petit provoque read-modify-write au niveau du périphérique à cause du désalignement des secteurs.

Fix : Rebuild/migrate les vdevs avec le bon ashift. Il n’existe pas de sysctl magique pour l’annuler.

Mistake 5: Traiter recordsize comme un « bouton performance » universel

Symptômes : certaines charges s’améliorent, d’autres empirent ; overhead métadonnées en hausse ; sauvegardes ralenties.

Cause racine : recordsize affecte la mise en page sur disque et le comportement des métadonnées ; le réduire partout augmente l’overhead.

Fix : Tuner par dataset. Les fichiers de données DB peuvent vouloir 8K/16K ; les archives média veulent 1M. Mesurez avec une charge réaliste.

Mistake 6: Ignorer les métadonnées comme une charge à part entière

Symptômes : « Mais on n’écrit que peu de données » alors que la latence est horrible ; opérations de répertoire lentes ; snapshots VM lents.

Cause racine : Les I/O de métadonnées dominent ; les vdevs parité gèrent mal les mises à jour de métadonnées sous churn.

Fix : Utilisez un vdev special (SSD en miroir) pour les métadonnées et possiblement les petits blocs ; gardez-le redondant ; surveillez-le comme une donnée de production (parce que c’en est une).

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

Design checklist: choosing mirrors vs parity for a new pool

  1. Si ce sont des bases de données, du stockage VM ou tout ce qui est sync-heavy : partez par défaut sur plusieurs vdevs miroir.
  2. Si ce sont des sauvegardes, médias, logs, dumps analytiques : RAIDZ convient ; optimisez pour la capacité.
  3. Comptez les vdevs, pas les disques : les IOPS aléatoires s’échelonnent avec les top-level vdevs. Planifiez assez de vdevs miroir pour atteindre les IOPS cibles avec marge.
  4. Planifiez la story sync : acceptez la latence sur les disques du pool, ou ajoutez un SLOG miroir approprié. Ne « résolvez » pas cela avec sync=disabled.
  5. Planifiez les métadonnées : si la charge est riche en métadonnées, prévoyez un vdev special en miroir.
  6. Gardez de la marge d’espace : concevez pour rester en dessous de ~80% si les écritures aléatoires importent.
  7. Choisissez ashift délibérément : supposez secteurs 4K au minimum ; préférez ashift=12 sauf raison contraire.
  8. Décidez de la taille de bloc par dataset : définissez recordsize/volblocksize pour correspondre à la charge, pas à votre humeur.

Migration plan: moving from RAIDZ to mirrors without drama

  1. Construisez le nouveau pool (miroirs, ashift correct, tiering SSD si nécessaire) à côté de l’ancien pool.
  2. Définissez les propriétés des datasets sur le nouveau pool avant la copie (recordsize, compression, atime, sync, special_small_blocks).
  3. Utilisez ZFS send/receive pour les filesystems ; pour les zvols, planifiez un downtime invité ou une stratégie de réplication cohérente avec votre hyperviseur.
  4. Exécutez une baseline de performance en parallèle avant la bascule : mesurez latence fsync, IOPS écriture aléatoire, p99.
  5. Coupez avec rollback : gardez l’ancien pool en lecture seule pendant une fenêtre ; surveillez erreurs et performance.
  6. Après la bascule revérifiez la fragmentation et le taux de remplissage ; corrigez le prochain goulot (souvent le réseau ou le CPU).

Operational checklist: keeping small random write performance from decaying

  1. Surveillez l’utilisation du pool et les tendances de fragmentation mensuellement.
  2. Faites des scrubs selon le calendrier ; traitez les erreurs de checksum comme urgentes, pas cosmétiques.
  3. Suivez la latence, pas seulement le débit. Le p99 write latency est la vérité.
  4. Gardez les firmwares cohérents entre les SSD utilisés pour SLOG/special vdev.
  5. Faites des tests de charge contrôlés après changements majeurs (kernel, version ZFS, remplacement de disques).

FAQ

1) Le RAIDZ est-il toujours plus lent que les miroirs ?

Non. Pour les lectures/écritures séquentielles larges et les niveaux orientés capacité, RAIDZ peut être excellent. La douleur concerne spécifiquement les petites écritures aléatoires et les patterns sync-heavy.

2) Pourquoi les miroirs « s’échelonnent » mieux pour l’I/O aléatoire ?

Parce que chaque top-level vdev est une unité d’ordonnancement. Beaucoup de vdevs miroir donnent à ZFS plus de voies indépendantes pour les opérations aléatoires. Un seul gros vdev RAIDZ reste une voie.

3) Si j’ajoute plus de disques au RAIDZ, est-ce que j’obtiens plus d’IOPS ?

Vous gagnez plus de bande passante agrégée et parfois une meilleure concurrence, mais vous élargissez aussi les stripes et augmentez la coordination de parité. Pour les petites écritures aléatoires, cela s’échelonne rarement comme vous le souhaitez.

4) Un SLOG rapide peut-il rendre RAIDZ bon pour les petites écritures aléatoires ?

Un SLOG aide la latence des écritures sync en accélérant les commits ZIL. Il n’élimine pas le surcoût parité pour les écritures de données principales qui atterrissent encore dans le pool lors des flush txg.

5) Dois-je mettre sync=disabled pour la performance ?

Seulement pour des données que vous pouvez perdre sans regret. Pour les bases de données et images VM, c’est une panne qui attend un événement d’alimentation. Utilisez un SLOG approprié et des miroirs à la place.

6) Un vdev special remplace-t-il le besoin de miroirs ?

Non. Un vdev special peut grandement améliorer la performance des métadonnées et des petits blocs, ce qui peut rendre RAIDZ moins pénible pour certaines charges. Il ne change pas l’arithmétique de la parité pour les blocs de données normaux.

7) RAIDZ sur SSD est-il correct pour les petites écritures aléatoires ?

Mieux que RAIDZ sur HDD, mais souvent encore derrière plusieurs miroirs pour la latence tail, surtout sous charges sync-heavy ou fragmentées. Les SSD masquent le problème ; ils ne l’enlèvent pas.

8) Quels réglages de dataset améliorent le plus le comportement des petites écritures aléatoires ?

compression=lz4, atime=off pour les datasets DB/VM, recordsize/volblocksize adaptés à la charge, et une stratégie sync/SLOG correcte. Un vdev special pour les charges riches en métadonnées est un levier important.

9) Quelle est la méthode la plus fiable pour prouver que la parité est le goulot ?

Corrélez les pics de latence applicative avec zpool iostat -v, les await/%util disque via iostat -x, et les indicateurs de throttling ZFS comme dmu_tx_delay. Si les disques du pool sont occupés avec un await élevé et un MB/s faible, vous êtes IOPS/latence-bound.

10) Quelle est la phrase à retenir pour les SRE ?

« L’espoir n’est pas une stratégie. » — General Gordon R. Sullivan

Prochaines étapes à exécuter cette semaine

  1. Classifiez vos datasets : lesquels sont sensibles à la latence (DB/VM), lesquels sont orientés débit/capacité (sauvegardes, archives).
  2. Exécutez le mode opératoire de diagnostic rapide pendant une fenêtre d’incident réelle : capturez zpool iostat -v et iostat -x quand les utilisateurs se plaignent.
  3. Réglez les gains faciles : atime=off là où c’est approprié, vérifiez compression=lz4, confirmez la politique sync et la santé du SLOG.
  4. Si vous êtes sur de la parité pour des workloads chauds : décidez si vous allez (a) ajouter un « hot pool » basé sur des miroirs, (b) ajouter un vdev special et du tiering SSD, ou (c) redesign vers des miroirs définitivement.
  5. Planifiez la migration comme un changement opérationnel, pas un hobby de weekend : baseline, répliquez, basculez avec rollback.

Si votre charge consiste en petites écritures aléatoires et que votre entreprise tient à la latence, les miroirs ne sont pas un luxe. Ce sont le choix par défaut sensé. La parité sert quand l’efficacité de capacité compte plus que la latence tail. Choisissez délibérément, et ZFS fera ce qu’il fait de mieux : garder vos données correctes pendant que vous dormez.

← Précédent
Corruption silencieuse avec ZFS : ce que ZFS détecte et que RAID ignore souvent
Suivant →
Deux connexions Internet au bureau : basculement VPN sur MikroTik sans chaos

Laisser un commentaire