Debian 13 : tempêtes d’écriture différée — ajustez vm.dirty sans risque de perte de données (cas n°45)

Cet article vous a aidé ?

Tout va bien jusqu’à ce que ça ne va plus. Votre hôte Debian 13 tourne tranquillement pendant des heures, puis un job batch arrive, la mémoire se remplit de cache dirty, et la machine passe de « réactive » à « pourquoi la frappe SSH est en slow motion ». L’iowait explose, les threads kworker s’illuminent, et votre base de données commence à timeout comme si elle auditionnait pour un drame.

Ceci est la tempête d’écriture différée (writeback) : un flush synchronisé de trop nombreuses pages dirty, qui arrive trop tard, au pire moment. La solution n’est généralement pas « acheter des disques plus rapides ». C’est faire en sorte que Linux commence le writeback plus tôt, par petites bouchées, et vérifier que vous n’avez pas échangé performance contre risque pour les données.

À quoi ressemble une tempête d’écriture différée (et pourquoi Debian 13 la rend évidente)

Une tempête d’écriture différée n’est pas « le disque est lent ». C’est un problème de synchronisation : le noyau laisse une grande quantité de mémoire accumuler des pages dirty (données de fichiers modifiées en cache et non encore sur disque), puis il doit les flusher. Si on flush trop à la fois, on obtient de la contention partout :

  • Les écrivains au premier plan sont soudainement fortement bridés, souvent en rafale.
  • kswapd ou la pression de reclaim force le writeback en même temps.
  • jbd2 (journal ext4) ou les commits du journal du système de fichiers s’empilent.
  • Les threads sensibles à la latence (bases de données, workers d’API) bloquent sur l’IO ou sur des verrous du système de fichiers.

Sur Debian 13, vous exécutez probablement un noyau moderne avec une meilleure visibilité et parfois un comportement de reclaim/writeback plus agressif que sur d’anciennes flottes. C’est bien : vous voyez le problème plus tôt. C’est aussi moins bien : des ratios par défaut qui « fonctionnaient bien » sur des disques magnétiques de l’époque de Debian 10 peuvent se transformer en falaises de latence façon NVMe parce que le système écrit très vite… jusqu’au moment où ça coince.

Une vérité opérationnelle : les tempêtes d’écriture différée sont rarement aléatoires. Elles se reproduisent quand votre pattern de charge est reproductible (jobs ETL, sauvegardes, compactions, pics de logs, pulls d’images de conteneurs, artefacts CI). C’est une aubaine. Profitez-en.

Blague #1 : le noyau est optimiste — il suppose que votre disque gérera parfaitement tous ces écrits plus tard. « Plus tard » c’est quand vous êtes en astreinte.

Notions de base sur les pages dirty : ce que fait réellement le noyau

Linux utilise la mémoire libre de façon agressive comme cache de pages. Quand les applications écrivent dans des fichiers (sans utiliser direct IO), elles écrivent souvent d’abord en mémoire. Ces pages deviennent « dirty ». Plus tard, le writeback en arrière-plan les flush vers le disque. C’est performant et découple la vitesse de l’application de la vitesse du disque — jusqu’à ce que vous atteigniez les limites.

Termes clés qui comptent en production

  • Pages dirty : données de fichiers en cache modifiées en RAM mais pas encore persistées sur le stockage.
  • Writeback : le noyau qui flush les pages dirty vers le disque, en tâche de fond ou sous pression.
  • Seuil de writeback en arrière-plan : quand les threads de fond commencent à écrire les données dirty de manière proactive.
  • Limite dirty : quand les processus générant des données dirty sont throttlés (ou forcés à écrire).
  • Durée d’expiration : combien de temps les données dirty peuvent rester avant que le writeback n’essaie de les pousser.

Les commandes se trouvent dans /proc/sys/vm/, notamment :

  • vm.dirty_background_ratio et vm.dirty_ratio
  • vm.dirty_background_bytes et vm.dirty_bytes
  • vm.dirty_expire_centisecs et vm.dirty_writeback_centisecs

Ratios versus octets (choisissez un style, ne mélangez pas sans précaution)

Les ratios sont des pourcentages de « mémoire disponible » (pas strictement de la RAM totale ; le noyau utilise un comptage interne qui varie avec la pression mémoire). Les octets sont des seuils absolus. En production, les octets sont plus prévisibles entre machines et en cas de variations de pression mémoire, surtout sur des hôtes riches en conteneurs où la « mémoire libre » est une cible mouvante.

Si vous définissez dirty_bytes, les knobs de ratio sont effectivement ignorés (même chose pour dirty_background_bytes). C’est généralement ce que vous voulez : moins de surprises.

Ce que signifie réellement le « risque de données » ici

Régler les vm.dirty* ne change pas la correction du système de fichiers ni les garanties de journaling. Cela change combien de données sont autorisées à rester dirty en RAM et combien de temps elles peuvent y rester. Les risques sont opérationnels :

  • Des limites dirty plus grandes peuvent améliorer le débit mais augmenter la quantité de données « à risque » en cas de perte d’alimentation (données non encore flushées), et produire des tempêtes d’écriture plus grandes et douloureuses.
  • Des limites dirty plus petites réduisent la taille des tempêtes et l’exposition des données dirty, mais peuvent throttler les écrivains plus tôt et réduire le débit de pointe.

