Vous vous connectez en SSH à une machine Ubuntu 24.04 qui est « en feu » : la moyenne de charge est à deux chiffres, des services expirent, et votre canal d’incident se remplit de captures d’écran de top montrant… essentiellement aucune utilisation CPU. Les graphiques ne ressemblent pas à une tempête CPU. Pourtant le système n’est clairement pas sain.
Bienvenue dans le piège iowait : la machine n’est pas « occupée à calculer », elle attend. Et Linux affichera volontiers cette attente comme « n’utilisant pas le CPU » alors que vos utilisateurs vivent « tout est cassé ».
Ce que signifie réellement charge élevée + faible CPU
La moyenne de charge Linux n’est pas « utilisation CPU ». C’est un comptage de tâches qui sont soit :
- En exécution (sur le CPU, état
R) - En sommeil ininterruptible (généralement attente I/O, état
D)
Quand le stockage devient lent, vos threads s’entassent en état D. Ils ne consomment pas de cycles CPU, donc l’utilisation CPU semble faible. Mais ils contribuent toujours à la moyenne de charge car ils sont prêts à faire du travail… dès que le noyau pourra compléter leurs I/O.
Ce décalage explique pourquoi vous voyez :
- Moyenne de charge élevée
- Faible CPU utilisateur/système
- Pourcentage iowait élevé (parfois), plus une latence disque augmentée
- Beaucoup de threads bloqués (état D) et des expirations partout
Une chose de plus : « iowait » n’est pas une ressource que vous pouvez « consommer ». C’est un symptôme. Il vous dit que les CPU sont inactifs parce que les tâches attendent des I/O. Ne combattez pas le symptôme ; trouvez l’attente.
Blague n°1 : Si votre moyenne de charge est élevée et le CPU faible, félicitations — vous avez construit une salle d’attente très coûteuse.
Faits & history vous intéressants en pleine intervention
- La moyenne de charge précède Linux. Elle vient des systèmes Unix des années 1970 ; le concept supposait que « exécutable » signifiait « veut le CPU », bien avant les piles de stockage modernes et les systèmes distribués.
- Linux compte le sommeil ininterruptible dans la charge. C’est pourquoi les blocages I/O gonflent la moyenne de charge même si les CPU ne sont pas saturés.
- « iowait » est une attribution du temps idle par CPU. Le CPU n’est pas occupé ; l’ordonnanceur dit « je pourrais exécuter du travail, mais il est bloqué sur I/O ».
- NVMe a rendu la latence visible, pas négligeable. Les dispositifs plus rapides réduisent la latence moyenne, mais la latence de queue (p95/p99) ruine toujours les files d’attente et la charge lorsque les périphériques ou le firmware se comportent mal.
- Le writeback peut transformer les lectures en problème. Les seuils de pages sales et le throttling peuvent bloquer des travaux non liés lorsque le noyau décide qu’il doit vidanger.
- Le stockage bloc cloud a souvent un comportement en rafales. Vous pouvez « manquer de performances » alors que vous avez de la capacité, et cela ressemble exactement à de l’iowait.
- Les systèmes de fichiers échangent cohérence et performance différemment. Les modes de journalisation ext4, le comportement d’allocation de XFS et les groupes de transactions ZFS créent chacun des signatures de blocage distinctes.
- Les contrôleurs RAID mentent encore. Certains caches accusent réception des écritures tôt, puis bloquent les flushs en cas de problèmes de batterie/capaciteur ou de changements de politique de writeback.
- Linux a amélioré l’observabilité. Des outils comme
iostat -x,pidstat -det le traçage basé sur BPF rendent plus difficile pour les problèmes de stockage de se cacher derrière « le CPU a l’air bien ».
Playbook de diagnostic rapide (premier/deuxième/troisième)
Voici la liste courte à exécuter quand le pager hurle et que votre cerveau fait cette chose amusante où il oublie comment fonctionnent les ordinateurs.
Premier : confirmer que ce n’est pas réellement le CPU
- Vérifier la charge, la répartition CPU et la file d’exécution vs les tâches bloquées.
- Rechercher des processus en état D et un iowait élevé.
Deuxième : confirmer la latence et l’encombrement du stockage
- Le périphérique est-il occupé (
%util) ? - La latence est-elle élevée (
await,r_await,w_await) ? - La file est-elle profonde (
aqu-sz) ?
Troisième : identifier quelle charge et quel niveau
- Quels PID effectuent des I/O ? Quels systèmes de fichiers ? Quels points de montage ?
- Est-ce du disque local, mdraid, LVM, dm-crypt, ZFS, NFS, iSCSI ou un volume cloud ?
- Est-ce du throttling writeback, de la contention de journal, ou un problème de périphérique sous-jacent ?
Une fois ces trois étapes faites, vous pouvez arrêter de débattre et commencer à réparer.
Confirmer l’iowait et le travail bloqué : commandes qui tranchent
Ci-dessous des tâches pratiques à lancer sur Ubuntu 24.04. Chacune inclut : une commande, une sortie réaliste, ce que ça signifie et la décision que vous en tirez.
Task 1: Take a snapshot of the crime scene (load + CPU breakdown)
cr0x@server:~$ uptime
14:22:08 up 36 days, 6:11, 2 users, load average: 18.42, 17.90, 16.77
Signification : La charge est très élevée pour la plupart des serveurs. Cela ne vous dit pas pourquoi.
Décision : Vérifier immédiatement si la charge est une file d’exécution (CPU) ou des tâches bloquées (I/O).
Task 2: Use top like an adult (look at wa, not just %CPU)
cr0x@server:~$ top -b -n 1 | head -n 15
top - 14:22:13 up 36 days, 6:11, 2 users, load average: 18.42, 17.90, 16.77
Tasks: 612 total, 9 running, 168 sleeping, 0 stopped, 435 zombie
%Cpu(s): 3.1 us, 1.2 sy, 0.0 ni, 63.9 id, 31.6 wa, 0.0 hi, 0.2 si, 0.0 st
MiB Mem : 64221.1 total, 1234.8 free, 40211.4 used, 22774.9 buff/cache
MiB Swap: 8192.0 total, 8100.0 free, 92.0 used. 18920.2 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
18412 postgres 20 0 4267120 2.1g 12536 D 1.3 3.4 12:48.21 postgres
19344 www-data 20 0 621432 31420 8920 D 0.7 0.0 0:38.02 php-fpm
2228 root 20 0 0 0 0 I 0.3 0.0 9:12.33 kworker/u16:2
Signification : wa à ~30% est un panneau publicitaire : les CPU attendent des I/O. Remarquez aussi les processus en état D.
Décision : Passer de « qui utilise le CPU ? » à « qu’est-ce qui est bloqué sur I/O ? »
Task 3: Measure run queue vs blocked tasks (vmstat)
cr0x@server:~$ vmstat 1 5
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
7 38 94208 126412 81200 23110240 0 0 812 9440 3221 6110 4 1 63 32 0
5 41 94208 125980 81200 23109988 0 0 640 10320 3199 5902 3 1 62 34 0
8 44 94208 124992 81200 23109012 0 0 712 11088 3340 6408 3 1 60 36 0
6 39 94208 125220 81200 23108211 0 0 540 9720 3101 6055 3 1 64 32 0
7 45 94208 125104 81200 23107010 0 0 690 10840 3368 6530 4 1 61 34 0
Signification : La colonne b (bloqué) est énorme comparée à r (exécutable). C’est une pression classique d’attente I/O.
Décision : Aller voir les métriques de latence et d’encombrement au niveau du périphérique.
Task 4: Confirm disk saturation and latency (iostat -x)
cr0x@server:~$ iostat -x 1 3
Linux 6.8.0-41-generic (server) 12/28/2025 _x86_64_ (16 CPU)
avg-cpu: %user %nice %system %iowait %steal %idle
3.2 0.0 1.3 31.4 0.0 64.1
Device r/s w/s rKB/s wKB/s rrqm/s wrqm/s r_await w_await aqu-sz rareq-sz wareq-sz svctm %util
nvme0n1 22.0 340.0 1800.0 93200.0 0.1 12.0 9.80 144.20 52.10 81.8 274.1 2.1 99.2
dm-0 21.8 338.9 1792.0 93140.0 0.0 0.0 10.10 146.90 52.30 82.2 274.9 0.0 0.0
Signification : %util ~99% indique que le périphérique est saturé ou constamment occupé. w_await ~144ms est douloureux pour toute application transactionnelle. aqu-sz ~52 signifie une file profonde : les requêtes s’accumulent.
Décision : Identifier qui écrit et pourquoi ; décider de brider la charge, déplacer la charge, ou corriger le chemin de stockage.
Task 5: Find which processes are doing I/O (pidstat -d)
cr0x@server:~$ pidstat -d 1 5
Linux 6.8.0-41-generic (server) 12/28/2025 _x86_64_ (16 CPU)
14:22:41 UID PID kB_rd/s kB_wr/s kB_ccwr/s iodelay Command
14:22:42 999 18412 0.00 18240.00 2400.00 11234 postgres
14:22:42 33 19344 0.00 1220.00 180.00 1320 php-fpm
14:22:42 0 1451 0.00 9800.00 0.00 2100 systemd-journald
14:22:42 UID PID kB_rd/s kB_wr/s kB_ccwr/s iodelay Command
14:22:43 999 18412 0.00 19010.00 2600.00 11620 postgres
14:22:43 0 1451 0.00 9400.00 0.00 2050 systemd-journald
Signification : postgres et journald sont de gros écrivains, et iodelay est élevé (temps d’attente I/O). kB_ccwr/s suggère du writeback/écritures en cache qui devront être flushées ensuite.
Décision : Examiner les modèles d’écriture de la base de données et du journal ; envisager de limiter le débit des logs, changer la destination des logs, ajuster les paramètres de checkpoint DB ou déplacer le WAL.
Task 6: Confirm lots of tasks stuck in D state (ps)
cr0x@server:~$ ps -eo state,pid,comm,wchan:32 --sort=state | head -n 20
D 18412 postgres io_schedule
D 19344 php-fpm io_schedule
D 20488 nginx io_schedule
D 20501 nginx io_schedule
D 20512 nginx io_schedule
D 22110 rsyslogd io_schedule
R 9123 top -
R 9130 ps -
S 1451 systemd-journald ep_poll
S 1680 sshd do_select
Signification : Plusieurs processus applicatifs bloqués dans io_schedule. Ils attendent la complétion d’I/O.
Décision : Considérer ceci comme un incident lié au chemin de stockage sauf preuve du contraire.
Task 7: Map I/O back to filesystems and mount options (findmnt)
cr0x@server:~$ findmnt -D
SOURCE FSTYPE OPTIONS TARGET
/dev/dm-0 ext4 rw,relatime,errors=remount-ro /
/dev/nvme0n1p2 ext4 rw,relatime /var/lib/postgresql
tmpfs tmpfs rw,nosuid,nodev /run
tmpfs tmpfs rw,nosuid,nodev /dev/shm
Signification : Les données Postgres sont sur /dev/nvme0n1p2, root est sur dm-0. Si iostat montre que nvme0n1 est saturé, cela concorde.
Décision : Se concentrer d’abord sur le périphérique NVMe et le schéma d’écriture de la base.
Task 8: Check filesystem-level pressure (df, inodes, and journal symptoms)
cr0x@server:~$ df -hT /var/lib/postgresql
Filesystem Type Size Used Avail Use% Mounted on
/dev/nvme0n1p2 ext4 1.8T 1.6T 120G 94% /var/lib/postgresql
Signification : 94% d’utilisation n’est pas une catastrophe instantanée, mais c’est un risque connu : l’allocation devient plus difficile, la fragmentation augmente, et certains systèmes de fichiers se comportent mal près de la saturation.
Décision : Prévoir de libérer de l’espace ou d’étendre le système de fichiers. En attendant, poursuivre l’investigation — cela seul n’explique pas un await d’écriture à 150ms, mais ça contribue.
Task 9: Check kernel logs for disk resets/timeouts
cr0x@server:~$ sudo journalctl -k -n 30 --no-pager
Dec 28 14:18:02 server kernel: nvme nvme0: I/O 672 QID 4 timeout, completion polled
Dec 28 14:18:02 server kernel: nvme nvme0: Abort status: 0x371
Dec 28 14:18:03 server kernel: nvme nvme0: resetting controller
Dec 28 14:18:06 server kernel: nvme nvme0: controller reset complete
Dec 28 14:18:11 server kernel: EXT4-fs (nvme0n1p2): Delayed block allocation failed for inode 262401 at logical offset 918234
Dec 28 14:18:12 server kernel: blk_update_request: I/O error, dev nvme0n1, sector 1819238480 op 0x1:(WRITE) flags 0x0 phys_seg 1 prio class 0
Signification : Réinitialisations de contrôleur et erreurs I/O. Ce n’est pas « l’app qui est lente ». C’est « le stockage est malade ». Les échecs d’allocation ext4 suggèrent aussi une pression et/ou des erreurs sous-jacentes.
Décision : Escalader immédiatement vers la santé matérielle/volume cloud ; démarrer des mesures d’atténuation (basculement, mode lecture seule, réduction de la charge d’écriture) pendant l’investigation.
Task 10: Check SMART/NVMe health signals
cr0x@server:~$ sudo nvme smart-log /dev/nvme0 | head -n 20
Smart Log for NVME device:nvme0 namespace-id:ffffffff
critical_warning : 0x00
temperature : 63 C
available_spare : 100%
available_spare_threshold : 10%
percentage_used : 89%
data_units_read : 12,349,221
data_units_written : 81,442,109
host_read_commands : 902,112,441
host_write_commands : 5,110,992,120
controller_busy_time : 145,221
power_cycles : 33
power_on_hours : 14,880
unsafe_shutdowns : 7
media_errors : 18
num_err_log_entries : 18
Signification : percentage_used à 89% et des media_errors non nuls sont un fort indice que le disque vieillit ou est en train de faillir. Pas définitif seul, mais combiné aux timeouts/réinitialisations c’est accablant.
Décision : Remplacer/évacuer le périphérique (ou déplacer la charge) plutôt que de bricoler autour d’un disque en fin de vie.
Task 11: Verify whether memory pressure is triggering writeback storms
cr0x@server:~$ cat /proc/meminfo | egrep 'Dirty|Writeback|MemFree|MemAvailable|Buffers|Cached'
MemFree: 126412 kB
MemAvailable: 19374132 kB
Buffers: 83192 kB
Cached: 22640480 kB
Dirty: 1562040 kB
Writeback: 81220 kB
Signification : Les données sales sont ~1.5GB ; pas énorme sur une machine à 64GB. MemAvailable est sain. Donc c’est moins probablement « la mémoire force un writeback brutal », et plus probablement « la latence du périphérique est élevée ».
Décision : Ne pas modifier à la légère les paramètres sysctl de writeback pour l’instant. Corriger le chemin de stockage d’abord.
Task 12: Look for direct evidence of block-layer queueing
cr0x@server:~$ cat /sys/block/nvme0n1/queue/nr_requests
1023
Signification : La file du périphérique permet beaucoup de requêtes en attente. Ça peut être bon pour le débit, mauvais pour la latence si le périphérique est déjà surchargé (la théorie des files d’attente n’est pas une discipline basée sur les impressions).
Décision : Si c’est un service sensible à la latence, envisager de réduire la concurrence côté application ou base de données d’abord ; ne pas bidouiller les files noyau sans mesurer l’impact.
Task 13: Check IO scheduler and multipath/dm stack quickly
cr0x@server:~$ cat /sys/block/nvme0n1/queue/scheduler
[none] mq-deadline kyber bfq
Signification : NVMe utilise couramment none (alias pas de scheduler traditionnel) avec multiqueue. C’est correct. Si vous voyez une pile étrange (dm-crypt, dm-thin, multipath), en tenir compte dans votre modèle mental.
Décision : Si c’est du stockage cloud/virtualisé, vérifier les limites de la couche de disque virtuelle ; changer de scheduler ne corrigera pas l’épuisement de crédits de rafale ou le throttling du backend.
Task 14: Correlate with sar to see if it’s chronic or acute
cr0x@server:~$ sar -u 1 3
Linux 6.8.0-41-generic (server) 12/28/2025 _x86_64_ (16 CPU)
14:23:41 CPU %user %nice %system %iowait %steal %idle
14:23:42 all 3.05 0.00 1.22 32.10 0.00 63.63
14:23:43 all 2.88 0.00 1.11 30.84 0.00 65.17
14:23:44 all 3.18 0.00 1.30 31.66 0.00 63.86
Average: all 3.04 0.00 1.21 31.53 0.00 64.22
Signification : l’iowait est constamment élevé dans cette fenêtre. Si vous avez des données historiques sar, vous pouvez comparer au « normal » et identifier quand cela a commencé.
Décision : Si cela a commencé après un changement (déploiement, mise à jour du noyau, migration de stockage), revenir en arrière ou isoler. Si c’est progressif, suspecter la capacité et l’usure.
Task 15: If it’s network storage, check the obvious (mount type and RPC stats)
cr0x@server:~$ mount | egrep 'type nfs|type cifs' || true
cr0x@server:~$ nfsstat -c 1 2
Client rpc stats:
calls retrans authrefrsh
12433 118 0
Client nfs v4:
null read write commit open
0 812 9340 220 14
Signification : Des retransmissions indiquent un problème réseau ou côté serveur. Pour des charges NFS, l’iowait peut être « disque » mais être en réalité « réseau + disque distant + files d’attente serveur ».
Décision : Si les retransmissions augmentent, traiter comme un incident de stockage distribué : vérifier les pertes réseau, la charge du serveur NFS et la latence du backend de stockage.
Task 16: Confirm if systemd/journald is amplifying the pain
cr0x@server:~$ sudo journalctl --disk-usage
Archived and active journals take up 6.7G in the file system.
Signification : De gros journaux plus des services bavards peuvent générer des écritures soutenues. Habituellement pas suffisant pour saturer un NVMe seul, mais pendant un incident de stockage ça peut aggraver la situation.
Décision : Pendant l’atténuation, réduire la verbosité des logs ou expédier les logs ailleurs. Ne pas supprimer les journaux aveuglément en incident sauf accord sur le compromis.
Trouver le goulot : disque, système de fichiers, contrôleur, stockage réseau ou noyau
Étape 1 : Ne pas faire confiance à un seul indicateur — corréler trois éléments
Pour affirmer avec confiance que la charge est due à l’iowait, vous voulez un triangle de preuves :
- Preuve de l’ordonnanceur : moyenne de charge élevée + beaucoup de processus en état D (
ps,vmstat) - Attribution CPU : iowait significatif (
top,sar -u) - Preuve du périphérique : latence élevée + file d’attente (
iostat -x)
Si les trois s’alignent, cessez de discuter avec les graphiques. Commencez à réparer le chemin de stockage.
Étape 2 : Décidez si vous avez un problème de débit ou de latence
Ça compte parce que la correction est différente :
- Problème de latence : les awaits sont élevés, la file est profonde, %util peut être élevé. Les applications expirent ; les BDs sont tristes ; les requêtes web s’entassent.
- Plafond de débit : les awaits sont modérés mais vous poussez un IO soutenu énorme. Les utilisateurs peuvent voir des tâches en batch plus lentes, pas nécessairement des expirations.
Pour l’incident « charge élevée, CPU faible », c’est généralement de la latence et de l’encombrement.
Étape 3 : Déterminez si ce sont des lectures, écritures ou flushs
iostat -x distingue r_await et w_await. Ça importe parce que :
- Des pics de w_await se corrèlent souvent avec des commits de journal, des patterns WAL/fsync, la politique de cache du contrôleur, ou le throttling d’un volume cloud.
- Des pics de r_await suggèrent des ratés de cache, une amplification d’I/O aléatoire, ou un périphérique incapable de suivre l’ensemble de travail.
Étape 4 : Identifier la couche qui ajoute la douleur
Sur Ubuntu, la pile I/O peut inclure : système de fichiers → LVM → dm-crypt → mdraid → périphérique, plus éventuellement le stockage réseau. Chaque couche peut multiplier la latence sous charge.
Moyens rapides pour cartographier :
lsblk -fmontre l’empilement device-mapper.dmsetup ls --treele rend explicite.multipath -ll(si utilisé) montre la santé des chemins.
cr0x@server:~$ lsblk -o NAME,TYPE,FSTYPE,SIZE,MOUNTPOINT
NAME TYPE FSTYPE SIZE MOUNTPOINT
nvme0n1 disk 1.8T
├─nvme0n1p1 part vfat 512M /boot/efi
├─nvme0n1p2 part ext4 1.6T /var/lib/postgresql
└─nvme0n1p3 part crypto 200G
└─dm-0 crypt ext4 200G /
Signification : Le root est chiffré via dm-crypt ; les données DB sont sur ext4 nu partition 2.
Décision : Si le volume DB est le périphérique chaud, ne vous laissez pas distraire par le tuning dm-crypt du root.
Étape 5 : Comprendre le mode d’échec « l’état D ne meurt pas »
Quand des threads sont coincés en sommeil ininterruptible, vous ne pouvez pas toujours les tuer. Le noyau attend la complétion d’I/O ; les signaux n’aident pas. Si le périphérique ou le chemin sous-jacent est bloqué, les processus peuvent rester coincés jusqu’à la récupération du périphérique — ou jusqu’à un reboot.
C’est pourquoi « redémarrer le service » ne fonctionne parfois pas. Les anciens processus sont toujours bloqués, les nouveaux se bloquent aussi, et vous ajoutez maintenant du chaos.
Blague n°2 : Envoyer SIGKILL à un processus en état D, c’est comme crier sur une barre de chargement — satisfaisant émotionnellement, inutile opérationnellement.
Étape 6 : Utilisez une citation pour guider votre comportement en incident
Werner Vogels (idée paraphrasée) : « Tout échoue, tout le temps — concevez et opérez comme si la défaillance était normale. »
Les incidents d’iowait semblent mystérieux parce qu’ils ne sont « pas CPU ». Mais ils sont normaux : les disques bloquent, les réseaux dérivent, les contrôleurs réinitialisent, et les volumes cloud limitent. Construisez détection et runbooks en conséquence.
Trois mini-histoires d’entreprise (qui restent en tête)
Mini-histoire 1 : L’incident causé par une mauvaise hypothèse
Ils avaient un service de paiements sur Ubuntu, des graphiques CPU sains, et une montée soudaine de la moyenne de charge qui a déclenché les alarmes de « scaling ». L’astreignant a supposé que c’était un pic de trafic et a ajouté deux instances. La charge est restée élevée. Les requêtes échouaient toujours.
Ils ont ensuite fait le classique : grep dans les logs applicatifs, trouvé « database connection timeout » et déclaré la base « lente ». Ils ont redémarré la DB. Ça n’a rien changé. Pire : le redémarrage a pris une éternité car le processus DB était coincé en état D en attente du disque, et les hooks d’arrêt ont bloqué.
Finalement quelqu’un a lancé iostat -x et a vu des awaits d’écriture de centaines de millisecondes et %util au maximum. Le stockage était un volume bloc cloud. Ce n’était pas un problème d’espace ; c’était un épuisement des IOPS garantis dû à un changement de configuration fait des semaines plus tôt, plus un pic de logs d’audit très écrivant qui a brûlé le crédit de rafale.
La mauvaise hypothèse était subtile : « charge élevée = pression CPU ». Ils l’ont traitée comme un problème CPU et ont autoscalé. Mais l’iowait ne se résout pas en ajoutant des instances quand chaque instance rencontre la même limite de stockage (backend partagé) ou le même pattern d’écriture (amplification).
Correction : revenir sur le changement de logging, réduire la fréquence d’écriture, et temporairement déplacer le WAL/logs vers un niveau de volume plus rapide. Ce n’est qu’alors que le système est redevenu stable. Le postmortem a été clair : ils avaient des alarmes pour la saturation CPU mais aucune pour la latence disque. Le pager hurlait, les tableaux de bord chuchotaient.
Mini-histoire 2 : L’optimisation qui a échoué
Une équipe plateforme voulait des déploiements plus rapides. Ils ont déplacé les couches d’images de conteneurs et les caches de build sur un montage NFS partagé pour « dédupliquer le stockage » et « simplifier les backups ». C’était joli sur le diagramme. Tout le monde a applaudi. Puis est arrivé le premier grand jour de release.
Les nœuds ont montré une charge élevée, mais top n’affichait pas d’utilisation CPU. Des pods ont échoué aux checks de readiness. Les ingénieurs ont chassé Kubernetes scheduling, DNS et des quotas « voisin bruyant » CPU. Pendant ce temps, le backend du serveur NFS faisait des écritures synchrones pour des opérations riches en métadonnées et se faisait écraser par des milliers d’opérations sur de petits fichiers.
L’optimisation a échoué car elle a concentré les I/O aléatoires de métadonnées. Le chemin de stockage réseau a amplifié la latence de queue : caches clients, files serveur, disque backend, et retransmissions occasionnelles quand le réseau était occupé. Chaque couche était « correcte » en moyenne. Ensemble, elles formaient une usine à latence.
La récupération n’était pas compliquée : remettre les caches de build sur NVMe local de chaque nœud et traiter le montage NFS partagé comme dépôt d’artefacts, pas comme scratchpad. Les « économies » sur la capacité de stockage avaient été payées en temps d’incident, perte de confiance des développeurs et beaucoup d’autoscaling CPU inutile.
Mini-histoire 3 : La bonne pratique ennuyeuse qui a sauvé la mise
Une entreprise exécutait un pipeline d’analytics client sur quelques gros hôtes Ubuntu. Rien de glamour : jobs batch, une base, et une queue de messages. L’équipe SRE avait une habitude pas sexy : ils enregistraient chaque semaine des latences de référence pour chaque périphérique de stockage — résumés iostat -x, await maximal, et une note de ce qu’était le « normal ».
Un après-midi, la moyenne de charge a commencé à grimper et l’équipe applicative a signalé « le CPU semble correct ». L’astreignant a ouvert le document de référence et a immédiatement vu le décalage : le w_await normal était en millisecondes à un chiffre ; maintenant il était à trois chiffres. Pas de débat. Pas de « peut-être le code ». C’était le stockage jusqu’à preuve du contraire.
Ils ont vérifié les logs noyau et trouvé des réinitialisations de contrôleur occasionnelles. Pas suffisantes pour planter le système, mais suffisantes pour ruiner la latence. Parce qu’ils avaient une référence, ils n’ont pas perdu une heure à prouver que « c’est anormal ». Ils le savaient déjà.
L’atténuation a été tout aussi ennuyeuse : basculer la DB vers une standby sur un matériel différent, drainer les workers batch, et planifier une fenêtre de maintenance pour remplacer le périphérique suspect. Le rapport d’incident n’était pas excitant. C’est le point. Les bonnes opérations ressemblent souvent à quelqu’un qui refuse d’être surpris deux fois par la même classe de panne.
Erreurs courantes : symptôme → cause racine → correction
C’est ici que les équipes perdent du temps. L’astuce est de traiter les symptômes comme des indices, pas comme des diagnostics.
1) Symptom: High load average, low CPU, lots of timeouts
Cause racine : threads bloqués en état D à cause de la latence ou des erreurs de stockage.
Correction : Confirmer avec vmstat/ps/iostat -x. Atténuer en réduisant la charge d’écriture, en déplaçant les chemins chauds (WAL/logs) vers un stockage plus rapide, ou en basculant. Examiner la santé du périphérique et les logs noyau.
2) Symptom: iowait is low, but load is high and apps hang
Cause racine : la charge inclut l’état D ; le pourcentage iowait peut être trompeur sur des systèmes multicœurs ou quand les tâches bloquées ne sont pas échantillonnées comme attendu.
Correction : Compter directement les tâches en état D (ps) et vérifier les métriques du bloc (iostat -x). Ne pas dépendre uniquement d’iowait.
3) Symptom: %util is 100% but throughput is not high
Cause racine : le périphérique est occupé à exécuter des opérations lentes (haute latence), souvent des écritures aléatoires, des workloads fsync-intensifs, ou des retries dus à des erreurs.
Correction : Identifier le pattern I/O (iostat -x, pidstat -d). Réduire la fréquence des fsync là où c’est sûr, déplacer les write-ahead logs, ou corriger les limites hardware/volume cloud.
4) Symptom: Everything gets worse after “tuning” kernel writeback
Cause racine : pousser les ratios de données sales trop haut augmente la rafale ; les storms de flush deviennent plus grandes et plus pénalisantes quand le périphérique ne suit pas.
Correction : Revenir aux valeurs par défaut sauf si vous avez une raison mesurée. Préférer lissage au niveau applicatif (regroupement, limitation) plutôt que la roulette du noyau.
5) Symptom: Restarting services doesn’t help; shutdown hangs
Cause racine : processus coincés en état D attendant la complétion d’I/O ; ils ne meurent pas tant que l’I/O ne revient pas.
Correction : Corriger le blocage de stockage sous-jacent ou redémarrer en dernier recours après avoir confirmé que vous n’empirez pas la récupération (surtout pour les erreurs de stockage). Pour une BD, privilégier le basculement plutôt que de redémarrer le primaire.
6) Symptom: Only one mount point is slow, others fine
Cause racine : un volume/montage spécifique est saturé ou en erreur ; pas « tout l’hôte ».
Correction : Cartographier les montages vers les périphériques (findmnt, lsblk). Déplacer cette charge, ajuster les quotas, ou réparer/remplacer ce périphérique.
7) Symptom: Bursty slowness at predictable intervals
Cause racine : checkpointing, commits de journal, syncs ZFS txg, rotations de logs ou snapshots de sauvegarde périodiques.
Correction : Aligner les horaires, réduire la concurrence, échelonner les jobs, tuner les checkpoints DB avec précaution, et isoler logs/WAL des données quand possible.
8) Symptom: High iowait on a VM, but disks “look fine” in the guest
Cause racine : contention côté hôte, throttling ou effets de voisin bruyant ; les métriques invitées peuvent masquer la vraie file d’attente.
Correction : Vérifier les métriques de l’hyperviseur/cloud et les limites du volume. Les outils en-guest montrent le symptôme ; ils peuvent ne pas montrer la cause.
Listes de vérification / plan étape par étape
Checklist A: 10-minute triage (do this before changing anything)
- Enregistrer
uptime, un snapshottopetvmstat 1 5. - Exécuter
iostat -x 1 3et sauvegarder la sortie. - Lister les processus I/O principaux avec
pidstat -d 1 5. - Compter les tâches en état D :
ps -eo state | grep -c '^D'. - Vérifier les logs noyau pour erreurs de stockage :
journalctl -k -n 100. - Cartographier les montages vers les périphériques :
findmnt -Detlsblk. - Vérifier l’espace libre et la pression d’inodes sur les montages chauds :
df -hT,df -i.
Checklist B: Mitigation options (choose the least risky that works)
- Réduire rapidement la charge d’écriture : baisser la verbosité des logs, mettre en pause les jobs batch, ralentir l’ingestion.
- Protéger la base : si WAL/checkpoints posent problème, déplacer le WAL vers un périphérique plus rapide/dédié si disponible, ou basculer.
- Isoler les processus bruyants : arrêter ou brider le plus gros émetteur I/O (avec précaution — ne pas tuer la seule chose garantissant la cohérence des données).
- Déplacer le trafic : drainer le nœud des load balancers ; basculer vers des réplicas sains.
- Dernier recours : redémarrer seulement après avoir confirmé que vous n’empirez pas la récupération (surtout pour des erreurs de stockage).
Checklist C: Root cause workflow (after stabilizing)
- Déterminer si la latence est dominée par les lectures ou les écritures (
iostat -x). - Corréler avec les déploiements/changes (
journalctl, votre journal des changements). - Confirmer la santé hardware/volume (NVMe SMART, logs RAID, événements de volume cloud).
- Vérifier le comportement du système de fichiers près de la saturation, le mode journal, et les options de montage.
- Évaluer les changements de pattern I/O applicatif (nouveaux logs, nouveaux index, vacuum, jobs de compaction).
- Ajouter de l’alerte :
awaitdisque, profondeur de file, et compte D-state — pas juste CPU.
FAQ
1) Why does load average go up if CPU is idle?
Parce que la charge Linux inclut les tâches en sommeil ininterruptible (D), typiquement en attente d’I/O. Elles n’utilisent pas le CPU, mais elles ne « progressent » pas, donc elles comptent dans la charge.
2) Is high iowait always bad?
Non. Un iowait élevé pendant un job de lecture/écriture massif connu peut être acceptable si les services sensibles à la latence sont isolés. C’est mauvais quand ça se corrèle avec des expirations côté utilisateur et une augmentation des tâches en état D.
3) Why is iowait sometimes low even when storage is the problem?
iowait est une attribution du temps idle CPU, pas une mesure directe de la latence disque. Sur des systèmes multicœurs, quelques threads bloqués ne font pas forcément bouger beaucoup le pourcentage global. De plus, l’échantillonnage et les outils peuvent masquer des pics courts. Comptez les tâches en état D et mesurez la latence du périphérique directement.
4) What’s the fastest single command to prove it’s storage?
iostat -x 1 3. Si vous voyez un await élevé, une aqu-sz élevée et un %util élevé sur un périphérique pertinent, vous avez une preuve solide. Associez-le aux processus en état D pour une démonstration implacable.
5) Does high %util mean the disk is at maximum throughput?
Pas nécessairement. Ça peut signifier que le périphérique est occupé à traiter des opérations lentes (haute latence) ou des retries/erreurs. Le débit peut être moyen pendant que %util est saturé.
6) How do I tell if it’s the disk or the filesystem/journal?
Si le périphérique brut montre une latence élevée, commencez par là. Si la latence du périphérique brut est correcte mais qu’un montage particulier est lent, regardez le comportement au niveau du système de fichiers : contention de journal, allocation près de la saturation, options de montage et patterns fsync applicatifs.
7) Why do processes in D state ignore kill -9?
Parce qu’ils sont coincés dans un appel noyau ininterruptible, typiquement en attente de complétion d’I/O. Le signal est en attente, mais le processus ne peut le traiter tant que l’appel noyau ne retourne pas.
8) If it’s a cloud VM, what should I suspect first?
Limites de performance du volume, épuisement des crédits de rafale, effets de voisin bruyant et contention côté hôte. Les outils invités montrent le symptôme ; la cause peut être hors de la VM.
9) Can swapping cause this exact pattern?
Oui. Un swap intensif génère de la pression I/O et des tâches bloquées. Mais vous verrez généralement de l’activité swap dans vmstat (si/so) et une baisse de MemAvailable. Ne pas deviner — mesurer.
10) What alerts should I add so I don’t get surprised again?
Au minimum : par-périphérique await (lecture/écriture), profondeur de file (aqu-sz), compte de tâches en état D, remplissage système de fichiers, et taux d’erreur noyau stockage. Le CPU seul ment dans cette histoire.
Conclusion : next steps that actually move the needle
Quand Ubuntu 24.04 affiche une charge élevée mais « rien n’utilise le CPU », traitez cela comme un problème de travail bloqué jusqu’à preuve du contraire. Confirmez avec le comptage D-state, vmstat et iostat -x. Une fois que vous observez une latence et un encombrement élevés, arrêtez de bidouiller le CPU et commencez à travailler le chemin de stockage : santé du périphérique, réinitialisations de contrôleur, limites de volume, pression du système de fichiers et les processus spécifiques qui génèrent des écritures.
Prochaines étapes pratiques :
- Inclure le playbook de diagnostic rapide dans votre runbook on-call tel quel.
- Ajouter des alertes sur la latence disque et les tâches bloquées, pas seulement CPU et charge.
- Baseliner le
awaitnormal et la profondeur de file pour vos hôtes critiques. - Séparer les logs/WAL sensibles à la latence des données en masse quand possible.
- Si les logs noyau montrent des timeouts/réinitialisations : arrêter les tunings et commencer à migrer/remplacer le stockage. Vous ne pouvez pas sysctl-er la physique.