« Le disque est lent » est le rapport d’incident le moins utile et le plus fréquent. Il se manifeste par des expirations dans votre application, des sauvegardes qui prennent soudainement toute la nuit, ou une base de données qui commence à se comporter comme si elle faisait un travail philosophique profond entre deux écritures.
Ubuntu 24.04 embarque un noyau Linux moderne avec IO bloc multi-file et beaucoup de valeurs par défaut sensées. Pourtant, un mauvais ordonnanceur, une profondeur de file mal assortie, ou un bouton de réglage « utile » peut transformer un NVMe rapide en arroseur de latence. La solution n’est rarement mystique. C’est généralement de la mesure, quelques réglages délibérés, et l’habitude de prouver que le changement a réellement aidé.
Playbook de diagnostic rapide
Quand quelqu’un dit « disque est lent », vous ne commencez pas par changer des réglages. Vous commencez par déterminer si vous avez un problème de latence, un plafond de débit, un problème de saturation ou une simple défaillance de périphérique. Voici l’ordre qui attrape le plus d’incidents avec le moins de drame.
1) Première étape : identifiez quel type de « lent » c’est
- Pointe de latence (p99/p999 des lectures ou des fsync qui montent) : souvent file d’attente, mauvais ordonnanceur, flushs de cache, firmware/GC du périphérique, ou stockage backend.
- Plafond de débit (MB/s bloqués bas) : souvent vitesse de lien, problèmes de lanes PCIe, contraintes RAID/MD, limites des volumes cloud, ou schéma IO inadapté.
- Saturation (utilisation élevée, files longues) : souvent profondeur de file trop faible/élevée, trop d’écrivains concurrents, ou un voisin bruyant.
- Bloquages/expirations : peuvent être des erreurs de périphérique, basculements multipath, réinitialisations de contrôleur, ou douleur au niveau du système de fichiers/journal.
2) Deuxième étape : vérifiez les erreurs et réinitialisations avant de tuner
Si le noyau logge des réinitialisations NVMe ou des timeouts SCSI, votre « problème de performance » est un incident de disponibilité qui porte un masque de performance.
3) Troisième étape : mesurez la mise en file d’attente et la latence au niveau bloc
Utilisez iostat et pidstat pour voir si vous construisez des files (aqu-sz), subissez de la latence (await), ou faites juste beaucoup d’IO. Puis cartographiez cela aux réglages d’ordonnanceur et de profondeur de file.
4) Quatrième étape : validez ordonnanceur et profondeur de file selon le type de périphérique
NVMe, SSD SATA, HDD et volumes block cloud sont des bêtes différentes. Si vous les traitez pareil, ils vous puniront différemment.
5) Cinquième étape : changez une seule chose, lancez le même test, comparez les percentiles
Si vous ne pouvez pas reproduire le problème, vous ne pouvez pas prouver la correction. Ce n’est pas du cynisme ; c’est la façon d’éviter de « tuner » jusqu’à provoquer un nouvel incident.
Ce que signifie réellement « disque lent » en production
Les discussions sur les performances de stockage dérapent parce que les gens mélangent les métriques. Un développeur dit « disque lent » et entend que la requête API expire. Un admin système entend « disque lent » et vérifie les MB/s. Un spécialiste base de données veut dire latence fsync. Ils ont tous raison, et tous tort, jusqu’à ce que vous vous ancriez sur la même mesure.
Quatre métriques qui comptent (et une qui ment)
- IOPS : opérations par seconde. Utile pour IO aléatoire et petits blocs. Inutile si vous ignorez la distribution des latences.
- Débit (MB/s) : bon pour lectures/écritures en flux et gros blocs. Trompeur pour bases de données et charges riches en métadonnées.
- Latence (moyenne et percentiles) : la seule métrique que vos utilisateurs ressentent. p99 compte plus que la « moyenne ».
- File d’attente : combien de travail attend. La file d’attente est souvent là où « lent » naît.
- CPU iowait : le menteur. iowait peut être bas alors que la latence est terrible (IO asynchrone), et élevé alors que tout va bien (CPU idle, IO occupé).
Sur Ubuntu 24.04, vous êtes typiquement sur une pile bloc multi-file (blk-mq). Cela signifie plusieurs files matérielles, plusieurs chemins de soumission logiciels, et un comportement d’ordonnanceur différent des anciens jours à file unique. C’est plus rapide et plus parallèle. C’est aussi plus facile de créer un désordre si votre profondeur de file n’est pas adaptée au périphérique ou au backend.
Une citation qui devrait figurer sur chaque runbook d’astreinte :
« L’espoir n’est pas une stratégie. » — Gene Kranz
Le tuning du stockage sans mesure, c’est de l’espoir en blouse blanche.
Faits et historique intéressants (court, concret, utile)
- Linux choisissait autrefois entre CFQ, deadline et noop ; avec blk-mq, beaucoup de périphériques utilisent maintenant
none(bypass de l’ordonnanceur) oumq-deadline. - NVMe a été conçu pour des files profondes (beaucoup de commandes en vol) parce que la flash et le PCIe prospèrent avec le parallélisme.
- Les HDD détestent l’IO aléatoire non pas parce qu’ils sont « lents », mais parce qu’ils sont mécaniques : chaque seek aléatoire est un petit déplacement physique.
- L’ordonnancement deadline est devenu populaire pour les bases car il empêche l’inanition et maintient la latence de lecture sous contrôle sous charge d’écriture.
- Le comportement du cache d’écriture et des flushs a changé la donne : les SSD modernes peuvent acquitter rapidement des écritures, mais un flush forcé (fsync/fua) peut encore coûter du temps réel.
- Le tuning de la profondeur de file est devenu courant avec les SAN : trop peu gâche l’array, trop rend la latence tail insupportable par file d’attente.
- Les volumes block cloud imposent souvent des plafonds de performance (IOPS/MB/s) indépendamment de ce que votre instance peut faire, menant à un « disque lent » qui est en réalité « budget lent ».
- Le multi-file a été adopté pour s’adapter aux cœurs CPU ; un verrou global de file était un goulot d’étranglement sur les SSD rapides.
- « noop » n’était pas « sans ordonnancement » tant que « fusion minimale » ; cela avait du sens pour le matériel qui réordonne déjà efficacement.
Ordonnanceur IO sur Ubuntu 24.04 : rôle et cas où il compte
L’ordonnanceur IO est le chef de la circulation entre votre système de fichiers et votre périphérique bloc. Son travail est de décider ce qui est envoyé ensuite et dans quel ordre, et à quel point fusionner et dispatcher les requêtes.
Sur Linux moderne, surtout avec NVMe, le périphérique et le firmware font déjà beaucoup de réordonnancement. C’est pourquoi « pas d’ordonnanceur » (none) peut être le meilleur choix : le noyau s’écarte. Mais « peut être » n’est pas « l’est toujours ».
Ordonnanceurs que vous verrez couramment
- none : contourne effectivement l’ordonnancement pour les périphériques blk-mq ; s’appuie sur le matériel pour gérer l’ordre. Souvent meilleur pour NVMe et arrays haut de gamme.
- mq-deadline : version multi-file de deadline ; vise à borner la latence et à prévenir l’inanition. Souvent un bon choix par défaut pour SSD SATA et charges mixtes.
- kyber : cible basse latence en contrôlant le dispatch basé sur des latences cibles. Peut aider sur certains périphériques ; peut aussi vous embrouiller si vous ne mesurez pas correctement.
- bfq : orienté équité, souvent pour postes de travail ; peut être utile pour la latence interactive sur média rotatif, mais ce n’est pas mon premier choix pour des serveurs sauf raison spécifique.
Quand l’ordonnanceur importe
Si votre charge est principalement IO séquentiel (gros reads/writes en flux), le choix de l’ordonnanceur ne change souvent pas grand-chose. Si votre charge est mixte avec lectures/écritures aléatoires sous contention (bases, hôtes VM, Ceph OSDs), le comportement de l’ordonnanceur et de la mise en file peut dominer la latence tail.
Si vous êtes sur un contrôleur RAID matériel ou un SAN qui fait déjà un ordonnancement sophistiqué, ajouter une couche d’ordonnanceur lourde peut revenir à demander à deux chefs de projet de « coordonner » le même sprint. Vous aurez plus de réunions, pas plus de fonctionnalités.
Profondeur de file : le levier caché derrière le débit et la latence tail
La profondeur de file est combien de requêtes IO vous autorisez à être en vol simultanément. Plus de profondeur augmente le parallélisme et peut améliorer le débit et les IOPS. Trop de profondeur augmente le délai de mise en file et dégrade la latence, surtout au tail.
Trois files que vous devez arrêter de confondre
- Concurrence applicative : combien de threads, tâches async, ou processus émettent de l’IO.
- Profondeur de file du noyau bloc : capacité de la couche bloc à garder et dispatcher des requêtes (pensez
nr_requestset limites par périphérique). - Profondeur de file du périphérique ou du backend : taille de file NVMe, profondeur HBA, limites LUN SAN, limites de volume cloud.
La profondeur de file « correcte » dépend de la charge et du périphérique. Les bases veulent souvent une latence bornée plus que le débit maximal. Les sauvegardes veulent du débit et tolèrent la latence. Les hôtes VM veulent les deux, d’où les disputes.
Blague n°1 : Le tuning de la profondeur de file, c’est comme l’expresso — trop peu et rien ne se passe, trop et personne ne dort, y compris votre array de stockage.
Ce qui se passe quand la profondeur est mal réglée
- Trop faible : vous verrez une faible utilisation et un débit médiocre ; le périphérique pourrait faire plus mais n’a pas assez de travail parallèle.
- Trop élevée : vous verrez une forte utilisation, un
aqu-szélevé, unawaiten hausse, et des p99 laids. Vous n’êtes pas « occupé » ; vous êtes congestionné.
Volumes cloud et SAN : la profondeur est une politique, pas la physique
Avec des volumes du type EBS, vous pouvez avoir un instance store NVMe local rapide et être quand même plafonné par la politique du périphérique réseau. C’est pourquoi changer l’ordonnanceur sur l’invité aide parfois moins que changer la classe de volume, les IOPS provisionnés, ou le type d’instance.
Vérification : comment benchmarker sans se mentir
Si vous voulez vérifier des améliorations, vous avez besoin d’un test qui correspond à votre pattern IO de production et d’une méthode qui contrôle le cache, le warm-up et la concurrence.
Règles du benchmarking du stockage qui vous gardent employé
- Choisissez un pattern IO : aléatoire vs séquentiel, lecture vs écriture, taille de bloc, sync vs async, fréquence de fsync.
- Utilisez les percentiles : la latence moyenne est une histoire du soir. p95/p99 est votre panne.
- Contrôlez le cache : le cache page peut rendre les lectures magiques. Direct IO peut rendre les systèmes de fichiers pires que la réalité. Choisissez délibérément.
- Séparez tests périphérique brut et tests système de fichiers : testez le bloc brut quand vous suspectez ordonnanceur/profondeur ; testez le système de fichiers quand vous suspectez journalisation ou options de montage.
- Lancez assez longtemps : GC SSD et throttling thermique peuvent apparaître après des minutes, pas des secondes.
- Une chose à la fois : ce n’est pas une émission de cuisine.
Votre meilleur ami ici est fio. Pas parce qu’il est sophistiqué, mais parce qu’il est explicite : vous pouvez décrire la charge et obtenir des stats de latence détaillées. Associez-le à iostat et aux logs noyau, et vous pouvez dire si votre « amélioration » est réelle.
Tâches pratiques : commandes, signification des sorties et décisions
Ce sont de vraies tâches que vous pouvez exécuter sur Ubuntu 24.04. Chacune inclut quoi regarder et quelle décision prendre. Exécutez-les en root si nécessaire. Si vous êtes en production, faites d’abord les contrôles à faible impact.
Tâche 1 : identifier les périphériques bloc réels et la topologie
cr0x@server:~$ lsblk -e7 -o NAME,TYPE,SIZE,ROTA,TRAN,MODEL,SERIAL,MOUNTPOINTS
NAME TYPE SIZE ROTA TRAN MODEL SERIAL MOUNTPOINTS
nvme0n1 disk 1.8T 0 nvme Samsung SSD 980PRO S64DNE0R123456A
├─nvme0n1p1 part 512M 0 nvme /boot/efi
└─nvme0n1p2 part 1.8T 0 nvme /
Ce que cela signifie : ROTA=0 suggère SSD/NVMe ; TRAN vous indique si c’est nvme, sata, sas, etc. Si vous êtes en multipath ou RAID MD, vous verrez des couches différentes.
Décision : Choisissez le bon périphérique à inspecter (nvme0n1 ici). Ne touchez pas la partition en oubliant le disque sous-jacent.
Tâche 2 : vérifiez les logs noyau pour erreurs, réinitialisations, timeouts
cr0x@server:~$ sudo dmesg -T | egrep -i "nvme|scsi|blk_update_request|reset|timeout|I/O error" | tail -n 30
[Mon Dec 29 10:11:14 2025] nvme nvme0: I/O 123 QID 4 timeout, aborting
[Mon Dec 29 10:11:14 2025] nvme nvme0: Abort status: 0x371
[Mon Dec 29 10:11:15 2025] nvme nvme0: controller reset scheduled
Ce que cela signifie : Timeouts et réinitialisations ne sont pas des « opportunités de tuning ». Ce sont des incidents de fiabilité. La performance devient chaotique quand le périphérique est instable.
Décision : Si vous voyez cela, mettez le tuning en pause. Vérifiez firmware, câblage/backplane, erreurs PCIe AER, santé du contrôleur et avis du fournisseur. Si c’est cloud, ouvrez un ticket support et envisagez de déplacer le volume.
Tâche 3 : vérifiez l’ordonnanceur actuel par périphérique
cr0x@server:~$ cat /sys/block/nvme0n1/queue/scheduler
[none] mq-deadline kyber bfq
Ce que cela signifie : L’ordonnanceur entre crochets est actif (none ici). Les options disponibles suivent.
Décision : Pour NVMe, none est souvent correct. Si vous observez de la latence tail sous charge mixte, testez mq-deadline. Ne présumez pas.
Tâche 4 : vérifiez profondeur de file basique et limites de requêtes
cr0x@server:~$ for f in nr_requests read_ahead_kb rotational rq_affinity nomerges; do echo -n "$f="; cat /sys/block/nvme0n1/queue/$f; done
nr_requests=256
read_ahead_kb=128
rotational=0
rq_affinity=1
nomerges=0
Ce que cela signifie : nr_requests plafonne les requêtes mises en file au niveau bloc. Ce n’est pas la seule file, mais ça compte pour la congestion. read_ahead_kb influence le comportement de lecture séquentielle.
Décision : Si votre débit est faible et que l’utilisation n’est pas maximale, des files peu profondes peuvent être suspectes. Si votre latence est mauvaise avec forte concurrence, des files trop profondes peuvent aggraver la mise en file. Changez prudemment et mesurez.
Tâche 5 : vérifiez le nombre et la taille des files matérielles (NVMe)
cr0x@server:~$ sudo nvme id-ctrl /dev/nvme0 | egrep -i "mdts|sqes|cqes|oacs|oncs|nn|cntlid"
nn : 0x1
mdts : 0x9
cntlid: 0x0001
oacs : 0x0017
oncs : 0x001f
sqes : 0x66
cqes : 0x44
Ce que cela signifie : Les capacités NVMe façonnent la taille maximale des IO et les fonctionnalités supportées. Pas un nombre direct de « profondeur de file », mais cela vous dit si le périphérique se comporte comme un NVMe moderne.
Décision : Si les outils NVMe rapportent des anomalies, confirmez que vous n’êtes pas derrière un contrôleur présentant NVMe en mode étrange. Pour le cloud, confirmez que vous êtes réellement sur NVMe et pas virtio-blk avec un comportement différent.
Tâche 6 : observez la latence IO et la mise en file en direct avec iostat
cr0x@server:~$ iostat -x -d 1 10 nvme0n1
Linux 6.8.0-xx-generic (server) 12/29/2025 _x86_64_ (32 CPU)
Device r/s rkB/s rrqm/s %rrqm r_await rareq-sz w/s wkB/s wrqm/s %wrqm w_await wareq-sz aqu-sz %util
nvme0n1 120.0 3840.0 0.0 0.0 1.20 32.0 95.0 3040.0 0.0 0.0 8.50 32.0 1.05 32.0
Ce que cela signifie : r_await/w_await sont les latences moyennes lecture/écriture (ms). aqu-sz est la taille moyenne de la file. %util est le temps occupé (pas toujours fiable sur les périphériques rapides, mais reste un indice).
Décision : Un await élevé avec aqu-sz élevé suggère de la mise en file : vous entassez des requêtes. Un await élevé avec peu de mise en file suggère que le périphérique/backend est simplement lent par IO (ou en train de flusher).
Tâche 7 : cartographiez « qui fait de l’IO » vers une liste de processus
cr0x@server:~$ sudo pidstat -d 1 5
Linux 6.8.0-xx-generic (server) 12/29/2025 _x86_64_ (32 CPU)
10:21:01 AM UID PID kB_rd/s kB_wr/s kB_ccwr/s iodelay Command
10:21:02 AM 1001 21455 0.00 51200.00 0.00 8 postgres
10:21:02 AM 0 1892 0.00 2048.00 0.00 1 systemd-journald
Ce que cela signifie : C’est le moyen le plus rapide d’attraper une compaction qui tourne, un job de sauvegarde, ou un orage de logs.
Décision : Si le « disque lent » coïncide avec un processus qui domine les écritures, corrigez d’abord la charge (limiter le débit, planifier, déplacer) avant de toucher l’ordonnanceur.
Tâche 8 : confirmez le système de fichiers et les options de montage
cr0x@server:~$ findmnt -no SOURCE,FSTYPE,OPTIONS /
/dev/nvme0n1p2 ext4 rw,relatime,errors=remount-ro
Ce que cela signifie : Le choix du système de fichiers et les options influent sur l’IO de métadonnées, le comportement de writeback et les barrières.
Décision : Ne faites pas du cargo-cult avec les options de montage. Si quelqu’un a désactivé les barrières ou forcé data=writeback « pour la performance », considérez cela comme un incident d’intégrité des données en attente d’une panne de courant.
Tâche 9 : inspectez les règles udev et les profils tuned qui pourraient écraser les réglages
cr0x@server:~$ systemctl is-enabled tuned 2>/dev/null || true
disabled
cr0x@server:~$ grep -R "queue/scheduler" -n /etc/udev/rules.d /lib/udev/rules.d 2>/dev/null | head
/lib/udev/rules.d/60-persistent-storage.rules:...
Ce que cela signifie : Vous pouvez « régler l’ordonnanceur » manuellement et avoir udev ou un profil qui le rétablit au démarrage.
Décision : Si les réglages changent sans cesse, arrêtez de courir après des fantômes. R rendez la configuration persistante via des règles udev ou la ligne de commande du noyau où approprié, et documentez-la.
Tâche 10 : changez l’ordonnanceur temporairement (pour un test contrôlé)
cr0x@server:~$ echo mq-deadline | sudo tee /sys/block/nvme0n1/queue/scheduler
mq-deadline
cr0x@server:~$ cat /sys/block/nvme0n1/queue/scheduler
none [mq-deadline] kyber bfq
Ce que cela signifie : L’ordonnanceur a changé à chaud. C’est réversible et doit être testé sous la même charge.
Décision : Lancez votre benchmark et comparez la latence p99 et le débit. Si cela aide, rendez-le persistant. Si ça aggrave, revenez en arrière et passez à autre chose.
Tâche 11 : testez la latence et le débit du périphérique brut avec fio (direct IO)
cr0x@server:~$ sudo fio --name=randread --filename=/dev/nvme0n1 --direct=1 --ioengine=io_uring --iodepth=32 --rw=randread --bs=4k --numjobs=1 --time_based=1 --runtime=60 --group_reporting
randread: (g=0): rw=randread, bs=(R) 4096B-4096B, (W) 4096B-4096B, ioengine=io_uring, iodepth=32
fio-3.36
Starting 1 process
randread: IOPS=210k, BW=820MiB/s (860MB/s)(49.1GiB/60s)
lat (usec): min=42, max=8500, avg=145.2, stdev=60.1
clat percentiles (usec):
| 1.00th=[ 70], 5.00th=[ 85], 10.00th=[ 95], 50.00th=[ 140],
| 90.00th=[ 210], 95.00th=[ 260], 99.00th=[ 420], 99.90th=[ 1100]
Ce que cela signifie : Cela contourne le cache page et frappe le périphérique. Vous obtenez des percentiles qui reflètent réellement la latence tail.
Décision : Utilisez cela comme base pour le « comportement du périphérique ». Si le périphérique est rapide ici mais que votre appli est lente, le goulot est au-dessus de la couche bloc (système de fichiers, pattern fsync, fragmentation, concurrence applicative, stockage réseau).
Tâche 12 : testez la performance du système de fichiers (inclut métadonnées et journalisation)
cr0x@server:~$ mkdir -p /mnt/fio-test
cr0x@server:~$ sudo fio --name=fsyncwrite --directory=/mnt/fio-test --ioengine=io_uring --direct=0 --rw=write --bs=16k --numjobs=4 --iodepth=8 --fsync=1 --size=2G --group_reporting
fsyncwrite: (g=0): rw=write, bs=(R) 16384B-16384B, (W) 16384B-16384B, ioengine=io_uring, iodepth=8
fio-3.36
fsyncwrite: IOPS=9800, BW=153MiB/s (160MB/s)(8192MiB/53s)
clat percentiles (msec):
| 1.00th=[ 0.6], 5.00th=[ 0.9], 10.00th=[ 1.0], 50.00th=[ 2.1],
| 90.00th=[ 6.2], 95.00th=[ 9.5], 99.00th=[ 21.0], 99.90th=[ 45.0]
Ce que cela signifie : C’est plus proche d’un comportement « base de données » : beaucoup de points de synchronisation. La latence tail ici est ce qui tue les transactions.
Décision : Si p99/p99.9 est élevé ici, l’ordonnanceur et la profondeur de file peuvent aider, mais regardez aussi les options du système de fichiers, les réglages du journal, et les écrivains concurrents.
Tâche 13 : inspectez les périphériques multipath/DM (si applicable)
cr0x@server:~$ lsblk -o NAME,TYPE,SIZE,PKNAME,MOUNTPOINTS | head
NAME TYPE SIZE PKNAME MOUNTPOINTS
dm-0 lvm 900G /
└─mpatha mpath 900G sdb
├─sdb disk 900G
└─sdc disk 900G
Ce que cela signifie : Les couches device-mapper peuvent cacher des contraintes de profondeur de file. Tuner la mauvaise couche ne change rien.
Décision : Identifiez les périphériques feuilles et confirmez leurs limites de file et ordonnanceur. Pour les SANs, vérifiez la profondeur HBA et les réglages multipath. Si vous pouvez remarquer un basculement de chemin dans les graphiques de latence, vous avez du travail.
Tâche 14 : observez le temps IO par périphérique et les merges avec sar
cr0x@server:~$ sar -d 1 5
Linux 6.8.0-xx-generic (server) 12/29/2025 _x86_64_ (32 CPU)
10:24:01 AM DEV tps rkB/s wkB/s areq-sz aqu-sz await %util
10:24:02 AM nvme0n1 220.00 4096.00 6144.00 46.55 2.10 9.55 40.00
Ce que cela signifie : Une autre vue de la mise en file et de l’utilisation, utile pour la capture historique.
Décision : Si vous devez convaincre quelqu’un, collectez sar durant la fenêtre d’incident. Les humains croient plus facilement des graphiques que des arguments.
Tâche 15 : vérifiez le comportement de discard/TRIM (SSD) et évitez les mythes « toujours activé »
cr0x@server:~$ lsblk -D -o NAME,DISC-GRAN,DISC-MAX,DISC-ZERO
NAME DISC-GRAN DISC-MAX DISC-ZERO
nvme0n1 512B 2G 0
Ce que cela signifie : Le périphérique supporte discard. Monter avec discard ou exécuter périodiquement fstrim a de l’importance.
Décision : Préférez un fstrim périodique (via timer systemd) plutôt que discard continu pour la plupart des charges serveurs. Le discard continu peut ajouter overhead et imprévisibilité.
Tâche 16 : vérifiez que votre « amélioration » est stable (répétabilité)
cr0x@server:~$ sudo fio --name=randrw --filename=/dev/nvme0n1 --direct=1 --ioengine=io_uring --iodepth=16 --rw=randrw --rwmixread=70 --bs=4k --numjobs=2 --time_based=1 --runtime=180 --group_reporting
randrw: (g=0): rw=randrw, bs=(R) 4096B-4096B, (W) 4096B-4096B, ioengine=io_uring, iodepth=16
fio-3.36
randrw: IOPS=120k, BW=469MiB/s (492MB/s)(84.1GiB/180s)
clat percentiles (usec):
| 95.00th=[ 600], 99.00th=[ 1200], 99.90th=[ 4200]
Ce que cela signifie : Un run mixte plus long attrape GC, throttling et effets de mise en file.
Décision : Si les résultats varient fortement d’un run à l’autre, vous n’avez pas un problème de tuning ; vous avez un problème d’environnement (contention, throttling, variabilité backend, ou limites thermiques).
Trois micro-histoires du monde de l’entreprise (anonymisées, plausibles, techniquement exactes)
1) Incident causé par une mauvaise hypothèse : « NVMe est toujours rapide »
Une équipe a migré un service sensible à la latence vers des serveurs plus récents avec des disques NVMe. Le ticket disait « nous avons mis à jour le stockage, le disque ne peut pas être le problème ». Ils avaient aussi un nouveau déploiement, donc la narration était presque écrite : cela doit être le code applicatif.
Le premier indice était que la latence moyenne semblait correcte, mais le service subissait des expirations périodiques de requêtes. Sur l’hôte, iostat montrait des rafales où aqu-sz bondissait et w_await grimpait en dizaines. Ce n’était pas constant ; ça arrivait par vagues. Le périphérique était « rapide » jusqu’à ce qu’il ne le soit plus.
L’hypothèse « NVMe = faible latence » cachait la vraie histoire : la charge était écrivante avec des syncs fréquents, et le modèle de disque avait une petite cache SLC. Sous écritures soutenues, le disque basculait vers un état plus lent et le GC entrait en jeu. Les logs noyau étaient propres. Pas d’erreurs. Juste un disque qui fait ce que font les disques orientés grand public quand on les utilise comme journal de base de données.
Ils ont testé un autre ordonnanceur (mq-deadline) et réduit légèrement la concurrence. La latence tail s’est améliorée, mais la vraie correction fut ennuyeuse : passer à une classe de SSD orientée endurance et séparer le WAL/journal sur un périphérique avec un comportement d’écriture soutenu prévisible.
La leçon n’était pas « acheter des disques chers ». C’était « arrêter de traiter les noms produits comme des garanties de performance ». NVMe est un protocole. Votre charge doit encore s’adapter à la physique et au firmware.
2) Optimisation qui s’est retournée contre eux : « On va pousser la profondeur de file au max »
Un cluster de virtualisation souffrait d’un faible débit sur un LUN SAN partagé. Quelqu’un avait lu que « des files profondes augmentent les IOPS », ce qui est vrai dans le même sens que « plus de voitures augmentent le flux de trafic » : parfois, jusqu’à un certain point.
Ils ont augmenté des réglages liés aux files à plusieurs couches : profondeur HBA, réglages multipath, et nr_requests au niveau bloc. Les graphiques du cluster semblaient magnifiques pendant un jour. Puis le helpdesk a commencé à recevoir des plaintes « la VM est gelée ». Pas lente. Gelée.
L’investigation a montré que la latence moyenne restait acceptable, mais la p99 est tombée dans un gouffre sous charge. Le SAN absorbait le parallélisme accru en mettant en file en interne. Quand une rafale arrivait (sauvegarde + patch + quelqu’un générant un rapport), les files internes ont grossi. Ce délai de file s’est traduit par des attentes IO de plusieurs secondes pour des VMs malchanceuses. Les files profondes rendaient le SAN occupé, pas réactif.
Ils ont reculé. Réduire la profondeur de file a diminué le débit de pointe mais amélioré la latence tail et éliminé l’expérience « VM gelée ». L’approche correcte fut de régler la profondeur à ce que l’array pouvait gérer avec une latence bornée, et d’appliquer l’isolation des charges (LUNs séparés / QoS / scheduling des sauvegardes).
Blague n°2 : L’« optimisation » de la profondeur de file est le seul tweak de performance qui peut aussi jouer le rôle de générateur d’incident improvisé.
3) Pratique ennuyeuse mais correcte qui a sauvé la mise : benchmarks de base et discipline de changement
Un service lourd en stockage avait un rituel : à chaque cycle de mise à jour du noyau, ils exécutaient la même petite suite de jobs fio sur un hôte de staging. C’était ennuyeux. Personne n’en tirait gloire. Mais cela a construit une baseline au fil du temps : IOPS typiques, p99 attendu, et comment ça variait selon l’ordonnanceur.
Une semaine, une mise à jour Ubuntu est arrivée. L’équipe applicative a signalé « disque lent », mais les graphiques étaient ambigus : CPU pas élevé, réseau pas saturé, dashboard stockage normal. Normalement c’est là que les querelles commencent et que l’incident traîne.
Parce qu’ils avaient des baselines, ils ont immédiatement relancé le même profil fio et vu la p99 doublée pour des écritures aléatoires 4k à la même concurrence. Cela a réduit la recherche. Ils ont découvert que la mise à jour avait changé une règle udev appliquée à certaines classes de périphériques, basculant l’ordonnanceur de leur réglage choisi au défaut.
Ils l’ont corrigé avec une règle udev persistante et ont vérifié le correctif avec le même benchmark. Pas d’héroïsme, pas de devinettes, pas de « ça semble plus rapide maintenant ». Le postmortem fut court et ennuyeux — exactement ce que l’on veut.
La pratique n’était pas brillante. Elle était reproductible et à portée de changement minimal. Le type d’ennui qui garde les pagers tranquilles.
Erreurs courantes : symptôme → cause racine → correction
1) Symptomatique : iowait élevé, mais le disque semble inactif
Cause racine : L’IO ne touche pas le disque local que vous surveillez (système de fichiers réseau, LUN différent, couche device-mapper), ou l’IO est bloqué sur des flushs pendant que le périphérique rapporte une faible utilisation.
Correction : Utilisez pidstat -d pour trouver le processus incriminé et findmnt pour identifier le périphérique sous-jacent. Si c’est NFS/Ceph/RBD, les changements d’ordonnanceur locaux n’aideront pas ; concentrez-vous sur la latence réseau/backend.
2) Symptomatique : gros débit, latence p99 terrible
Cause racine : Profondeur de file trop grande, ou concurrence de charge trop élevée, provoquant des délais de file. Souvent vu sur arrays partagés et volumes cloud.
Correction : Réduisez la concurrence (threads applicatifs, iodepth, nombre de jobs) et/ou choisissez un ordonnanceur qui borne la latence (souvent mq-deadline). Mesurez les percentiles avant/après.
3) Symptomatique : les IOPS de lecture aléatoire sont bien en dessous des attentes sur SSD/NVMe
Cause racine : Profondeur de file trop faible, mauvais moteur IO, ou un test utilisant accidentellement l’IO bufferisé et dominé par le cache page.
Correction : Utilisez fio avec --direct=1, réglez un iodepth raisonnable (par ex. 16–64), et confirmez l’ordonnanceur. Vérifiez aussi le lien PCIe et la localité NUMA si les résultats sont étrangement bas.
4) Symptomatique : les écritures deviennent lentes après quelques minutes de test
Cause racine : Exhaustion du cache SLC du SSD, garbage collection, throttling thermique, ou pression sur le journal du système de fichiers.
Correction : Étendez les benchmarks à 3–10 minutes et comparez comportement initial vs tardif. Vérifiez SMART/santé NVMe et température. Si la performance soutenue compte, utilisez des SSD entreprise et évitez les disques grand public pour les charges intensives de journal.
5) Symptomatique : changer l’ordonnanceur semble ne rien faire
Cause racine : Vous changez l’ordonnanceur sur la mauvaise couche (partition vs disque, dm-crypt vs disque sous-jacent), ou le périphérique est contrôlé par RAID matériel/SAN qui domine l’ordonnancement.
Correction : Utilisez lsblk pour trouver le périphérique feuille et vérifiez l’ordonnanceur là. Dans les cas RAID/SAN, concentrez-vous sur la politique de l’array, la profondeur HBA, et l’isolation des charges.
6) Symptomatique : pauses occasionnelles de plusieurs secondes avec comportement normal par ailleurs
Cause racine : Réinitialisations/timeouts de périphérique, basculements multipath, ou problèmes de firmware. Parfois aussi des commits ext4 sous contention pathologique.
Correction : Vérifiez dmesg pour messages de reset/timeout. Stabilisez le hardware/backend d’abord. Puis tunez.
7) Symptomatique : lectures séquentielles lentes sur arrays HDD
Cause racine : Read-ahead trop petit, fragmentation, ou IO aléatoire concurrent. Également possible : politique de cache du contrôleur ou RAID en reconstruction/dégradé.
Correction : Confirmez la santé de l’array, vérifiez le read-ahead, et isolez les charges séquentielles des écrivains aléatoires. Ne corrigez pas cela en approfondissant les files sans réfléchir.
Listes de vérification / plan étape par étape
Checklist A : réponse incidente « Disque lent » (15–30 minutes)
- Confirmez le chemin du périphérique : utilisez
findmntetlsblkpour vous assurer que vous réglez la bonne chose. - Vérifiez les erreurs : scannez
dmesgpour timeouts, réinitialisations ou erreurs IO. - Mesurez la latence/la mise en file en direct :
iostat -x 1sur le périphérique concerné pendant le problème. - Identifiez les principaux processus IO :
pidstat -d 1. Décidez si contrôler la charge est la vraie correction. - Établissez si c’est une douleur en lecture ou en écriture : comparez
r_awaitvsw_await, et considérez les charges fsync-intensives. - Faites un benchmark de base (si sûr) : un court run
fiocorrespondant au pattern suspecté. - Testez ensuite les changements d’ordonnanceur/profondeur : une manette à la fois, mesurez, notez les résultats.
Checklist B : plan de tuning contrôlé (compatible gestion des changements)
- Choisissez une charge représentative : définissez taille de bloc, mix lecture/écriture, concurrence et durée.
- Capturez la baseline : stockez la sortie
fio, snapshotsiostat, et version du noyau. - Définissez des critères de succès : ex. p99 write latency sous X ms à Y IOPS ; pas juste « plus rapide ».
- Testez les candidats ordonnanceur :
nonevsmq-deadline(et kyber si vous savez pourquoi). - Testez une plage de profondeurs : variez
fio --iodepthet le nombre de jobs ; n’allez pas direct aux extrêmes. - Validez sous contention : lancez une deuxième charge concurrente si la production a habituellement des IO mixtes.
- Rendez les changements persistants : une fois prouvés, implémentez via règle udev ou config appropriée et documentez.
- Surveillez après déploiement : regardez p95/p99, pas seulement les moyennes, pendant au moins un cycle métier.
Rendre les changements d’ordonnanceur persistants (sans surprises)
Les changements temporaires via echo disparaissent au reboot. Pour la persistance, les règles udev sont courantes. Le critère d’appariement exact dépend de la nomenclature du matériel. Testez en staging d’abord.
cr0x@server:~$ sudo tee /etc/udev/rules.d/60-ioscheduler.rules >/dev/null <<'EOF'
ACTION=="add|change", KERNEL=="nvme0n1", ATTR{queue/scheduler}="mq-deadline"
EOF
cr0x@server:~$ sudo udevadm control --reload-rules
cr0x@server:~$ sudo udevadm trigger --name-match=nvme0n1
Décision : Si vous ne pouvez pas correspondre avec confiance les périphériques (parce que les noms changent), usez d’attributs comme ID_MODEL ou WWN. Le but est une configuration stable, pas une roulette au démarrage.
FAQ
1) Dois-je utiliser none ou mq-deadline sur NVMe ?
Commencez par none pour des disques NVMe locaux purs, puis testez mq-deadline si vous tenez à la latence tail sous charge mixte. Choisissez celui qui améliore la p99 pour votre charge réelle.
2) Quelle profondeur de file dois-je définir ?
Il n’y a pas de nombre universel. Pour NVMe, des files plus profondes peuvent améliorer le débit. Pour arrays partagés/volumes cloud, trop de profondeur augmente souvent la p99. Tuner en mesurant : lancez fio avec iodepth 1, 4, 16, 32, 64 et tracez les percentiles de latence.
3) Pourquoi %util montre 100% mais le débit est faible ?
Sur des périphériques rapides ou quand la mise en file est lourde, %util peut refléter « attente occupée » et délai de file plutôt que du débit productif. Regardez await et aqu-sz pour interpréter.
4) Changer l’ordonnanceur aide-t-il sur RAID matériel ?
Parfois un peu, souvent pas beaucoup. Les contrôleurs RAID matériels et SANs font leur propre ordonnancement et caching. Dans ces cas, concentrez-vous sur la politique du contrôleur, l’alignement de taille de stripe, la santé de l’array, et la profondeur HBA.
5) Mon appli est lente, mais fio périphérique brut est rapide. Et maintenant ?
Le goulot est au-dessus du périphérique : journalisation du système de fichiers, comportement fsync, petits écrits synchrones, contention de métadonnées, ou pattern IO de l’application. Lancez des tests fio basés système de fichiers et inspectez options de montage et comportement de writeback.
6) bfq est-il adapté sur des serveurs ?
Rarement, mais pas jamais. Si vous avez besoin d’équité entre sources IO concurrentes (systèmes multi-tenant) et pouvez tolérer l’overhead, cela peut aider. Pour la plupart des charges serveurs, commencez par none ou mq-deadline et ne déviez qu’avec des preuves.
7) Dois-je monter ext4 avec discard pour la performance SSD ?
Généralement non. Préférez le trim périodique (fstrim.timer) pour un overhead prévisible. Le discard continu peut ajouter de la variance de latence.
8) Puis-je « réparer » un disque lent en augmentant le read-ahead ?
Seulement si la charge est vraiment des lectures séquentielles et que le goulot est le thrash de read-ahead. Pour les charges aléatoires (bases), augmenter le read-ahead gaspille souvent le cache et aggrave la situation. Mesurez le hit rate du cache page et le pattern avant d’y toucher.
9) Pourquoi la performance change après reboot ?
Vous avez perdu des réglages non persistants, le nom du périphérique a changé, ou udev/tuned a appliqué des politiques différentes. Rendez les changements persistants et vérifiez après reboot avec le même benchmark.
10) io_uring est-il toujours le meilleur moteur fio ?
C’est un bon défaut sur les noyaux modernes, mais pas universel. Dans certains environnements ou drivers, libaio est plus comparable aux applications legacy. Utilisez le moteur qui correspond à votre pile IO de production.
Conclusion : prochaines étapes qui survivent au contrôle des changements
Ubuntu 24.04 ne sabote pas secrètement vos disques. La plupart des problèmes « disque lent » se résument à trois choses : vous mesurez la mauvaise couche, vous mettez trop (ou trop peu) en file, ou le périphérique/backend est malade et le tuning distrait.
Faites ceci ensuite, dans l’ordre :
- Capturez des preuves : extraits
iostat -x,pidstat -d, etdmesgpendant le ralentissement. - Baselined avec fio : un test périphérique brut et un test système de fichiers qui ressemblent à la production.
- Testez les choix d’ordonnanceur :
nonevsmq-deadlineest le point pragmatique de départ. Vérifiez avec des percentiles. - Tunez la concurrence/profondeur de file délibérément : ajustez la parallélisme applicatif d’abord ; touchez aux knobs noyau seulement si vous pouvez expliquer pourquoi.
- Rendez persistant et documenté : si le correctif n’est pas reproductible après reboot, ce n’est pas un correctif ; c’est une démonstration.
L’objectif n’est pas de gagner un benchmark. C’est de rendre le système prévisible pendant le pire jour que vous êtes certain d’avoir.