La plupart des équipes n’ont pas besoin d’un débit héroïque. Elles ont besoin d’une latence prévisible. Réglez en conséquence.

Une citation reliée à la fiabilité, parce qu’elle a sa place ici. Charity Majors a dit : « You can’t improve what you don’t measure. » C’est tout le jeu avec les tempêtes d’écriture différée : mesurer d’abord, régler ensuite.

Faits & historique : comment on en est arrivés là

  • Fait 1 : Les noyaux Linux anciens utilisaient un comportement de cache beaucoup plus simple ; le writeback moderne a évolué fortement autour de l’ère 2.6 avec des mécanismes par bdi (backing device) pour mieux gérer la pression IO.
  • Fait 2 : Les ratios dirty par défaut supposaient historiquement « un disque raisonnable » et une RAM à échelle humaine. Ces valeurs ont mal vieilli quand des hôtes à 128–512 Go de RAM sont devenus la norme.
  • Fait 3 : Le throttling dirty est un outil de latence déguisé en costume de débit : il existe pour empêcher le système de rendre « infinie » la mise en dirty de la mémoire puis de bloquer indéfiniment sur l’IO.
  • Fait 4 : Avec les SSD et le NVMe, le problème se déplace souvent du débit vers la latence de queue — les pics du 99.9e percentile causés par l’encombrement et les rafales soudaines.
  • Fait 5 : Les systèmes de fichiers journalisés (ext4, XFS) peuvent encore produire des patterns IO heurtés, surtout sous des charges riches en métadonnées.
  • Fait 6 : Les environnements virtualisés peuvent amplifier les tempêtes : writeback au niveau hôte plus writeback invité = « double caching », et chaque couche pense rendre service.
  • Fait 7 : L’idée du noyau de « mémoire disponible » est dynamique ; les seuils en ratio peuvent monter et descendre pendant le reclaim, changeant le comportement du writeback en plein incident.
  • Fait 8 : Régler dirty_writeback_centisecs à 0 ne signifie pas « pas de writeback », cela désactive juste le timer périodique ; le writeback se produit toujours via d’autres déclencheurs.
  • Fait 9 : Beaucoup d’incidents « le disque est lent » sont en réalité des incidents « la queue est saturée » — votre disque peut être rapide, mais vous l’alimentez avec des rafales pathologiques.

Mode d’urgence : diagnostic rapide (vérifier 1/2/3)

Version astreinte. Vous n’êtes pas là pour philosopher. Vous êtes là pour trouver le goulet en cinq minutes et décider si régler vm.dirty aide.

Premier point : confirmez que c’est du writeback, pas de l’IO aléatoire

  • Vérifiez les niveaux de pages dirty et l’activité de writeback.
  • Vérifiez si des tâches sont bloquées en état D.
  • Vérifiez si l’iowait et la profondeur de la queue disque montent pendant le gel.

Second point : identifiez le périphérique et la source de pression

  • Quel dispositif bloc est saturé ? (NVMe ? RAID ? bloc réseau ?)
  • La charge est-elle des écritures de fichiers, des commits de journal, ou du swap/reclaim forçant le writeback ?
  • Cela se passe-t-il dans une VM ou un hôte de conteneurs avec double caching ?

Troisième point : choisissez la mitigation minimale et sûre

  • Réduisez les limites dirty (utilisez des octets) pour démarrer le writeback plus tôt et éviter les grosses rafales de flush.
  • Raccourcissez éventuellement l’expiration dirty pour que les données dirty anciennes ne s’accumulent pas.
  • Validez avec des métriques de latence et de file d’attente ; revenez en arrière si le débit s’effondre.

Tâches pratiques : 14 vérifications avec commandes, sorties et décisions

Voici des tâches réelles à lancer sur Debian 13 pendant un incident ou en fenêtre calme. Chaque tâche inclut : commande, sortie d’exemple, ce que cela signifie, et quelle décision prendre.

Task 1: Inspect current dirty tunables

cr0x@server:~$ sysctl vm.dirty_ratio vm.dirty_background_ratio vm.dirty_bytes vm.dirty_background_bytes vm.dirty_expire_centisecs vm.dirty_writeback_centisecs
vm.dirty_ratio = 20
vm.dirty_background_ratio = 10
vm.dirty_bytes = 0
vm.dirty_background_bytes = 0
vm.dirty_expire_centisecs = 3000
vm.dirty_writeback_centisecs = 500

Signification : Cet hôte utilise des seuils basés sur des ratios (les octets sont à 0). Le writeback en arrière-plan commence à 10 % dirty, et les écrivains sont throttlés à 20 % dirty.

Décision : Sur les hôtes à grande mémoire, 20 % peut représenter une énorme quantité. Si des tempêtes existent, prévoyez de passer à des seuils en octets.

