ZFS a une réputation : « on règle et on oublie ». C’est en grande partie justifié — jusqu’à ce que ça ne le soit plus. Une propriété de dataset, recordsize, décide discrètement si votre stockage se comporte comme une piste bien huilée ou comme un carrousel à bagages plein de roulettes en colère. Ce réglage ne résout pas tous les problèmes, mais il amplifie les bonnes décisions et punit les mauvaises.
Si vous gérez des systèmes de production — flottes de VM, bases de données, répertoires NFS utilisateurs, cibles de sauvegarde — recordsize est le bouton que vous touchez lorsque la performance est « mystérieusement correcte en dev » puis devient « mystérieusement coûteuse en prod ». Cet article est un guide pratique : ce que recordsize contrôle réellement, comment il interagit avec la compression et le cache, et comment choisir des valeurs qui n’engendrent pas d’amplification d’écriture cachée ou des pics de latence.
Ce qu’est vraiment le recordsize (et ce qu’il n’est pas)
recordsize est une propriété de dataset ZFS qui contrôle la taille maximale d’un bloc de données de fichier que ZFS tentera d’utiliser pour les fichiers ordinaires dans ce dataset. Considérez-le comme « la taille de morceau préférée par ZFS quand il le peut ». Ce n’est pas une promesse que chaque bloc aura cette taille.
Nuances importantes pour la production :
- C’est un maximum, pas un minimum. ZFS utilisera des blocs plus petits quand il le faut (fichiers petits, blocs de queue, allocations fragmentées, ou lorsque la compression réduit la taille des blocs).
- Ça s’applique aux blocs de données de fichiers. Ce n’est pas la même chose que
volblocksize(pour les zvols), et cela ne définit pas directement les tailles des blocs de métadonnées. - Ça influence la quantité de données à lire/réécrire quand une partie d’un fichier change. C’est le point d’appui des performances : des écritures aléatoires et petites dans un grand record peuvent déclencher un schéma de lecture-modification-écriture au niveau des blocs.
- Ça ne réécrit pas rétroactivement les blocs existants. Changer
recordsizeaffecte les nouvelles écritures. Les blocs anciens restent tels quels jusqu’à réécriture (par le flux normal des applications ou par des opérations de copie/réécriture que vous lancez).
Blague n°1 (courte, pertinente) : Choisir le mauvais recordsize revient à acheter un camion pour livrer une enveloppe — techniquement ça fonctionne, mais votre facture d’essence va développer des émotions.
Pourquoi ce réglage unique domine les performances des fichiers
Pour de nombreuses charges, les « performances fichier » se résument à quelques comportements mesurables :
- Latence pour les lectures/écritures aléatoires petites (bases de données, images de VM, boîtes mail)
- Débit pour les lectures/écritures séquentielles larges (médias, sauvegardes, blobs type objet)
- Amplification d’écriture quand de petites mises à jour déclenchent la réécriture de gros blocs
- Efficacité du cache dans l’ARC/L2ARC : met-on en cache la bonne granularité ?
recordsize touche ces quatre aspects. Des enregistrements plus grands signifient moins de pointeurs, moins d’opérations I/O pour un scan séquentiel, et souvent de meilleurs ratios de compression. Des enregistrements plus petits signifient moins de « dégâts collatéraux » quand une petite partie d’un fichier change, et ils peuvent réduire le comportement « lire plus que demandé » quand une application effectue des lectures aléatoires.
En termes opérationnels : recordsize est un des rares réglages ZFS qui (a) est par dataset, (b) est facile à changer en sécurité, et (c) produit des différences immédiates et mesurables. Cette combinaison est rare, et c’est pourquoi il finit par décider « 80 % des performances des fichiers » en pratique — surtout quand le goulot d’étranglement est un décalage entre le pattern I/O et la configuration plutôt que la bande passante brute du disque.
Faits et contexte historique expliquant les valeurs par défaut actuelles
Un peu de contexte concret aide à expliquer pourquoi les valeurs par défaut et la « sagesse populaire » existent :
- ZFS a hérité d’un monde où les pages 4K et les secteurs disque 4K étaient courants. Beaucoup de chemins I/O d’OS et de bases de données opèrent naturellement en unités de 4K ou 8K, ce qui influence les tailles de bloc « raisonnables » pour I/O aléatoire.
- La valeur par défaut classique de ZFS pour recordsize était 128K. Ce défaut n’est pas magique ; c’est un compromis ciblant des systèmes de fichiers généralistes avec beaucoup d’I/O séquentielle mélangée.
- Les disques anciens menteaient souvent sur leurs secteurs. L’ère 512e a créé des problèmes d’alignement douloureux ;
ashiftde ZFS a été la contre-mesure « toujours aligner à une puissance de deux ». - Les systèmes de fichiers copy-on-write ont rendu les écrasements partiels coûteux. Les systèmes traditionnels pouvaient écraser sur place ; ZFS écrit généralement de nouveaux blocs, ce qui modifie le profil de coût des petites mises à jour.
- La compression devenue suffisamment « gratuite » a changé les priorités de réglage. Les CPU modernes ont rendu la compression légère pratique, et les gros enregistrements compressent souvent mieux (moins d’en-têtes, plus de redondance par bloc).
- L’explosion des VM a créé une nouvelle classe de « fichiers qui se comportent comme des disques ». QCOW2/VMDK/raw sont des fichiers, mais leurs patterns d’accès ressemblent à des périphériques bloc —
recordsizedoit suivre cette réalité. - La latence des SSD a rendu les I/O petits plus faisables, mais aussi plus sensibles. Quand le média est rapide, les surcoûts (checksums, métadonnées, amplification d’écriture) deviennent une fraction plus importante du temps total.
- L’ARC a rendu le caching plus stratégique que « plus de RAM = plus vite ». Cacher des morceaux de 128K pour des lectures aléatoires de 8K peut être gaspilleur ; cacher des morceaux de 16K pour du streaming séquentiel peut être trop coûteux.
Comment ZFS transforme vos écritures en réalité sur disque
Pour régler recordsize de façon responsable, il vous faut un modèle mental de ce que ZFS fait avec vos octets.
Le piège « lecture-modification-écriture » lié au recordsize
Supposons qu’une application mette à jour 8K au milieu d’un gros fichier. Si le bloc pertinent sur disque (record) est de 128K, ZFS ne peut pas simplement écraser 8K « en place » comme faisaient les systèmes traditionnels. Il doit généralement :
- Lire l’ancien bloc de 128K (sauf s’il est déjà dans l’ARC)
- Modifier la portion de 8K en mémoire
- Écrire un nouveau bloc de 128K ailleurs (copy-on-write)
- Mettre à jour les métadonnées pour pointer vers le nouveau bloc
Vue simplifiée, mais la conclusion tient : les petites écritures aléatoires dans de grands enregistrements créent des I/O supplémentaires et de l’agitation d’allocation. Sur pools HDD, l’I/O supplémentaire signifie des seeks pénibles. Sur pools SSD, cela se traduit par de l’amplification d’écriture et des fluctuations de latence, surtout lors d’écritures synchrones ou sous forte concurrence.
Pourquoi de plus grands enregistrements peuvent être spectaculaires pour le séquentiel
Si votre charge lit et écrit de grandes plages contiguës — flux de sauvegarde, sorties d’encodage média, fichiers parquet, archives de logs — de plus grands enregistrements réduisent les surcoûts :
- Moins de pointeurs de blocs et d’opérations de métadonnées
- Moins de requêtes I/O pour la même quantité de données
- Souvent une meilleure efficacité de compression
- Meilleur comportement de prélecture : ZFS peut charger des morceaux significatifs
Sur disques tournants, c’est la différence entre un débit soutenu et un graphe semblable à un sismographe. Sur SSD, c’est la différence entre saturer la bande passante et saturer d’abord le CPU/la latence IOPS.
Granularité ARC : mettre en cache la bonne « forme » de données
L’ARC met en cache des blocs ZFS. Si votre application fait des lectures aléatoires de 8K mais que vos blocs font 128K, chaque miss de cache ramène 128K. C’est une « amplification de lecture ». Parfois c’est utile (localité spatiale), mais pour des patterns vraiment aléatoires, cela consomme l’ARC inutilement et éjecte des données utiles plus rapidement.
Inversement, si votre charge est des lectures séquentielles larges, un recordsize trop petit augmente le surcoût : plus de blocs à gérer, plus d’opérations de checksum, plus de recherches de métadonnées et moins d’efficacité de la prélecture.
Compression et recordsize : alliés avec des frontières
La compression se fait par bloc. Les blocs plus grands compressent souvent mieux parce que la redondance est visible sur une fenêtre plus large. Mais la compression signifie aussi que votre taille physique sur disque peut être plus petite que le recordsize, ce qui change la forme de l’I/O :
- Sur des données compressibles, un grand
recordsizepeut être un gain gratuit : moins de blocs, moins d’I/O physique. - Sur des données incompressibles, un grand
recordsizechange principalement la granularité I/O et le coût de réécriture. - Sur des données mixtes, vous verrez un mélange de tailles physiques ; le « maximum » reste important pour l’amplification de réécriture.
Recordsize vs volblocksize (ne pas confondre)
Les datasets stockent des fichiers ; les zvols présentent des périphériques bloc. Pour les zvols, le bouton équivalent est volblocksize, qui se définit à la création et est difficile à changer sans reconstruire le zvol. Une erreur fréquente en production est de régler recordsize sur un dataset qui contient des images VM à l’intérieur d’un zvol — ou de régler volblocksize pour des fichiers. Ce sont des couches différentes.
Associer les charges de travail au recordsize (avec des chiffres réels)
Voici l’affectation pratique que j’utilise quand je suis responsable des réveils nocturnes.
Partages de fichiers généraux (répertoires personnels, fichiers mixtes de bureau)
Accès typique : petits documents, téléchargements occasionnels volumineux, beaucoup d’opérations métadonnées petites. Le 128K par défaut est souvent acceptable. Si vous avez beaucoup d’accès aléatoires dans de gros fichiers (ex. archives PST énormes), envisagez de diminuer, mais n’optimisez pas à l’aveugle ; mesurez d’abord.
Choix courant : 128K. Parfois 64K si beaucoup d’I/O aléatoire petite dans de gros fichiers.
Bases de données (Postgres, MySQL/InnoDB) stockées comme fichiers dans des datasets
La plupart des bases font des lectures/écritures de 8K–16K (varie selon le moteur et la configuration). Si vous stockez les fichiers de données de la base sur un dataset (pas un zvol), un grand recordsize peut provoquer lecture-modification-écriture et inefficacité ARC. Un recordsize proche de la taille de page de la BD est généralement préférable.
Choix courant : 16K pour Postgres (pages 8K mais 16K s’aligne avec le comportement pratique de ZFS) ; 16K pour beaucoup de configurations InnoDB (souvent 16K). Parfois 8K pour des usages très sensibles à la latence, mais surveillez le surcoût.
Images disque VM en tant que fichiers (qcow2, raw, vmdk) sur datasets
C’est le cas classique du « fichier qui se comporte comme un disque ». L’OS invité fait des écritures aléatoires de 4K ; l’hyperviseur écrit dans un fichier. Un grand recordsize vous punit par l’amplification d’écriture et la fragmentation. Vous voulez des blocs plus petits. Beaucoup d’équipes choisissent 16K ou 32K comme compromis ; 8K peut être approprié mais augmente les métadonnées.
Choix courant : 16K ou 32K.
Cibles de sauvegarde, archives média, archives de logs
Séquentiel, streaming, append-heavy, généralement sans réécriture en place. Plus grand est mieux : réduire le surcoût, augmenter le débit en streaming, améliorer le ratio de compression. Pour OpenZFS moderne, 1M de recordsize est un choix légitime pour ces datasets — quand la charge est vraiment du grand séquentiel.
Choix courant : 1M (ou 512K si vous voulez un compromis plus sûr).
Images de conteneur et caches de build
Cela peut être pénible : beaucoup de petits fichiers et de métadonnées, mais aussi de grandes couches. Régler le seul recordsize ne résoudra pas tout ; vous vous soucierez aussi des vdevs spéciaux pour les métadonnées, de la compression et peut‑être de atime=off. Pour le recordsize, le défaut est généralement correct ; si la charge est dominée par de nombreuses petites lectures aléatoires dans de gros blobs, envisagez 64K, mais mesurez.
Choix courant : 128K, parfois 64K.
Fichiers analytiques (parquet, ORC, données en colonne)
Souvent lus par larges segments mais avec des sauts. Si vos requêtes scannent de larges plages, de plus grands enregistrements aident. Si vous faites beaucoup de « lire un peu à de nombreux endroits », plus petit peut réduire l’amplification de lecture. C’est spécifique à la charge ; commencez par 1M pour du scan pur, puis ajustez.
Choix courant : 512K–1M pour scan intensif ; 128K–256K pour mixte.
Blague n°2 (courte, pertinente) : En stockage, « une taille unique convient à tous » ressemble à « un timeout convient à tous » — c’est vrai seulement jusqu’à votre première revue d’incident.
Trois mini-récits du monde de l’entreprise
1) Incident causé par une mauvaise hypothèse : « C’est un fichier, donc recordsize=1M sera plus rapide »
Ils avaient un cluster de virtualisation qui stockait les disques VM comme fichiers raw sur un dataset ZFS. Quelqu’un avait lu un billet vantant un grand recordsize pour améliorer le débit des sauvegardes et a décidé que le dataset VM devait être recordsize=1M aussi. Ça semblait raisonnable : plus gros blocs, moins d’I/O, plus de vitesse.
Le changement a été déployé pendant une fenêtre de maintenance. Le premier signal d’alerte n’était pas un benchmark ; c’était le support. « Les VMs Windows sont lentes. » Puis la supervision s’est allumée : IO wait élevé, profondeurs de file d’attente en hausse, et un motif étrange — les lectures semblaient correctes, mais les écritures avaient des variations. Les graphes de l’hyperviseur montraient des rafales : beaucoup de petites écritures se sont transformées en une activité d’écriture backend beaucoup plus importante.
À l’analyse, le pool ZFS effectuait beaucoup de lecture-modification-écriture. La charge était typique d’un comportement VM : petites écritures aléatoires, mises à jour de métadonnées du filesystem invité, journaling, et syncs périodiques. Avec des enregistrements de 1M, chaque petite mise à jour pouvait réécrire un bloc énorme (ou au moins déclencher des schémas d’allocation de gros blocs). Les taux de cache ARC ne les sauvaient pas ; l’ensemble de travail dépassait la RAM et le pattern aléatoire rendait la mise en cache moins efficace.
La résolution n’a pas été héroïque. Ils ont créé un nouveau dataset avec recordsize=16K, ont migré les images VM avec une copie forçant la réécriture des blocs, et les symptômes ont largement disparu. Le post-mortem fut clair : « fichier » n’est pas une description de charge. Si votre « fichier » est un disque virtuel, traitez-le comme un I/O bloc.
Le gain culturel fut la suite : ils ont ajouté une étape légère de revue de conception de stockage pour les nouveaux datasets. Pas une bureaucratie — juste une check-list de dix minutes : pattern d’accès, tailles I/O attendues, comportement sync, et un choix explicite de recordsize.
2) Optimisation qui a échoué : réduire le recordsize partout pour diminuer la latence
Une autre organisation a eu un incident de latence sur un système de base de données et en a tiré la conclusion simple : « des blocs plus petits sont plus rapides. » C’est compréhensible quand on a été brûlé par l’amplification d’écriture. Ils ont donc standardisé recordsize=8K sur la plupart des datasets : bases, répertoires personnels, stockage d’artefacts, sauvegardes, tout.
Les rapports du mois suivant étaient étranges. La latence BD s’est améliorée un peu, certes. Mais le système de sauvegarde a commencé à rater ses fenêtres. La chaîne de traitement média a mis plus de temps pour lire et écrire de gros fichiers. L’utilisation CPU sur les nœuds de stockage a augmenté, et pas parce que les utilisateurs étaient soudainement plus heureux. C’était de l’overhead : plus de blocs, plus d’agitation de métadonnées, plus de travail de checksum, plus d’opérations I/O pour déplacer la même quantité de données.
Puis le vrai coup : leur périphérique L2ARC semblait « occupé » mais n’améliorait pas les résultats. Un recordsize petit signifiait plus d’entrées cache et de la pression sur les métadonnées. Cela augmentait la charge de gestion de l’ensemble de travail, et le cache devenait moins efficace pour les workloads de streaming qui bénéficiaient auparavant des gros enregistrements et de la prélecture.
La correction fut d’arrêter de traiter recordsize comme un réglage global. Ils ont séparé les datasets par classe de charge : 16K pour fichiers BD, 128K pour partages généraux, 1M pour cibles de sauvegarde et gros artefacts. Le résultat n’a pas été que des performances — c’était de la prévisibilité. Les systèmes de streaming ont retrouvé leur débit ; les systèmes d’I/O aléatoire ont vu leur amplification baisser ; et le CPU de stockage a cessé de se comporter comme un mineur de crypto.
La leçon retenue : un recordsize « universel » est un compromis universel, et les compromis sont le terreau des incidents.
3) Une pratique ennuyeuse mais correcte qui a sauvé la mise : dataset par workload avec discipline de migration
Voici le type d’histoire dont personne ne se vante, mais c’est la raison pour laquelle certaines équipes dorment sur leurs deux oreilles.
Cette entreprise exploitait un pool ZFS partagé pour plusieurs services internes : une flotte PostgreSQL, un service NFS pour répertoires personnels, et un dépôt de sauvegarde. Il y a des années, ils ont pris l’habitude de créer un dataset par classe de workload avec des propriétés explicites : recordsize, compression, atime, politique de sync, et conventions de point de montage. Ce n’était pas sophistiqué. C’était simplement cohérent.
Quand un nouveau service analytique est arrivé — gros fichiers en colonne, principalement lectures séquentielles — l’équipe de stockage n’a pas touché les datasets existants. Ils ont créé un nouveau dataset avec recordsize=1M et la compression adéquate, puis ont onboardé le service là-bas. Les performances furent bonnes dès le premier jour, en grande partie parce qu’ils n’ont pas forcé une charge analytique à vivre sur un dataset réglé pour des répertoires personnels.
Des mois plus tard, une régression dans le pipeline analytique a provoqué plus de lectures aléatoires. La latence a augmenté. Le SRE en astreinte a fait un diagnostic rapide : confirmer le changement de pattern I/O, inspecter les tailles de blocs, vérifier le comportement ARC. Ils ont réduit modérément le recordsize (de 1M à 256K) en staging, validé avec des requêtes réalistes, puis effectué une migration contrôlée qui a réécrit les données chaudes. Pas d’opération chirurgicale en urgence en production. Pas de « changeons la configuration du pool et croisons les doigts ».
La journée fut sauvée par l’ennui : datasets nommés, propriétés explicites, et une habitude « si tu changes le recordsize, planifie comment réécrire les données ». Le ticket d’incident n’est jamais devenu un appel d’incident. C’est le meilleur type de succès.
Tâches pratiques : commandes, sorties et interprétation
Les tâches suivantes sont écrites comme je les exécuterais sur un hôte Linux avec OpenZFS. Ajustez les noms de pool/dataset selon vos besoins. Chaque commande inclut ce qu’il faut chercher pour prendre une décision, pas seulement produire une sortie.
Task 1: Lister les datasets et le recordsize
cr0x@server:~$ zfs list -o name,used,avail,recordsize,compression,mountpoint
NAME USED AVAIL RECORDSIZE COMPRESS MOUNTPOINT
tank 3.21T 7.80T 128K lz4 /tank
tank/vm 1.40T 7.80T 16K lz4 /tank/vm
tank/db 820G 7.80T 16K lz4 /tank/db
tank/backups 900G 7.80T 1M zstd /tank/backups
Interprétation : vérifiez que les datasets correspondent aux classes de workload. Si votre dataset VM est en 1M, vous avez probablement trouvé votre prochain ticket de performance.
Task 2: Vérifier une propriété de dataset (et les valeurs héritées)
cr0x@server:~$ zfs get -o name,property,value,source recordsize tank/vm
NAME PROPERTY VALUE SOURCE
tank/vm recordsize 16K local
Interprétation : si SOURCE est inherited, confirmez que le dataset parent est configuré intentionnellement. L’héritage accidentel est courant dans les environnements « quick fix ».
Task 3: Changer le recordsize en toute sécurité (n’affecte que les nouvelles écritures)
cr0x@server:~$ sudo zfs set recordsize=16K tank/vm
cr0x@server:~$ zfs get -o name,property,value,source recordsize tank/vm
NAME PROPERTY VALUE SOURCE
tank/vm recordsize 16K local
Interprétation : ceci ne réécrit pas les blocs existants. Si vous avez besoin d’impact immédiat, planifiez une réécriture/migration (voir tâches plus bas).
Task 4: Inspecter les tailles de blocs réellement utilisées par un fichier existant
cr0x@server:~$ sudo zdb -bbbbb tank/vm | head -n 20
Dataset tank/vm [ZPL], ID 56, cr_txg 124567, 1.40T, 1045320 objects
Object lvl iblk dblk dsize dnsize lsize %full type
96 1 128K 16K 1.40T 512B 1.40T 100% ZFS plain file
Interprétation : regardez dblk (taille du bloc de données). Si votre dataset est à 16K mais que des fichiers existants montrent 128K, ils ont été écrits avant le changement ou par un autre pattern d’écriture.
Task 5: Vérifier le ratio de compression et l’espace logique vs physique
cr0x@server:~$ zfs get -o name,property,value -H compressratio,logicalused,used tank/backups
tank/backups compressratio 1.82x
tank/backups logicalused 1.63T
tank/backups used 900G
Interprétation : si la compression aide, un recordsize plus grand peut amplifier cet avantage pour les workloads séquentiels. Si compressratio est ~1.00x sur des données incompressibles, le choix de recordsize importe surtout pour la forme I/O et le coût de réécriture.
Task 6: Mesurer rapidement I/O aléatoire vs séquentiel avec fio (vérification de base)
cr0x@server:~$ fio --name=randread --directory=/tank/vm --rw=randread --bs=4k --iodepth=32 --numjobs=4 --size=4G --time_based --runtime=30 --group_reporting
randread: (groupid=0, jobs=4): err= 0: pid=18432: Thu Dec 24 12:10:33 2025
read: IOPS=38.2k, BW=149MiB/s (156MB/s)(4468MiB/30001msec)
slat (usec): min=3, max=240, avg=12.7, stdev=4.9
clat (usec): min=60, max=3900, avg=820.2, stdev=210.5
Interprétation : exécutez ceci sur le dataset correspondant à votre workload. Si votre appli fait des I/O aléatoires 4K/8K et que la latence est mauvaise, le recordsize peut être trop grand (ou les réglages sync/log device limitent). Utilisez fio pour confirmer la forme I/O avant de modifier.
Task 7: Observer l’I/O et la latence du pool sous charge réelle
cr0x@server:~$ iostat -x 1
Linux 6.8.0 (server) 12/24/2025 _x86_64_ (32 CPU)
avg-cpu: %user %nice %system %iowait %steal %idle
9.2 0.0 6.1 18.7 0.0 66.0
Device r/s w/s rMB/s wMB/s avgrq-sz avgqu-sz await svctm %util
nvme0n1 420.0 980.0 52.0 120.0 180.2 4.6 3.6 0.4 54.0
nvme1n1 410.0 970.0 51.3 119.1 179.6 4.8 3.8 0.4 55.0
Interprétation : si await augmente avec de petits I/O, vous pouvez subir la latence d’écritures synchrones, la saturation de périphériques, ou l’amplification d’écriture. Le recordsize devient suspect quand la bande passante d’écriture semble élevée par rapport au taux d’écriture réel de l’application.
Task 8: Surveiller l’I/O au niveau ZFS avec zpool iostat
cr0x@server:~$ zpool iostat -v 1
capacity operations bandwidth
pool alloc free read write read write
-------------------------- ----- ----- ----- ----- ----- -----
tank 3.21T 7.80T 8.10K 12.5K 210M 380M
mirror 1.60T 3.90T 4.05K 6.20K 105M 190M
nvme0n1 - - 2.02K 3.10K 52.5M 95.0M
nvme1n1 - - 2.03K 3.10K 52.5M 95.0M
-------------------------- ----- ----- ----- ----- ----- -----
Interprétation : comparez le débit applicatif avec la bande passante du pool. Si le pool écrit bien plus que l’application le revendique, vous observez de l’amplification (mismatch de recordsize, comportement sync, agitation de métadonnées, ou fragmentation).
Task 9: Vérifier les stats ARC pour des signaux d’efficacité du cache
cr0x@server:~$ arcstat 1 5
time read miss miss% dmis dm% pmis pm% mmis mm% arcsz c
12:11:10 185K 21.2K 11 9K 4 10K 5 2K 1 58G 64G
12:11:11 192K 30.1K 15 14K 7 12K 6 4K 2 58G 64G
Interprétation : une hausse de miss% pendant une charge qui devrait être cache-friendly peut signifier que les blocs sont trop gros pour le pattern d’accès (ou que l’ensemble de travail est trop large). Le recordsize peut faire partie du diagnostic.
Task 10: Confirmer la taille I/O de la charge (avant de tuner)
cr0x@server:~$ sudo pidstat -d 1 5 -p 12345
Linux 6.8.0 (server) 12/24/2025 _x86_64_ (32 CPU)
12:12:01 UID PID kB_rd/s kB_wr/s kB_ccwr/s Command
12:12:02 1001 12345 5120.0 2048.0 128.0 postgres
Interprétation : ceci montre des débits, pas des tailles, mais c’est votre premier indice sur la dominance lecture/écriture. Associez-le à la connaissance applicative (taille de page BD, tailles bloc VM). Si vous ne pouvez pas décrire vos tailles I/O, vous bricolez à l’aveugle.
Task 11: Forcer la réécriture d’un fichier pour appliquer un nouveau recordsize
C’est la partie opérationnelle utile : changer le recordsize ne réécrit pas les blocs existants. Pour l’appliquer aux fichiers existants, il faut réécrire le contenu du fichier. Une méthode simple est « copier dehors et de retour », idéalement dans le même pool mais vers un nouveau dataset avec les propriétés souhaitées.
cr0x@server:~$ sudo zfs create -o recordsize=16K -o compression=lz4 tank/vm_new
cr0x@server:~$ sudo rsync -aHAX --inplace --progress /tank/vm/ /tank/vm_new/
cr0x@server:~$ sudo zfs set mountpoint=/tank/vm_old tank/vm
cr0x@server:~$ sudo zfs set mountpoint=/tank/vm tank/vm_new
Interprétation : le rsync force l’allocation de nouveaux blocs avec les réglages du nouveau dataset. L’échange de point de montage est la coupure « ennuyeuse mais sûre ». Testez les services avant de supprimer l’ancien dataset.
Task 12: Valider que les données réécrites utilisent bien la nouvelle taille de bloc
cr0x@server:~$ sudo zdb -bbbbb tank/vm_new | head -n 20
Dataset tank/vm_new [ZPL], ID 102, cr_txg 124890, 1.39T, 1039120 objects
Object lvl iblk dblk dsize dnsize lsize %full type
96 1 128K 16K 1.39T 512B 1.39T 100% ZFS plain file
Interprétation : dblk doit correspondre au recordsize prévu pour les données nouvellement écrites. Si ce n’est pas le cas, cherchez : les fichiers sont-ils creux ? traitez-vous des zvols ? la charge écrit-elle en blocs plus grands que prévu ?
Task 13: Vérifier ponctuellement le comportement des « fichiers chauds » d’un dataset avec filefrag
cr0x@server:~$ sudo filefrag -v /tank/vm/disk01.raw | head -n 15
Filesystem type is: 0x2fc12fc1
File size of /tank/vm/disk01.raw is 107374182400 (26214400 blocks of 4096 bytes)
ext: logical_offset: physical_offset: length: expected: flags:
0: 0.. 255: 1200000.. 1200255: 256: last,eof
1: 256.. 511: 1310000.. 1310255: 256: 1200256:
Interprétation : la fragmentation n’est pas purement une question de recordsize, mais un mauvais alignement des tailles augmente l’agitation d’allocation et peut aggraver la fragmentation. Si vous observez une fragmentation extrême plus une charge d’écritures aléatoires, des enregistrements plus petits et une meilleure réserve d’espace peuvent aider.
Task 14: Vérifier les signaux du comportement des écritures synchrones (pour ne pas incriminer à tort le recordsize pour des problèmes de SLOG)
cr0x@server:~$ zfs get -o name,property,value,source sync tank/db
NAME PROPERTY VALUE SOURCE
tank/db sync standard inherited
Interprétation : si vous avez des écritures synchrones à latence élevée (les bases de données en ont souvent), le réglage du recordsize ne résoudra pas un SLOG lent ou l’absence d’un périphérique de journal séparé. Diagnosez le chemin sync séparément ; le recordsize change surtout le coût de réécriture et la granularité I/O.
Mode d’emploi pour un diagnostic rapide
Voici le plan « que vérifier en premier, second, troisième » quand la performance lâche et que vous soupçonnez un réglage ZFS en cause.
1) Confirmez le pattern I/O de la charge avant de toucher ZFS
- Est-ce aléatoire ou séquentiel ?
- Taille I/O typique : 4K, 8K, 16K, 128K, 1M ?
- Lecture dominante ou écriture dominante ? Sync fréquents ?
Faites des observations de base : métriques applicatives, iostat -x, zpool iostat. Si le pattern est de petites écritures aléatoires et que votre dataset est configuré pour d’énormes blocs séquentiels, vous tenez probablement l’arme fumante.
2) Vérifiez les propriétés du dataset et l’héritage
zfs get recordsize,compression,atime,primarycache,logbias,sync- Confirmez que vous êtes sur un dataset (fichiers) vs un zvol (périphérique bloc)
Si le dataset hérite d’un parent « fourre-tout », vérifiez que le parent n’a pas été ajusté pour une autre charge il y a six mois pendant une panique.
3) Vérifiez ce qui est réellement sur disque (pas ce que vous espérez)
zdb -bbbbb pool/datasetpour les tailles de blocs- Ratio de compression et usage logique vs physique
Cette étape évite un piège courant : vous avez changé le recordsize la semaine dernière mais vous n’avez jamais réécrit les données chaudes, donc rien n’a changé en pratique.
4) Cherchez l’amplification et les goulots qui se déguisent en problèmes de recordsize
- Le pool écrit beaucoup plus que l’application (amplification)
- Latence d’écriture sync élevée (SLOG ou latence des vdevs)
- Miss ARC causés par des blocs surdimensionnés pour des lectures aléatoires
- Saturation CPU due au checksum/compression sur des tailles de bloc minuscules
5) Faites un changement réversible, puis appliquez-le correctement
Changer recordsize est réversible, mais « ressentir » l’avantage nécessite la réécriture des données. Planifiez une migration (nouveau dataset, rsync, cutover) plutôt que de bidouiller une propriété en espérant le meilleur.
Erreurs courantes : symptômes et corrections
Erreur 1 : Utiliser un recordsize énorme pour des images VM
Symptômes : VMs qui se bloquent pendant des mises à jour, rafales d’écriture périodiques, écritures de stockage bien supérieures aux écritures invitées, pics de latence sous charge d’écritures concurrentes.
Correction : placez les images VM sur un dataset réglé pour l’I/O aléatoire : recordsize=16K ou 32K. Migrez/réécrivez les images pour recréer les blocs à la nouvelle taille.
Erreur 2 : Utiliser un recordsize trop petit pour les sauvegardes et médias
Symptômes : les jobs de sauvegarde ne tiennent pas leurs fenêtres, CPU élevé sur les nœuds de stockage, beaucoup d’IOPS mais peu de MB/s, la prélecture semble inefficace.
Correction : créez un dataset backup/media avec recordsize=1M (ou 512K), compression appropriée (souvent zstd ou lz4), et réécrivez/migrez les flux entrants.
Erreur 3 : Changer le recordsize et attendre une amélioration immédiate
Symptômes : « On a mis recordsize=16K et rien n’a changé. » Les benchmarks et le comportement applicatif restent identiques.
Correction : réécrivez les données chaudes. Pour les gros fichiers existants, vous devez copier/rsync vers un nouveau dataset, ou effectuer une réécriture contrôlée qui force de nouvelles allocations de blocs.
Erreur 4 : Confondre recordsize avec volblocksize
Symptômes : vous réglez recordsize, mais la charge est sur un zvol (iSCSI/LUN). Aucun impact mesurable ; confusion en revue d’incident.
Correction : pour les zvols, planifiez volblocksize à la création. Si c’est faux, la correction propre est souvent « nouveau zvol avec le bon volblocksize, migration des données ». Ne contrariez pas la physique avec des propriétés souhaitées.
Erreur 5 : Ignorer le comportement des écritures synchrones et blâmer le recordsize
Symptômes : les commits de la base sont lents, mais les lectures vont bien ; la latence corrèle avec les fsync/commit ; changer le recordsize n’aide pas.
Correction : évaluez le chemin sync : qualité du périphérique de log séparé (SLOG), latence des vdevs principaux, attentes de sync=standard, et réglages applicatifs. Le recordsize aide l’amplification de réécriture, pas « mon périphérique de sync est lent ».
Erreur 6 : Régler le recordsize sans considérer la compression
Symptômes : utilisation CPU imprévisible, ou taux d’écriture physique surprenant après des changements.
Correction : si vous utilisez la compression, comprenez que les tailles de blocs physiques peuvent diminuer ; mesurez compressratio, la marge CPU, et la bande passante du pool. Un recordsize plus grand peut améliorer la compression sur certaines données, mais il peut aussi augmenter le coût de réécriture pour des mises à jour aléatoires.
Listes de contrôle / plan pas-à-pas
Checklist A : Choisir le recordsize pour un nouveau dataset
- Nommer la charge. « Images VM », « Postgres », « flux de sauvegarde », « répertoires personnels mixtes ». Pas « fichiers ».
- Identifier la taille I/O typique. 4K/8K/16K/128K/1M. Utiliser la doc applicative ou des mesures.
- Décider si les réécritures sont fréquentes. Bases et VMs écrasent ; sauvegardes appendent.
- Choisir un
recordsizeinitial. Points de départ courants : 16K (BD/VM), 128K (général), 1M (sauvegarde/média). - Définir la compression intentionnellement.
lz4est généralement un bon défaut ; des compressions plus lourdes doivent être justifiées. - Documenter dans les propriétés du dataset. Ne comptez pas sur la mémoire collective. Au minimum :
recordsize, compression, atime, attentes de sync.
Checklist B : Changer le recordsize sur une charge existante (approche opérationnelle sûre)
- Confirmer que vous touchez la bonne couche. Dataset vs zvol.
- Mesurer le comportement actuel. Baseline : latence, débit, bande passante pool, miss% ARC. Capturer avant/après.
- Définir le
recordsizesur un nouveau dataset. Évitez de changer le dataset live si les données doivent être réécrites. - Migrer avec réécriture. Utiliser rsync/copy de façon à allouer de nouveaux blocs (copie vers nouveau dataset).
- Coupure par échange de point de montage ou changement de config service. Facilitez le rollback.
- Valider les tailles de blocs et les performances. Utiliser
zdbet des tests de charge. - Conserver l’ancien dataset temporairement. Assurance rollback ; supprimer plus tard quand la confiance est établie.
Checklist C : « Est-ce vraiment le recordsize le problème ? »
- Si les pics de latence corrèlent avec les écritures sync, investiguez la qualité du SLOG/périphérique d’abord.
- Si le débit est faible mais le CPU élevé, vérifiez un
recordsizetrop petit ou une compression trop lourde. - Si la bande passante du pool est énorme comparée aux écritures applicatives, suspectez l’amplification (mismatch de
recordsize, fragmentation, agitation métadonnées). - Si la performance se dégrade à mesure que le pool se remplit, vérifiez la fragmentation et l’espace libre ; le
recordsizepeut aggraver les symptômes mais n’en est pas toujours la cause racine.
FAQ
1) Changer le recordsize réécrit-il les données existantes ?
Non. Il n’affecte que les nouvelles écritures. Les blocs existants conservent leur ancienne taille jusqu’à ce que les régions de fichiers soient réécrites. Si vous avez besoin d’un bénéfice immédiat, migrez/réécrivez les données chaudes.
2) Quel est le meilleur recordsize pour PostgreSQL ?
Couramment 16K sur un dataset stockant des fichiers PostgreSQL, car cela se comporte bien avec l’I/O typique de BD tout en gardant l’amplification de réécriture raisonnable. Mais mesurez : la charge, la taille ARC/RAM, et si vous êtes limité par les syncs importent plus que les slogans.
3) Quel est le meilleur recordsize pour les images VM stockées comme fichiers ?
Typiquement 16K ou 32K. L’I/O VM est souvent petite et aléatoire ; un recordsize énorme provoque souvent de l’amplification. Si votre environnement est extrêmement sensible aux IOPS, 8K peut convenir, mais cela augmente l’overhead et peut réduire le débit séquentiel pour des opérations invitées comme les scans de disque.
4) Dois-je mettre recordsize à 1M pour tout parce que « c’est plus rapide » ?
Non. C’est plus rapide pour le gros streaming séquentiel et souvent pire pour les petits écrasements aléatoires. Un recordsize de 1M sur des datasets VM ou BD est une cause fréquente de tickets « pourquoi les écritures sont si saccadées ? ».
5) Comment la compression change-t-elle les décisions sur le recordsize ?
La compression est par bloc. Des blocs plus grands peuvent mieux compresser, ce qui peut réduire l’I/O physique pour des données compressibles. Mais le maximum d’un bloc contrôle toujours l’étendue de réécriture : un petit changement dans un grand record peut quand même déclencher la réécriture d’un gros bloc, même si la donnée compressée est petite.
6) Un recordsize plus petit est-il toujours meilleur pour la latence ?
Non. Des blocs plus petits peuvent réduire l’amplification de lecture pour des lectures aléatoires et réduire la taille de réécriture pour de petites modifications, mais ils augmentent aussi l’overhead métadonnées et le travail CPU. Pour les workloads séquentiels, un recordsize trop petit peut réduire le débit et augmenter la consommation CPU.
7) Comment savoir quelles tailles de blocs sont réellement utilisées ?
Utilisez zdb -bbbbb pool/dataset pour inspecter les tailles de blocs des objets du dataset, et validez par des contrôles ponctuels après une migration. Ne supposez pas qu’un changement de recordsize modifie les fichiers existants.
8) Quelle est la différence entre recordsize et ashift ?
ashift est un réglage d’alignement de secteur au niveau pool/vdev (typiquement 12 pour 4K, 13 pour 8K). Il affecte la granularité minimale d’allocation et l’alignement. recordsize est une taille maximale de bloc au niveau dataset. Ils interagissent — un mauvais alignement peut causer de l’amplification — mais ils résolvent des problèmes différents.
9) Le recordsize peut-il causer de la fragmentation ?
recordsize ne « cause » pas la fragmentation en soi, mais un mismatch (grands enregistrements avec réécritures aléatoires) augmente l’agitation d’allocation et peut empirer la fragmentation avec le temps. Garder un espace libre sain et adapter le recordsize aux patterns I/O aide.
10) Dois-je régler le recordsize pour des partages NFS ?
Réglez-le pour le comportement applicatif derrière NFS. NFS est un transport ; la vraie question est de savoir si les clients font de petits I/O aléatoires dans de gros fichiers (projets CAO, images VM, fichiers BD) ou majoritairement des workloads séquentiels/append.
Conclusion
Si vous voulez un levier de réglage ZFS unique qui change fiablement les résultats, recordsize est celui-là — parce qu’il détermine la granularité des blocs de données, et la granularité détermine tout : le coût de réécriture, l’efficacité du cache, le taux de requêtes I/O, et la manière dont vous gérez l’accès aléatoire vs séquentiel.
La subtilité opérationnelle est d’arrêter de considérer le recordsize comme un simple ajustement et de commencer à le traiter comme un choix de conception. Créez des datasets par workload, choisissez le recordsize en fonction des patterns I/O, et souvenez-vous de ce que tout le monde oublie : le modifier n’a d’effet que si vous réécrivez les données. Faites cela, et vous aurez moins de mystères de performance et plus de graphes ennuyeux — exactement le type d’ennui qui maintient la production en vie.