Chaque ingénieur stockage rencontre un jour le cas des « écritures lentes » : les graphiques semblent faux, les disques paraissent inertes et les équipes applicatives jurent qu’elles n’ont rien changé. Puis vous remarquez la charge : des millions de petits fichiers, beaucoup de lectures, sur un dataset avec atime=on. Vous basculez une propriété et soudain le chemin d’écriture se remet en ordre et commence à se comporter comme un système sérieux.
Ceci est cette histoire — sans la fin de conte de fées où un seul bouton règle tout pour toujours. Les mises à jour du temps d’accès de ZFS (atime) peuvent être une vraie taxe de performance au mauvais endroit, et elles sont subtiles parce qu’elles ne ressemblent pas à des « écritures » pour la plupart des gens. Elles ressemblent à des « lectures ». Mais le système de fichiers effectue silencieusement du travail métadata supplémentaire pour vous, et ce travail doit être matérialisé quelque part : dans le journal d’intention, dans les TXGs, dans l’ordonnanceur I/O, et souvent dans la latence que votre application qualifie de « réseau ».
Ce qu’est vraiment atime (et pourquoi il existe)
atime est le « temps d’accès » d’un fichier : un horodatage mis à jour lorsqu’un fichier est lu. Pas modifié — lu. Historiquement, cela semblait utile : systèmes de mail cherchant le « nouveau courrier », outils de sauvegarde décidant ce qui a été touché, scripts de nettoyage supprimant du contenu « inutilisé », outils de sécurité construisant des chronologies, et administrateurs répondant à la question éternelle « Est-ce que quelqu’un utilise ça ? »
Sur les systèmes de fichiers de type Unix, atime fait partie du trio classique :
- mtime : quand le contenu du fichier a changé
- ctime : quand les métadonnées ont changé (permissions, propriétaire, nombre de liens, etc.)
- atime : quand le fichier a été accédé pour la dernière fois (lu)
Dans ZFS, la propriété atime est une propriété de dataset, vous pouvez donc l’activer/désactiver par dataset sans gymnastique de remontage. C’est une bonne conception : différents répertoires peuvent avoir des vérités différentes. Votre spool mail peut conserver atime. Votre cache d’images de conteneurs n’en a probablement pas besoin.
Mais voici la subtilité : atime n’est pas une « métadonnée gratuite ». La mettre à jour nécessite d’écrire la métadonnée sur disque à terme. Avec la sémantique copy-on-write, les mises à jour de métadonnées sont de réelles allocations, de vrais blocs sales, et du vrai travail pour les TXGs. ZFS excelle à être correct ; il n’est pas tenu d’être bon marché à ce sujet.
Blague n°1 (approuvée par le stockage) : atime, c’est comme une réceptionniste qui note le nom de chaque visiteur — utile jusqu’à ce qu’elle commence à le faire pendant un exercice d’évacuation.
Comment atime transforme des lectures en écritures (et pourquoi cela peut bloquer les écritures réelles)
Quand vous lisez un fichier sur un dataset avec atime=on, ZFS met à jour les métadonnées de type inode (ZFS utilise des objets, dnodes et blocs de métadonnées plutôt que des inodes classiques, mais le principe reste). Cette mise à jour marque les métadonnées comme sales en RAM. Les données sales doivent être engagées lors du prochain TXG sync. Si vous lisez des millions de fichiers, vous générez un flux continu d’écritures de métadonnées.
« Et alors ? Ce sont des métadonnées ; c’est petit. » C’est l’hypothèse courante, et elle est fausse exactement pour les charges qui mettent les gens en colère :
- Scans de petits fichiers (antivirus, énumérations de sauvegarde, caches CI, checkouts de monorepo, couches de registres de conteneurs, caches d’images)
- Applications intensives en parcours de répertoires (gestionnaires de paquets, générateurs de sites statiques, systèmes de build)
- Partages NFS/SMB où les clients effectuent beaucoup de lectures de métadonnées
- Hôtes VM ou conteneurs avec beaucoup de petites lectures sur de nombreux datasets
La charge d’écriture n’est pas seulement « quelques octets ». C’est une chaîne de coûts :
1) Les métadonnées sales rivalisent avec les écritures réelles
Les TXGs disposent d’un temps et de ressources limités. Si vous avez un churn metadata constant en arrière-plan, vos écritures applicatives réelles peuvent se retrouver bloquées derrière. La latence augmente ; le débit devient en rafales.
2) La copie-sur-écriture amplifie le travail sur les métadonnées
ZFS n’écrase pas les blocs en place ; il écrit de nouveaux blocs et met à jour des pointeurs. Mettre à jour atime peut se répercuter jusqu’aux blocs indirects et aux modifications de l’arbre des métadonnées. Le volume d’I/O par « petite » mise à jour de métadonnée peut dépasser votre intuition, surtout avec la fragmentation et les petits blocs.
3) Le comportement de sync peut aggraver la situation
Si votre charge est lourde en sync (bases de données, NFS avec certaines options d’export, applications appelant fréquemment fsync()), le chemin ZIL/SLOG est sensible aux écritures métadonnées supplémentaires. Les mises à jour d’atime ne se comportent pas toujours comme des écritures synchrones, mais elles peuvent contribuer à l’ensemble des blocs sales et presser la pipeline au pire moment.
4) Pression ARC et effets d’éviction
Les métadonnées résident aussi dans l’ARC. Un scan riche en métadonnées avec atime activé peut gonfler le churn des métadonnées, expulsant des données utiles en cache et provoquant plus de lectures disque. C’est ainsi qu’on obtient l’incident particulier où « les lectures ont rendu les écritures plus lentes, puis les lectures elles-mêmes sont devenues plus lentes ».
5) Ça se cache derrière l’étiquette « workload de lecture »
Les équipes regardent l’application et disent « c’est en lecture », puis s’étonnent de voir des écritures sur le pool. atime est l’une des raisons classiques. Une autre est snapdir=visible et des utilisateurs qui explorent .zfs/snapshot, mais cela fera l’objet d’un autre article.
Faits et histoire : la petite estampille au long sillage
Un peu de contexte aide à choisir le bon comportement au lieu de coller atime=off partout par mimétisme.
- atime existe depuis bien avant ZFS. Il vient des premières sémantiques des systèmes de fichiers Unix où les horodatages étaient peu coûteux comparés au temps humain passé à déboguer.
- Le « relatime » de Linux est devenu populaire parce que l’atime strict était trop coûteux. L’industrie a en quelque sorte admis que « mettre à jour atime à chaque lecture, c’est trop » et a introduit un compromis.
- NFS et les spools de mail dépendaient historiquement d’atime. Certains outils anciens utilisaient atime pour décider si une boîte mail était « nouvellement accédée » ou sûre à modifier.
- Le flash a changé le profil de douleur. Les SSD gèrent bien les lectures aléatoires, mais les écritures de métadonnées coûtent toujours en latence et peuvent déclencher une amplification d’écriture et du garbage collection à des moments inopportuns.
- ZFS a été conçu pour la correction et l’observabilité. Tout l’esprit « on peut voir ce que ZFS fait » explique pourquoi des propriétés comme atime sont visibles et contrôlables par dataset.
- OpenZFS a fait de la croissance des fonctionnalités une préoccupation majeure. C’est pourquoi vous verrez des valeurs par défaut différentes selon les plateformes et les époques : tout le monde n’a pas adopté la même philosophie de tuning.
- Certaines solutions de sauvegarde utilisaient atime comme télémétrie de fortune. « Si c’était accédé, ça doit être important » n’est pas une bonne politique, mais elle a existé.
- atime interagit avec le comportement humain. Lancer
findsur un arbre sur un dataset avec atime activé peut transformer un audit inoffensif en un orage d’écritures métadonnées. Félicitations : votre scan de conformité en lecture seule vient de devenir une charge d’écriture. - atime est l’un des rares réglages de performance qui est aussi une décision de politique. Le désactiver n’est pas seulement plus rapide ; cela change la vérité enregistrée par votre système sur l’utilisation.
Quand désactiver atime est le bon choix (et quand ce n’est pas le cas)
Désactiver atime est souvent le bon choix par défaut pour les datasets sensibles à la performance moderne, mais « souvent » n’est pas « toujours ». La bonne décision dépend de si vous avez besoin de la sémantique du temps d’accès pour un comportement réel ou pour la conformité, pas pour des impressions.
Désactiver atime pour ces cas courants
- Caches de build (espaces CI, caches d’artefacts, caches de paquets)
- Stockage de conteneurs (couches d’images, stockages d’overlay, caches de registre)
- Actifs web statiques (servis par des processus qui ont déjà des logs)
- Images VM (l’atime à l’intérieur de la VM est ce qui compte, pas sur le dataset de l’hôte)
- Répertoires personnels en environnements d’entreprise où atime n’est pas utilisé pour quotas/nettoyage
Réfléchissez à deux fois avant de désactiver atime ici
- Spools mail ou outils hérités qui vérifient atime (moins courant aujourd’hui, mais pas éteint)
- Flux judiciaires ou de conformité où « a été accédé » est une preuve significative
- Automatisation du cycle de vie des données qui utilise atime pour expirer des fichiers « inutilisés » (conception douteuse, mais réelle)
Qu’en est-il de « relatime » sur ZFS ?
Sur Linux, les options de montage comme relatime sont bien connues pour ext4/xfs. Sur ZFS, le comportement est principalement piloté par les propriétés ZFS et la couche ZPL ; selon la plateforme et l’implémentation, vous pouvez voir des options de montage reflétées, mais considérez les propriétés ZFS comme la source de vérité. En pratique : si vous voulez un comportement prévisible, définissez atime au niveau du dataset et vérifiez-le avec zfs get.
Blague n°2 : « On va juste activer atime pour l’audit », a dit l’équipe, peu avant d’auditer l’incident de performance de stockage qu’elle venait de créer.
Méthode de diagnostic rapide : quoi vérifier en premier, deuxième, troisième
Voici la méthode que j’utilise quand quelqu’un signale « ZFS écritures lentes » et que l’horloge tourne. L’objectif est d’identifier si atime est un contributeur probable en 10–15 minutes, pas de produire une thèse.
Premier point : confirmer le symptôme et classifier le blocage
- Est-ce la latence ou le débit ? « Écritures lentes » peut signifier une latence élevée de commit (sync) ou un faible débit séquentiel (async). Utilisez les stats du pool et des vdevs pour décider.
- Le pool est-il chargé ? Si le pool n’est pas occupé mais que l’app est lente, regardez les paramètres de sync, ZIL/SLOG et la contention CPU.
- La charge est-elle réellement lecture-intensive ? Si oui, atime devient suspect.
Deuxième point : chercher des signatures de churn métadonnées
- IOPS élevés avec faible bande passante sur le pool : charge classique de petits blocs/métadonnées.
- Beaucoup d’« écritures » pendant un scan de lecture : pourraient être des mises à jour atime.
- Temps de sync TXG qui augmentent ou commits qui deviennent en rafales : la saleté des métadonnées peut vous pousser là.
Troisième point : vérifier les propriétés du dataset et valider l’hypothèse
- Est-ce que
atime=on? Vérifiez sur le dataset qui héberge la charge, pas seulement à la racine du pool. - Pouvez-vous reproduire avec un scan de lecture contrôlé ? Un rapide
findoutaren lecture peut montrer si des lectures déclenchent des écritures. - Pouvez-vous basculer en toute sécurité atime à off pour un test ? Si la politique le permet, changez-le et mesurez de nouveau.
Tâches pratiques : commandes, sorties et ce qu’elles signifient
Voici des tâches pratiques que vous pouvez exécuter en production avec un risque minimal. L’objectif n’est pas seulement « exécuter la commande », mais « interpréter le signal ». Remplacez les noms de pool/dataset selon le cas.
Task 1: Identify the dataset behind a path
cr0x@server:~$ df -T /srv/buildcache
Filesystem Type 1K-blocks Used Available Use% Mounted on
tank/buildcache zfs 500000000 12000000 488000000 3% /srv/buildcache
Interprétation : Le dataset est tank/buildcache. C’est là que vous vérifiez atime — pas tank, pas « ce que le pool avait comme valeur par défaut il y a des années ».
Task 2: Check atime and other relevant dataset properties
cr0x@server:~$ zfs get -o name,property,value,source atime,recordsize,primarycache,logbias,sync tank/buildcache
NAME PROPERTY VALUE SOURCE
tank/buildcache atime on local
tank/buildcache recordsize 128K inherited
tank/buildcache primarycache all default
tank/buildcache logbias latency default
tank/buildcache sync standard default
Interprétation : atime=on est explicitement défini (local). C’est un candidat pour « des lectures provoquant des écritures métadonnées ». Notez aussi recordsize et le cache — utile plus tard, mais ne touchez pas à cinq réglages en même temps.
Task 3: Observe pool I/O at a glance
cr0x@server:~$ zpool iostat -v tank 1
capacity operations bandwidth
pool alloc free read write read write
---------- ----- ----- ----- ----- ----- -----
tank 120G 1.70T 15000 9000 180M 40M
mirror 60G 850G 8000 4500 90M 20M
nvme0n1 - - 4000 2200 45M 10M
nvme1n1 - - 4000 2300 45M 10M
mirror 60G 850G 7000 4500 90M 20M
nvme2n1 - - 3500 2200 45M 10M
nvme3n1 - - 3500 2300 45M 10M
Interprétation : IOPS de lecture élevées, écritures non négligeables, bande passante modérée. Si l’application croit être « en lecture seule », ces 9k write IOPS sont un indice. atime est une raison fréquente ; pas la seule.
Task 4: Verify mount options and ZFS view (sanity check)
cr0x@server:~$ mount | grep '/srv/buildcache'
tank/buildcache on /srv/buildcache type zfs (rw,xattr,noacl)
Interprétation : N’attendez pas de voir atime ici sur ZFS comme sur ext4. Utilisez zfs get comme vérité.
Task 5: Prove reads are generating writes (controlled scan)
cr0x@server:~$ sudo sh -c 'zpool iostat -v tank 1 & sleep 2; find /srv/buildcache -type f -maxdepth 3 -print0 | xargs -0 -n 200 head -c 1 >/dev/null; sleep 2; pkill -f "zpool iostat -v tank 1"'
# (iostat output scrolls; look for a write spike during the read scan)
Interprétation : Si un scan en lecture seule avec head produit des écritures soutenues, atime est probablement en cause. Ce n’est pas une expérience parfaite, mais c’est rapide.
Task 6: Check file atime before and after a read
cr0x@server:~$ FILE=/srv/buildcache/example.bin
cr0x@server:~$ stat -c 'atime=%x mtime=%y ctime=%z %n' "$FILE"
atime=2025-12-24 09:41:02.000000000 +0000 mtime=2025-12-20 11:10:09.000000000 +0000 ctime=2025-12-20 11:10:09.000000000 +0000 /srv/buildcache/example.bin
cr0x@server:~$ dd if="$FILE" of=/dev/null bs=128K count=1 status=none
cr0x@server:~$ stat -c 'atime=%x mtime=%y ctime=%z %n' "$FILE"
atime=2025-12-24 10:02:18.000000000 +0000 mtime=2025-12-20 11:10:09.000000000 +0000 ctime=2025-12-20 11:10:09.000000000 +0000 /srv/buildcache/example.bin
Interprétation : L’atime a changé après la lecture. Cela implique des mises à jour de métadonnées. La vraie question est de savoir si ce churn de métadonnées est suffisamment important pour vous nuire. Dans les charges de petits fichiers, c’est souvent le cas.
Task 7: Disable atime on a dataset (safe, immediate, reversible)
cr0x@server:~$ sudo zfs set atime=off tank/buildcache
cr0x@server:~$ zfs get -o name,property,value,source atime tank/buildcache
NAME PROPERTY VALUE SOURCE
tank/buildcache atime off local
Interprétation : Cela change le comportement à partir de maintenant. Cela ne « réécrit » pas l’historique des métadonnées existantes ; cela arrête les mises à jour futures. Si vous avez besoin d’une fenêtre de test, vous pouvez le remettre.
Task 8: Measure the difference (before/after) with the same read scan
cr0x@server:~$ sudo sh -c 'zpool iostat -v tank 1 & sleep 2; find /srv/buildcache -type f -maxdepth 3 -print0 | xargs -0 -n 200 head -c 1 >/dev/null; sleep 2; pkill -f "zpool iostat -v tank 1"'
# Compare write IOPS now versus earlier
Interprétation : Si les écritures chutent fortement pendant les lectures, vous avez trouvé un contributeur réel à l’histoire des écritures lentes. Sinon, atime n’était pas votre coupable — ou pas le seul.
Task 9: Confirm whether sync writes are the actual bottleneck
cr0x@server:~$ zfs get -o name,property,value,source sync,logbias tank/buildcache
NAME PROPERTY VALUE SOURCE
tank/buildcache sync standard default
tank/buildcache logbias latency default
Interprétation : Si l’application est lente uniquement sur des opérations fsync-heavy, les changements d’atime pourraient ne pas beaucoup bouger la métrique. Mais atime peut quand même ajouter du bruit en arrière-plan et augmenter la variance de latence.
Task 10: Watch ZFS latency distribution at the device layer
cr0x@server:~$ iostat -x 1
Linux 6.8.0 (server) 12/24/2025 _x86_64_ (32 CPU)
Device r/s w/s rkB/s wkB/s aqu-sz await r_await w_await svctm %util
nvme0n1 3800 2100 48000 11000 2.1 0.5 0.4 0.7 0.1 55.0
nvme1n1 3900 2200 49000 12000 2.0 0.5 0.4 0.7 0.1 56.0
Interprétation : Si w_await monte en flèche pendant les scans en lecture, vous observez l’interaction « des lectures provoquant des écritures » dans la file d’attente du périphérique. Avec des HDD, cet effet est généralement plus spectaculaire.
Task 11: Look for metadata-heavy file counts and patterns
cr0x@server:~$ sudo zfs list -o name,used,refer,avail,mountpoint tank/buildcache
NAME USED REFER AVAIL MOUNTPOINT
tank/buildcache 12G 12G 1.7T /srv/buildcache
cr0x@server:~$ find /srv/buildcache -type f | wc -l
2457812
Interprétation : Des millions de fichiers, c’est là où les comportements métadonnées cessent d’être théoriques. Même si chaque mise à jour d’atime est « petite », le produit de « petit » fois millions, c’est ce qui vous réveille à 2 h du matin.
Task 12: Verify inheritance (avoid fixing the wrong dataset)
cr0x@server:~$ zfs get -r -o name,property,value,source atime tank | head -n 12
NAME PROPERTY VALUE SOURCE
tank atime on default
tank/buildcache atime off local
tank/home atime on inherited
tank/home/alice atime on inherited
tank/home/bob atime on inherited
Interprétation : atime est par dataset. Votre « correctif » peut ne s’appliquer qu’à un sous-arbre. C’est bien quand vous voulez un changement ciblé ; c’est mauvais quand vous pensiez avoir corrigé tout le pool.
Task 13: Check whether a snapshot / replication workflow relies on atime semantics
cr0x@server:~$ zfs list -t snapshot -o name,creation -s creation | tail -n 5
tank/buildcache@auto-2025-12-24-0900 Tue Dec 24 09:00 2025
tank/buildcache@auto-2025-12-24-1000 Tue Dec 24 10:00 2025
tank/buildcache@auto-2025-12-24-1100 Tue Dec 24 11:00 2025
tank/buildcache@auto-2025-12-24-1200 Tue Dec 24 12:00 2025
tank/buildcache@auto-2025-12-24-1300 Tue Dec 24 13:00 2025
Interprétation : Les snapshots suivent les blocs modifiés, pas « l’accès ». Désactiver atime réduit généralement le taux de changements et peut améliorer l’efficacité de la réplication. Si quelqu’un vous a dit « on a besoin d’atime pour que les snapshots voient les changements », c’est une incompréhension.
Task 14: Validate that applications aren’t using atime for eviction policies
cr0x@server:~$ sudo grep -R "atime" -n /etc 2>/dev/null | head
/etc/updatedb.conf:23:PRUNEFS="... zfs ..."
/etc/cron.daily/tmpwatch:45:# uses mtime, not atime
Interprétation : C’est grossier, mais cela attrape la classe de surprises « on a un job de nettoyage qui utilise atime ». La vraie vérification, ce sont les configs applicatives et les scripts ; atime est un contrat de politique.
Trois récits du monde de l’entreprise depuis les tranchées atime
1) Incident causé par une mauvaise hypothèse : « C’est en lecture seule, donc ça ne peut pas être le stockage »
Le ticket est arrivé classiquement : pipelines CI qui expirent, étapes de build « bloquées sur les uploads », et beaucoup d’accusations portant sur le réseau. L’équipe stockage a été appelée parce que les graphiques du pool de l’hôte VM montraient des IOPS en écriture qui montaient pendant les heures ouvrées, alors que le job de pipeline lisait surtout des dépendances et checkait le code source.
Quelqu’un avait récemment « standardisé » la création de datasets avec un template qui laissait atime=on partout. Ce n’était pas une modification malveillante — juste une valeur par défaut qui semblait inoffensive. Et sur des répertoires personnels normaux, c’était assez inoffensif. Sur le dataset du cache de build avec quelques millions de petits fichiers, c’est devenu un métronome de churn métadonnées.
La mauvaise hypothèse était que la charge était « en lecture seule ». En réalité, chaque accès fichier produisait une mise à jour d’atime, ce qui produisait des métadonnées sales, ce qui augmentait le travail de TXG sync. Le pool était rapide, mais la mise en file d’attente était réelle et périodique ; les écritures étaient en compétition avec les uploads d’artefacts et les écritures de logs. Les pics de latence coïncidaient avec des étapes de scan innocentes comme la résolution de dépendances.
Le correctif était ennuyeux : désactiver atime sur le dataset cache, puis relancer les pipelines. La partie dramatique a été sociale, pas technique : il a fallu dix minutes pour réparer et deux semaines pour convaincre tout le monde que « lecture » peut quand même entraîner des écritures. Une fois acceptée, une poignée d’autres « écritures mystérieuses » sont devenues faciles à expliquer.
Après ça, l’équipe a écrit une règle : les datasets destinés aux caches ou aux arbres de build sont créés avec atime=off. Pas parce qu’atime est mauvais, mais parce que les caches n’ont pas besoin d’un journal intime.
2) Une optimisation qui s’est retournée contre eux : « Désactiver atime partout » rencontre l’équipe conformité
Une autre entreprise, une autre saveur de douleur. Ils avaient un partage de fichiers utilisé par plusieurs départements, dont un groupe faisant de l’investigation et de la réponse aux incidents. La performance du stockage était médiocre, et quelqu’un a trouvé le levier magique : atime=off. Ils l’ont appliqué largement, y compris sur des datasets qui servaient (discrètement) des workflows d’enquête.
Le gain de performance était réel. L’erreur a été de supposer qu’atime était « réservé aux vieux nerds Unix ». Quelques mois plus tard, une enquête interne a tenté de répondre à « qui a accédé à ces fichiers et quand ». Ils n’utilisaient pas atime comme seule preuve, mais c’était une part de la triangulation temporelle. Soudain, les timestamps d’accès ont cessé d’être mis à jour. Les enquêteurs ont vu des atimes obsolètes et ont supposé que les fichiers n’étaient pas lus, ce qui les a fait poursuivre de mauvaises pistes pendant un moment.
Techniquement, le système de fichiers faisait exactement ce qu’on lui avait demandé. Organisationnellement, ils avaient violé un contrat implicite : la sémantique d’accès faisait partie d’un processus. L’optimisation a échoué non pas parce qu’elle était mauvaise, mais parce qu’elle a été appliquée sans discernement.
La récupération n’a pas été de réactiver atime partout. Ils ont créé un dataset dédié pour le partage d’investigation avec atime=on et une raison documentée. Pour les partages génériques et les caches, atime est resté off. La leçon : le tuning de performance est aussi de la conception système. Si vous changez ce que la vérité enregistre, vous devez avertir ceux qui dépendent de cette vérité.
3) Une pratique ennuyeuse mais correcte qui a sauvé la mise : politique par dataset et fenêtre de changement
La meilleure histoire d’atime que j’ai est celle qui n’est pas devenue une histoire. Une équipe plateforme faisait tourner OpenZFS pour des charges mixtes : bases de données, répertoires personnels, caches CI et passerelles d’objets. Ils avaient déjà été brûlés par le tuning « one-size-fits-all », alors ils maintenaient une petite matrice de profils de dataset : db, home, cache, logs. Chaque profil avait quelques propriétés définies intentionnellement, incluant atime.
Un vendredi, un outil de sécurité a été déployé et a scanné des arbres sources et des caches de dépendances sur toute la flotte. Sur les systèmes où les caches avaient atime désactivé, le scan a généré de la charge en lecture mais n’a pas créé d’orage d’écritures métadonnées. Sur les systèmes où les home conservaient atime activé, le coût était acceptable parce que le compte de fichiers et les patterns d’accès étaient différents. Le déploiement a quand même provoqué de la charge, mais elle est restée dans le budget.
Ce qui les a sauvés n’était pas un tweak noyau astucieux. C’était la gouvernance : propriétés per-dataset définies à la création, l’habitude de vérifier zfs get avant de chasser des fantômes, et une politique de fenêtres de changement qui leur permettait d’ajuster rapidement les propriétés sans se disputer sur « qu’est-ce qui a changé ». Ils pouvaient pointer un profil, montrer l’intention, et continuer d’avancer.
La morale est terriblement peu sexy : si vous traitez les propriétés ZFS comme faisant partie du contrat applicatif — documentées, revues et cohérentes — vous évitez beaucoup d’héroïsme de minuit. Et vous pouvez passer vos week-ends à autre chose qu’à lire des sorties iostat comme on lit des feuilles de thé.
Erreurs courantes, symptômes et corrections
Mistake 1: Flipping atime at the pool root and assuming it applies everywhere
Symptôme : Vous exécutez zfs set atime=off tank et rien ne change pour la charge problématique.
Pourquoi : La charge vit sur un dataset enfant avec atime=on défini localement, ou c’est un dataset complètement différent.
Correction : Identifiez le dataset du point de montage et vérifiez l’héritage.
cr0x@server:~$ df -T /srv/buildcache
cr0x@server:~$ zfs get -o name,property,value,source atime tank/buildcache
cr0x@server:~$ zfs get -r -o name,property,value,source atime tank | grep buildcache
Mistake 2: Disabling atime where a process depends on it
Symptôme : Les jobs de nettoyage/archivage cessent d’expirer correctement des fichiers, ou les workflows d’investigation se plaignent d’horodatages d’accès obsolètes.
Pourquoi : atime était utilisé comme entrée de politique.
Correction : Rétablissez atime sur les datasets qui en ont besoin, ou refactorez le job pour utiliser des logs applicatifs, mtime, ou des métadonnées explicites plutôt que le temps d’accès du système de fichiers.
cr0x@server:~$ sudo zfs set atime=on tank/investigations
cr0x@server:~$ zfs get -o name,property,value,source atime tank/investigations
Mistake 3: Declaring victory after flipping atime without re-measuring
Symptôme : L’équipe célèbre, mais le lendemain les « écritures lentes » reviennent.
Pourquoi : atime était un contributeur, pas le goulet d’étranglement ; le vrai problème peut être la latence des écritures sync, la saturation du SLOG, ou un vdev lent unique.
Correction : Faites des mesures avant/après, et creusez davantage si la latence continue de piquer.
cr0x@server:~$ zpool iostat -v tank 1
cr0x@server:~$ iostat -x 1
Mistake 4: Confusing “metadata writes” with “data corruption risk”
Symptôme : Quelqu’un refuse de désactiver atime parce que « les timestamps sont critiques » mais ne peut pas nommer un consommateur.
Pourquoi : Confondre la correction avec l’utilité. ZFS restera correct avec atime off ; vous choisissez de ne pas enregistrer un fait particulier.
Correction : Traitez atime comme une fonctionnalité ayant des parties prenantes. Identifiez les consommateurs ; si aucun, désactivez-le là où la performance compte.
Mistake 5: Benchmarking with tools that inadvertently update atime
Symptôme : Un « benchmark lecture » montre des écritures inattendues et des performances pires que prévu.
Pourquoi : Le benchmark lit des fichiers et déclenche des mises à jour atime, changeant la charge.
Correction : Soit désactivez atime pour un benchmark réaliste, soit assurez-vous que la sémantique du benchmark correspond aux besoins de production.
Listes de contrôle / plan étape par étape
Step-by-step plan: deciding whether to disable atime
- Identifiez le dataset qui supporte le chemin utilisé par l’application.
- Vérifiez le réglage actuel d’atime et s’il est hérité ou local.
- Demandez « qui utilise atime ? » et exigez une réponse qui nomme une application/processus.
- Mesurez la baseline : IOPS du pool, latence, et comportement TXG pendant la période problématique.
- Lancez un scan de lecture contrôlé et observez si les écritures augmentent.
- Si la politique le permet, basculez atime=off uniquement sur ce dataset.
- Relancez le même scan et comparez IOPS d’écriture et latence.
- Surveillez les effets secondaires (scripts de nettoyage, workflows forensiques, attentes des utilisateurs).
- Documentez l’intention du dataset : « dataset cache ; atime désactivé pour réduire le churn métadonnées. »
- Standardisez la création : intégrez la propriété dans la provision pour éviter de revivre ce combat.
Operational checklist: when “slow writes” hits the pager
- Confirmez quels dataset(s) sont affectés (
df -T,zfs list). - Vérifiez rapidement la santé du pool (
zpool status). - Surveillez l’I/O du pool (
zpool iostat -v 1) et la latence des périphériques (iostat -x 1). - Déterminez si la charge est sync-heavy (symptômes applicatifs +
zfs get sync). - Vérifiez les propriétés du dataset :
atime,recordsize,compression,logbias. - Cherchez des scans en lecture déclenchant des écritures (test contrôlé
find/head). - Si atime est on et la charge est lecture-intensive petits fichiers, testez
atime=off(si la gestion du changement le permet). - Re-mesurez et capturez des preuves pour le postmortem.
FAQ
1) Will disabling atime make ZFS “faster” in general?
Pas universellement. Cela aide surtout les charges avec beaucoup de lectures de fichiers, en particulier les scans de petits fichiers, où les mises à jour d’atime créent un flux constant d’écritures métadonnées. Pour les grosses écritures séquentielles, atime n’est généralement pas le facteur limitant.
2) Does atime=off affect mtime or ctime?
Non. Désactiver atime arrête la mise à jour du temps d’accès. mtime et ctime se comportent normalement lorsque vous modifiez le contenu ou les métadonnées.
3) Is disabling atime dangerous?
Ce n’est pas dangereux pour l’intégrité des données. C’est dangereux pour les suppositions. Si un workflow dépend d’horodatages d’accès précis, désactiver atime casse silencieusement ce workflow.
4) Can atime cause “slow writes” even if the app is writing, not reading?
Indirectement, oui. Si le système effectue aussi beaucoup de lectures (indexeurs, antivirus, sauvegardes, agents de monitoring), ces lectures peuvent générer des écritures métadonnées atime qui entrent en compétition pour les mêmes ressources TXG et bande passante I/O, augmentant la latence pour les écritures réelles.
5) Why do I see writes on the pool when users are “just browsing” a share?
Les listings de répertoires et les ouvertures de fichiers peuvent déclencher des mises à jour d’atime selon le comportement du client et le cache OS. Sur des partages avec beaucoup de petits fichiers, « juste naviguer » peut ressembler à un générateur de charge métadonnées.
6) If I disable atime, do I need to remount?
Non. C’est une propriété de dataset ZFS. Définissez-la avec zfs set atime=off dataset, et le comportement change immédiatement pour les nouveaux accès.
7) Does disabling atime reduce snapshot size or replication bandwidth?
Souvent, oui — parce que vous supprimez un flux de changements métadonnées qui aurait autrement sali des blocs. Les snapshots capturent les blocs modifiés, et les mises à jour d’atime peuvent compter comme des changements.
8) Is atime the same thing as “relatime” on Linux?
Ce sont des idées proches. relatime est un comportement de compromis courant sur les systèmes de fichiers Linux. Sur ZFS, la surface de contrôle fiable est la propriété de dataset atime. Si vous avez besoin de sémantiques spécifiques, validez avec des tests stat et zfs get.
9) How do I know if atime is the bottleneck or just noise?
Exécutez un scan de lecture contrôlé et observez l’augmentation des write IOPS et de la latence. Puis basculez atime à off et répétez. Si la pression d’écriture diminue et que la latence se stabilise, atime était au moins un contributeur significatif. Si rien ne change, continuez d’examiner le comportement sync, le déséquilibre des vdevs, la latence des périphériques, la fragmentation ou la contention CPU.
Conclusion
atime est une de ces fonctionnalités qui avaient parfaitement du sens quand les disques étaient lents, les systèmes de fichiers plus simples et que « savoir ce qui a été lu » semblait être une télémétrie opérationnelle. Dans les déploiements ZFS modernes — surtout ceux dominés par des caches, CI, conteneurs et un flux incessant de petits fichiers — atime peut transformer des lectures anodines en une charge d’écritures métadonnées en arrière-plan qui vole des IOPS, augmente la pression TXG et se présente à l’application comme des « écritures lentes ».
La solution est souvent un simple changement de propriété, mais la décision est plus large que le commutateur. Traitez atime comme un contrat : activez-le là où quelqu’un a réellement besoin de la vérité sur les temps d’accès, et désactivez-le là où il ne fait que brûler de la performance pour produire un timestamp que personne ne lit. Votre stockage sera plus rapide, vos graphiques plus calmes, et vous passerez moins de temps à expliquer aux dirigeants comment une lecture a causé une écriture.