Task 2: Confirm the machine’s memory scale (ratios are relative)

cr0x@server:~$ free -h
               total        used        free      shared  buff/cache   available
Mem:           256Gi       92Gi       11Gi       2.0Gi      154Gi      150Gi
Swap:          8.0Gi      0.0Gi      8.0Gi

Signification : Avec 256 GiB de RAM, une limite dirty de 20 % peut se traduire par des dizaines de gigaoctets de données dirty. C’est un flush important.

Décision : Préférez les octets. Par exemple, limitez le dirty à quelques Gio en chiffres simples sauf raison contraire.

Task 3: Watch dirty and writeback pages in real time

cr0x@server:~$ awk '/Dirty:|Writeback:|MemAvailable:|Cached:|Buffers:/{print}' /proc/meminfo
MemAvailable:   157392112 kB
Cached:         132884944 kB
Buffers:          126764 kB
Dirty:           6248120 kB
Writeback:        184320 kB

Signification : ~6.2 GiB dirty et un writeback actif. Lors d’une tempête, Dirty peut monter rapidement puis Writeback explose quand le flush commence.

Décision : Si Dirty grimpe en plusieurs Gio et que Writeback est à la traîne, votre seuil de fond est trop haut ou le stockage ne suit pas.

Task 4: Verify blocked tasks and IO wait during the freeze

cr0x@server:~$ top -b -n1 | head -n 20
top - 11:08:21 up 14 days,  3:52,  1 user,  load average: 18.42, 16.77, 10.91
Tasks: 612 total,   4 running,  58 sleeping,   0 stopped,   9 zombie
%Cpu(s):  5.3 us,  2.1 sy,  0.0 ni, 33.7 id, 58.6 wa,  0.0 hi,  0.3 si,  0.0 st
MiB Mem : 262144.0 total,  11540.0 free,  94512.0 used, 156092.0 buff/cache
MiB Swap:   8192.0 total,   8192.0 free,      0.0 used. 157000.0 avail Mem

Signification : 58.6 % iowait est une saturation classique. La charge moyenne augmente parce que les tâches attendent, pas parce que vous êtes « CPU bound ».

Décision : Poursuivez le diagnostic du chemin IO. Le réglage dirty aide quand l’attente corrèle avec un writeback massif.

Task 5: Identify the busiest block device and queue pressure

cr0x@server:~$ iostat -x 1 3
Linux 6.12.0 (server) 	12/30/2025 	_x86_64_	(64 CPU)

avg-cpu:  %user   %nice %system %iowait  %steal   %idle
           4.92    0.00    2.18   56.10    0.00   36.80

Device            r/s     w/s   rKB/s   wKB/s  avgrq-sz avgqu-sz   await  r_await  w_await  svctm  %util
nvme0n1          8.0  1900.0    256.0  78000.0    82.4     32.5   17.3     2.1    17.4    0.5   99.2

Signification : %util proche de 100 % et avgqu-sz élevé signifient que le périphérique est saturé. La latence d’écriture (w_await) est élevée.

Décision : Si cela correspond à l’accumulation de dirty, réduisez les seuils dirty. Sinon, trouvez la source d’IO (journal, swap, lectures aléatoires, etc.).

Task 6: Check pressure stall information (PSI) for IO

cr0x@server:~$ cat /proc/pressure/io
some avg10=12.43 avg60=8.11 avg300=3.21 total=91823354
full avg10=7.02 avg60=4.88 avg300=1.94 total=51299210

Signification : full indique le temps pendant lequel les tâches sont complètement bloquées sur l’IO. S’il augmente pendant la tempête, ce n’est pas subtil.

Décision : Un full élevé renforce l’argument en faveur d’un lissage du writeback ; vous observez des blocages système généralisés.

Task 7: Observe writeback and throttling counters

cr0x@server:~$ egrep 'dirty|writeback|balance_dirty' /proc/vmstat
nr_dirty 1605402
nr_writeback 48812
nr_writeback_temp 0
nr_dirtied 981234567
nr_written 979998123
balance_dirty_pages 734520
dirty_background_threshold 786432
dirty_threshold 1572864

Signification : balance_dirty_pages incrémente quand les tâches sont throttlées pour garder les pages dirty sous contrôle. Les seuils sont indiqués en pages.

Décision : Si vous voyez un nr_dirty énorme et des pics soudains de balance_dirty_pages, vous êtes en territoire de burst-throttle. Réglez pour démarrer plus tôt et éviter les falaises.

Task 8: Identify which processes are writing

cr0x@server:~$ pidstat -d 1 5
Linux 6.12.0 (server) 	12/30/2025 	_x86_64_	(64 CPU)

11:10:01      UID       PID   kB_rd/s   kB_wr/s kB_ccwr/s iodelay  Command
11:10:02        0      2143      0.00  32100.00  31500.00      12  rsyslogd
11:10:02      105     18721      0.00  98000.00  96500.00      88  postgres
11:10:02        0     291002     0.00  14000.00  13900.00       6  backup-agent

Signification : kB_ccwr/s montre les « octets d’écriture annulés » — des données rendues dirty puis tronquées ou réécrites avant le writeback. Des valeurs élevées peuvent indiquer du churn.

Décision : Si un seul processus domine les écritures, vous pouvez aussi corriger la charge (regrouper, cadence d’fsync, rotation de logs) au lieu de ne jouer que sur les seuils noyau.

Task 9: Check filesystem and mount options

cr0x@server:~$ findmnt -no SOURCE,TARGET,FSTYPE,OPTIONS /var/lib/postgresql
/dev/nvme0n1p3 /var/lib/postgresql ext4 rw,relatime,errors=remount-ro,data=ordered

Signification : ext4 data=ordered est par défaut et sensé ; il journalise les métadonnées et s’assure que les blocs de données sont écrits avant que les métadonnées ne pointent vers eux.

Décision : Ne changez pas les modes de journalisation en pleine incident. Si vous avez des options de montage exotiques, documentez-les et réévaluez plus tard.

Task 10: Check if you’re accidentally using writeback caching without power-loss protection

cr0x@server:~$ lsblk -d -o NAME,MODEL,ROTA,TRAN,TYPE,SIZE
NAME   MODEL                 ROTA TRAN TYPE  SIZE
nvme0n1 Samsung SSD 980 PRO     0 nvme disk  1.8T
cr0x@server:~$ sudo nvme id-ctrl /dev/nvme0n1 | egrep 'vwc|oncs'
vwc     : 0x01
oncs    : 0x001f

Signification : vwc indique la présence d’un cache d’écriture volatile. Ce n’est pas automatiquement mauvais, mais cela augmente l’importance des barrières/flush et de la protection contre la perte de puissance.

Décision : Si la plateforme n’a pas de protection contre la perte d’alimentation, gardez des limites dirty conservatrices. Vous ne pouvez pas « sysctl » contourner la physique.

Task 11: Confirm the IO scheduler (and avoid cargo culting)

cr0x@server:~$ cat /sys/block/nvme0n1/queue/scheduler
[none] mq-deadline kyber bfq

Signification : NVMe utilise souvent none par défaut ; cela peut être correct. Les ordonnanceurs peuvent affecter la latence sous contention.

Décision : Ne considérez pas le choix de l’ordonnanceur comme une solution de premier niveau pour les tempêtes de writeback. Réglez d’abord les seuils dirty ; changez d’ordonnanceur seulement avec des mesures.

Task 12: Measure fsync/commit pressure (ext4 example)

cr0x@server:~$ ps -eLo pid,comm,wchan:30 | egrep 'jbd2|fsync|fdatasync' | head
  612 jbd2/nvme0n1p3      jbd2_journal_commit_transaction
18721 postgres             do_fsync
18804 postgres             do_fsync

Signification : Si de nombreux threads attendent dans les chemins fsync tandis que les pages dirty sont élevées, votre charge force des points de durabilité sous un writeback intense.

Décision : Envisagez de réduire les limites dirty (baisser background/dirty bytes) et vérifiez le comportement fsync côté application et les paramètres de checkpoint.

Task 13: Apply a temporary, safer writeback profile (bytes-based)

cr0x@server:~$ sudo sysctl -w vm.dirty_background_bytes=$((512*1024*1024)) vm.dirty_bytes=$((2*1024*1024*1024))
vm.dirty_background_bytes = 536870912
vm.dirty_bytes = 2147483648

Signification : Le writeback en arrière-plan commence autour de 512 MiB dirty, et le throttling commence autour de 2 GiB dirty.

Décision : C’est un premier réglage raisonnable pour beaucoup de serveurs. Si les tempêtes diminuent et que la latence s’améliore, rendez-le persistant et affinez.

Task 14: Validate that storms shrink (dirty levels + IO await)

cr0x@server:~$ watch -n1 'awk "/Dirty:|Writeback:/{print}" /proc/meminfo; iostat -x 1 1 | tail -n +7'
Every 1.0s: awk "/Dirty:|Writeback:/{print}" /proc/meminfo; iostat -x 1 1 | tail -n +7

Dirty:            612480 kB
Writeback:         98304 kB
nvme0n1          6.0  820.0   192.0  31000.0    78.3      6.2    3.8     1.9     3.8    0.4   71.0

Signification : Dirty reste sous ~600 MiB, le writeback est actif mais pas explosif, await et la queue sont plus bas, et %util a de la marge.

Décision : Si cela tient pendant la charge maximale, vous avez maîtrisé la tempête. Si le débit chute trop, augmentez dirty_bytes prudemment (pas les ratios).

Stratégie de réglage sûre : réduire les tempêtes sans jouer au plus malin

Le tuning du writeback est trompeusement facile à mal faire. Les gens lisent un billet, mettent vm.dirty_ratio=80, puis se demandent pourquoi un coupure de courant a transformé une file en scène de crime.

Voici la stratégie qui fonctionne en vraie production :

1) Préférez les seuils en octets sur les serveurs modernes

Les ratios évoluent avec la mémoire. Ça semble bien jusqu’à ce que votre « 20 % dirty » devienne 30–50 GiB sur un gros hôte. Si votre stockage peut flusher ça rapidement et de façon constante, vous ne liriez pas ceci.

Recommandation : Fixez :

  • vm.dirty_background_bytes à 256–1024 MiB
  • vm.dirty_bytes à 1–8 GiB selon le stockage et la charge

Commencez conservateur. Augmentez seulement si vous avez mesuré une douleur au niveau du débit.

2) Gardez le seuil de fond nettement sous la limite dirty

Si background et dirty sont trop proches, vous n’obtenez pas un « writeback lissé », vous obtenez « le writeback démarre tard puis throttle immédiatement ». Ça ressemble à un blocage.

Règle pratique : background à 1/4–1/2 de la limite dirty. Exemple : 512 MiB background, 2 GiB dirty.

3) Raccourcissez l’expiration dirty si votre charge crée beaucoup de dirty longue durée

vm.dirty_expire_centisecs contrôle combien de temps les données dirty peuvent rester avant d’être « assez vieilles » pour être flushées. Les valeurs par défaut signifient souvent « jusqu’à plusieurs dizaines de secondes ». Ça peut convenir. Ça peut aussi permettre une accumulation lente qui devient une tempête quand la pression arrive.

Recommandation : Si vous voyez « accumulation silencieuse puis flush soudain », tentez de réduire modérément l’expiration (p.ex. de 30s à 10–15s). Ne la mettez pas à 1s et ne soyez pas surpris si le débit change.

4) Ne désactivez pas le writeback périodique à moins de comprendre les conséquences

vm.dirty_writeback_centisecs contrôle les réveils périodiques pour le writeback de fond. Le mettre à 0 change la dynamique et peut déplacer le flush vers des déclencheurs plus réactifs (reclaim, sync, chemins fsync intensifs). Ce n’est pas « plus efficace ». C’est « plus chaotique ».

5) Rappelez-vous ce que vous optimisez : la latence de queue

Vous ne tentez pas de gagner un benchmark. Vous cherchez à garder la latence p99 raisonnable tout en écrivant suffisamment de données. Des limites dirty plus basses lissent le writeback et réduisent la taille maximale des rafales. C’est l’objectif.

Blague #2 : si vous réglez les ratios dirty sur la base du « ça paraît plus rapide », vous avez réinventé les tests de performance — mal.

6) Rendez-le persistant, révisé et réversible

Les sysctls temporaires règlent les incidents. Les sysctls persistants empêchent les répétitions. Mais seulement s’ils sont déployés comme tout autre changement production : revus par les pairs, documentés et déployés progressivement.

cr0x@server:~$ sudo tee /etc/sysctl.d/99-dirty-writeback.conf >/dev/null <<'EOF'
# Reduce writeback storms by starting writeback earlier and capping dirty cache.
# Bytes-based thresholds are predictable across RAM sizes.
vm.dirty_background_bytes = 536870912
vm.dirty_bytes = 2147483648
vm.dirty_expire_centisecs = 1500
vm.dirty_writeback_centisecs = 500
EOF
cr0x@server:~$ sudo sysctl --system
* Applying /etc/sysctl.d/99-dirty-writeback.conf ...
vm.dirty_background_bytes = 536870912
vm.dirty_bytes = 2147483648
vm.dirty_expire_centisecs = 1500
vm.dirty_writeback_centisecs = 500

Signification : Vous avez appliqué un profil contrôlé : writeback en arrière-plan démarrant plus tôt, cache dirty maximal réduit, expiration légèrement accélérée.

Décision : Déployez d’abord sur un hôte canari, puis sur une tranche de la flotte. Surveillez la latence p99 et l’encombrement IO.

Profils suggérés (VMs, DBs, file servers, NVMe)

Ceux-ci sont des points de départ, pas des dogmes. Les bonnes valeurs dépendent du débit d’écriture du stockage, de la tolérance à la latence IO, et de la « raideur » des écrivains.

Profil A : VM ou serveur applicatif généraliste (sensible à la latence)

  • dirty_background_bytes = 256–512 MiB
  • dirty_bytes = 1–2 GiB
  • dirty_expire_centisecs = 1500–3000 (15–30s)

Utilisez ceci quand vous tenez plus à la réactivité qu’au débit d’écriture en continu.

Profil B : Hôte de base de données (points de durabilité + écritures stables)

  • dirty_background_bytes = 512 MiB–1 GiB
  • dirty_bytes = 2–4 GiB
  • dirty_expire_centisecs = 1000–2000 (10–20s)

Les bases de données effectuent souvent leurs propres flushs/checkpoints et tiennent à la latence des fsync. Une taille de tempête plus petite est généralement bénéfique.

Profil C : Serveur de fichiers / cible de sauvegarde (priorité débit, mais sans tempêtes)

  • dirty_background_bytes = 1–2 GiB
  • dirty_bytes = 4–8 GiB
  • dirty_expire_centisecs = 3000 (30s)

Pour des ingestes séquentielles où les utilisateurs tolèrent une latence légèrement supérieure mais pas des blocages d’hôte entier.

Profil D : NVMe RAID ou SSD local très rapide (éviter l’« optimisme excessif »)

Les périphériques rapides peuvent flusher vite, ce qui vous tente à augmenter les limites dirty. Le piège est que la mise en file peut quand même créer des pics, et le writeback de fond peut prendre du retard quand les patterns metadata/journal deviennent bizarres.

  • Commencez quand même par le Profil A ou B.
  • N’augmentez qu’après avoir mesuré le comportement d’écriture soutenu et la latence p99.

Trois mini-récits d’entreprise venus des tranchées du writeback

Mini-récit 1 : Incident causé par une mauvaise hypothèse (l’histoire « la RAM est gratuite, non ? »)

Une société SaaS de taille moyenne a migré un service analytique batch de nœuds 64 Go vers de nouveaux nœuds 256 Go. Le stockage est resté de la même classe : des SSD corrects derrière un contrôleur, suffisants pendant des années. Leur hypothèse était simple : plus de RAM veut dire plus de cache, donc moins de hits disque, donc jobs plus rapides.

Le premier lundi après le déploiement, le job d’ingestion a fini plus vite — jusqu’à ce que ça coince. À mi-parcours, la latence API a explosé. Les sessions SSH ont bégayé. La charge moyenne a atteint des chiffres qui faisaient ressembler le graphe à une skyline. L’équipe a d’abord blâmé un voisin bruyant dans la virtualisation car l’usage CPU était faible et la charge élevée. Lecture classique erronée.

Quand ils ont finalement regardé /proc/meminfo et /proc/vmstat, c’était évident : le cache dirty avait grimpé à des dizaines de gigaoctets, puis le noyau a throttlé les écrivains et lancé un flush agressif. Le stockage pouvait écrire rapidement, mais pas « flusher 40 GiB tout en servant des lectures aléatoires et des fsync » rapidement. La charge n’avait pas changé ; ce sont les ratios par défaut qui avaient changé.

Ils ont corrigé en passant à des seuils dirty en octets et en réduisant le trigger de fond. Le job est resté rapide, mais le système n’a plus gelé. L’erreur n’était pas « le cache aide ». C’était « les valeurs par défaut évoluent avec le hardware ». Elles ne tiennent pas toujours.

Mini-récit 2 : L’optimisation qui a mal tourné (l’expérience « laisser buffer plus »)

Une équipe fintech avait un pipeline de logs à haut débit écrivant de gros fichiers en append-only. Ils voulaient maximiser le débit car la réconciliation nocturne en dépendait. Quelqu’un a suggéré d’augmenter vm.dirty_ratio et vm.dirty_background_ratio pour « laisser Linux buffer plus et écrire en plus gros batches ». Sur le papier, cela peut améliorer l’efficacité des écritures séquentielles.

Ça a fonctionné lors d’un test rapide. Le débit s’est amélioré. Tout le monde a hoché la tête. Puis la charge a rencontré la réalité : rotation de logs, tâches de compression, et un processus de snapshot périodique. Ceux-ci ont introduit des rafales de métadonnées et d’opérations sync. Le système a commencé à expérimenter des pauses soudaines autour de l’heure pile, comme un train de banlieue prenant des rouges incompréhensibles.

Le problème profond : en augmentant les ratios dirty, ils ont accru considérablement l’empreinte dirty maximale. Sous la pression de la rotation et des snapshots, le writeback a dû pousser une montagne de données dirty d’un coup. Les tâches au premier plan — surtout celles faisant des fsync — se sont retrouvées bloquées derrière ce flush. Le pipeline n’a pas seulement ralenti ; il a créé des délais en cascade sur des systèmes dépendants.

La correction n’était pas « annuler la performance ». C’était choisir des limites dirty plus petites et démarrer le writeback plus tôt, puis ajuster le pipeline pour écrire de manière plus régulière. Leur « optimisation » était un changement orienté débit dans un système aux dépendances sensibles à la latence. Voilà comment on se plante.

Mini-récit 3 : La pratique ennuyeuse mais correcte qui a sauvé la mise (canari + métriques + rollback)

Une société média avait une flotte d’hôtes Debian servant des uploads et des sorties de transcodage. Ils avaient déjà été brûlés par des « fixes sysctl en une ligne » qui ont provoqué une semaine de régressions subtiles. Ils traitaient donc le tuning noyau comme un déploiement applicatif : canari, observer, élargir, et garder un plan de rollback.

Quand des tempêtes d’écriture différée ont commencé à apparaître pendant les fenêtres de pic d’upload, ils n’ont pas appliqué des changements sur toute la flotte. Ils ont choisi un hôte représentatif, appliqué des seuils dirty en octets, et surveillé deux choses : IO PSI full et la latence de requête en bordure applicative. Ils ont aussi surveillé les taux d’erreur, car rien ne dit « oops » comme des timeouts déguisés en succès.

Le canari s’est amélioré : moins de stalls IO, queue tail réduite, et aucun effondrement du débit. Ils ont déployé sur 10 % de la flotte, puis 50 %. Un cluster avec des SSD SATA plus anciens a vu une légère baisse de débit, donc ils ont augmenté modestement dirty_bytes pour cette classe matérielle seulement. Pas de drame, pas de salle de crise, pas d’héros.

La pratique ennuyeuse a gagné : déploiement contrôlé plus métriques pertinentes. Quand vous réglez le writeback, le meilleur outil n’est pas un sysctl — c’est la retenue.

Erreurs courantes : symptôme → cause racine → réparation

1) Symptôme : gels périodiques d’hôte entier avec iowait élevé

Cause racine : Le cache dirty s’accumule jusqu’à la limite dirty, puis des rafales de writeback saturent le stockage et throttlent tout.

Réparation : Utilisez des seuils en octets ; baissez dirty_bytes et dirty_background_bytes pour que le writeback commence plus tôt. Validez avec Dirty/Writeback de /proc/meminfo et la profondeur de queue via iostat -x.

2) Symptôme : le débit est correct, mais la latence p99 pique sous charge mixte

Cause racine : Effets de mise en file dus aux rafales de writeback ; journaling et points fsync concurrencent le flush en masse.

Réparation : Réduire les limites dirty (rafales plus petites). Cherchez les processus fsync intensifs et ajustez la cadence de flush côté application si possible.

3) Symptôme : régler les ratios « fonctionne » sur une classe d’hôte mais pas une autre

Cause racine : Les seuils en ratio évoluent avec la mémoire et avec le comptage de mémoire disponible du noyau, qui varie selon la charge et le rôle de l’hôte.

Réparation : Standardisez sur dirty_bytes/dirty_background_bytes par classe matérielle.

4) Symptôme : après avoir baissé les limites dirty, les écrivains massifs ralentissent fortement

Cause racine : Vous avez throttlé trop tôt par rapport au débit soutenu du disque ; le writeback de fond ne suit pas, donc les écrivains sont constamment forcés d’attendre.

Réparation : Augmentez légèrement dirty_background_bytes (démarrer le writeback plus tôt mais donner un peu plus de marge) et/ou augmentez modestement dirty_bytes. Confirmez le débit et la latence du disque ; si le périphérique est simplement trop lent, le tuning ne créera pas de bande passante.

5) Symptôme : « sync » ou snapshots causent des stalls de plusieurs minutes

Cause racine : Un gros backlog dirty rencontre un trigger de flush forcé (sync, snapshot, commit FS), provoquant un déluge d’écritures et d’activité de journal.

Réparation : Maintenez un backlog dirty petit via des seuils en octets. Planifiez les snapshots hors des périodes de pointe. Assurez-vous que vos outils de snapshot n’obligent pas un sync global inutilement.

6) Symptôme : les tempêtes se produisent surtout dans des VM, pas sur du bare metal

Cause racine : Double caching et interactions de writeback entre le client et l’hyperviseur. Chaque couche tamponne, puis flush en rafales.

Réparation : Utilisez des dirty bytes conservateurs dans les invités. Si possible, alignez les paramètres de stockage de l’hyperviseur et évitez les tampons extrêmes dans les deux couches. Mesurez côté invité et côté hôte.

Checklists / plan pas-à-pas

Étapes : de l’incident à une configuration stable

  1. Confirmez que c’est du writeback. Vérifiez Dirty/Writeback dans /proc/meminfo, IO PSI, et la file d’attente de iostat.
  2. Identifiez le périphérique chaud. Trouvez le périphérique bloc saturé et confirmez qu’il mappe au système de fichiers affecté.
  3. Capturez une baseline courte. Sauvegardez les sysctls actuels et un snapshot de 2–3 minutes des stats IO pendant l’événement.
  4. Appliquez des seuils temporaires en octets. Commencez avec 512 MiB background et 2 GiB dirty. Évitez de toucher les options de montage en pleine incident.
  5. Observez la forme. Dirty devrait osciller sous le plafond ; la profondeur de queue IO devrait baisser ; la latence devrait se lisser.
  6. Validez la santé applicative. Latence p99, taux d’erreur, et latence de commit BD si applicable.
  7. Pérennisez le changement. Utilisez /etc/sysctl.d/ avec des commentaires expliquant pourquoi.
  8. Déploiement canari. Un hôte → petite tranche → flotte, avec des dashboards incluant IO PSI et disk await.
  9. Affinez par classe. Les vieux disques et les blocs réseau peuvent nécessiter des plafonds différents du NVMe local.
  10. Rédigez une note runbook. « Si vous voyez A/B/C, vérifiez ces valeurs et ces graphes. » Le vous du futur est partie prenante.

Checklist de sécurité (la liste « ne pas créer de nouveaux incidents »)

  • Ne montez pas les seuils dirty pendant un incident à moins d’être absolument certain que vous êtes seulement limité en débit et pas sensible à la latence.
  • Ne mélangez pas ratio et octets « parce que les deux semblent pertinents ». Choisissez les octets pour la prévisibilité.
  • Ne désactivez pas les timers de writeback comme première réponse.
  • Ne changez pas les modes de journalisation ou les réglages de barrières pour résoudre les tempêtes. Ce n’est pas du tuning ; c’est un pari.
  • Gardez des caps dirty conservateurs sur les systèmes sans protection contre la perte d’alimentation.
  • Testez sur le même pattern de charge qui déclenche la tempête (fenêtre batch, fenêtre de sauvegarde, fenêtre de compaction).

FAQ

1) Les tempêtes d’écriture sont-elles un bug de Debian 13 ?

Généralement non. C’est un décalage entre des valeurs par défaut, la taille de votre RAM, et le caractère rafaleux de votre charge. Debian 13 rend juste le problème plus visible parce que les stacks modernes exposent mieux les problèmes de tail latency.

2) Dois-je utiliser vm.dirty_ratio ou vm.dirty_bytes ?

Utilisez des octets sur les serveurs où la prévisibilité compte (ce qui est le cas de la plupart des serveurs). Les ratios peuvent convenir sur des machines petites et homogènes, mais ils évoluent mal avec la montée de la RAM.

3) Baisser les limites dirty augmente-t-il la sécurité des données ?

Cela réduit la quantité de données de fichiers non écrites qui peuvent rester en RAM, donc l’exposition en cas de perte d’alimentation diminue. Cela ne remplace pas les pratiques de durabilité appropriées (journaling, flushs corrects, UPS/PLP sur le stockage).

4) Puis-je régler des limites dirty extrêmement basses pour éliminer les tempêtes ?

Vous le pouvez, mais vous pouvez aussi simplement convertir les tempêtes en throttling permanent. L’objectif est un writeback plus petit et continu — pas forcer chaque écrivain à agir comme s’il faisait de l’IO synchrone tout le temps.

5) Qu’en est-il des bases de données utilisant O_DIRECT ou direct IO ?

Le direct IO contourne la page cache pour les fichiers de données, ce qui réduit la pression des pages dirty pour cette charge. Mais les bases écrivent encore des logs, des métadonnées et d’autres fichiers via le cache, et le reste du système utilise la page cache. Le tuning dirty peut donc encore compter.

6) Dois-je plutôt modifier vm.swappiness ?

Le swappiness affecte le comportement de reclaim et l’utilisation du swap ; il peut influencer quand le writeback est déclenché sous pression mémoire, mais ce n’est pas l’outil principal pour les tempêtes de writeback. Réglez d’abord les seuils dirty, puis regardez le reclaim si vous voyez encore du thrashing.

7) Pourquoi les tempêtes surviennent-elles à des moments « aléatoires » ?

Elles sont souvent déclenchées par un événement périodique : rotation de logs, sauvegardes, compaction, snapshot, ou pression mémoire due à une nouvelle charge. Corrélez l’heure avec cron/systemd timers et les plannings applicatifs.

8) Changer l’ordonnanceur IO est-il une meilleure solution que le tuning dirty ?

Parfois, les ordonnanceurs aident la tail latency sous contention, mais ils ne résolvent pas la cause racine de « trop de données dirty flushées trop tard ». Le tuning de l’ordonnanceur sans régler le writeback, c’est polir la mauvaise partie de la machine.

9) Comment savoir si je n’ai pas juste masqué le problème ?

Si le périphérique reste collé à 100 % d’utilisation et que la queue reste profonde, vous n’avez pas résolu le goulet ; vous avez seulement déplacé la douleur. Une bonne correction réduit le temps de stall (IO PSI), réduit la profondeur de queue, et améliore la latence applicative sans faire exploser les taux d’erreur.

Prochaines étapes à faire aujourd’hui

Si vous observez des tempêtes d’écriture sur Debian 13, ne partez pas d’une superstition. Commencez par des preuves : niveaux Dirty/Writeback, IO PSI, et profondeur de queue disque. Puis faites un changement discipliné : passez des seuils en ratio à des caps en octets adaptés à votre réalité de stockage.

Faites ceci ensuite :

  1. Lancez les vérifications de diagnostic rapide et capturez une baseline pendant une tempête.
  2. Appliquez un profil temporaire : dirty_background_bytes=512MiB, dirty_bytes=2GiB, et optionnellement dirty_expire_centisecs=1500.
  3. Confirmez que les tempêtes diminuent : Dirty reste borné, la profondeur de queue IO baisse, la p99 s’améliore.
  4. Pérennisez la config dans /etc/sysctl.d/ avec des commentaires, canarisez, puis déployez.

Vous n’avez pas besoin d’éliminer le writeback. Il faut juste l’empêcher d’arriver d’un coup, comme une facture impayée avec intérêts.

← Précédent
Proxmox ZFS « pool is not healthy » : que faire avant que ça empire
Suivant →
Permissions du répertoire web Debian/Ubuntu : arrêter les 403 sans 777 (Cas n°69)

Laisser un commentaire