Rien ne gâche autant votre matinée qu’une VM Linux qui « fonctionne bien » jusqu’au moment où elle ne fonctionne plus : l’invite du shell se fige, les commits MySQL se bloquent, journald cale, et la courbe de latence de votre application devient hostile. Vous vérifiez le CPU et la RAM — tout va bien. Le réseau — tout va bien. Puis vous ouvrez iostat et vous le voyez : la latence d’écriture aléatoire monte par pics jusqu’à plusieurs secondes. L’invité donne l’impression d’être sur un disque mécanique alimenté par des vibes.
Ceci est généralement réparable. Très souvent, ce n’est pas « le stockage est lent », mais plutôt que vous avez choisi le mauvais contrôleur de disque virtuel, le mauvais mode de cache, ou que vous avez mis QEMU dans un mode qui transforme une rafale de petites écritures en embouteillage. La bonne nouvelle : Proxmox vous donne des leviers. La mauvaise : les valeurs par défaut ne correspondent pas toujours à votre charge.
Méthode de diagnostic rapide
Si votre VM « saccade », vous devez décider d’où provient la latence : invité, QEMU, noyau hôte, backend de stockage ou le dispositif/cluster physique. Ne devinez pas. Triage.
Première étape : prouvez que c’est la latence de stockage (pas du CPU steal ou pression mémoire)
-
Dans l’invité : vérifiez si les blocages corrèlent avec l’attente disque.
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 1 0 0 812340 22012 903112 0 0 120 980 310 510 4 2 92 2 0 0 2 0 810112 22020 904000 0 0 0 4096 280 420 2 1 58 39 0 0 1 0 809980 22020 904120 0 0 0 2048 270 410 2 1 70 27 0Ce que cela signifie : un
waélevé indique du temps passé en attente d’IO. Sist(steal) est élevé, vous êtes en sur-souscription CPU ; corrigez cela avant de vous acharner sur les réglages disque.Décision : si
wapique pendant la saccade et questest bas, continuez à creuser du côté du stockage.
Deuxième étape : trouvez la couche lente (file d’attente invité vs périphérique hôte)
-
Sur l’hôte Proxmox : surveillez la latence et la saturation par périphérique.
cr0x@server:~$ iostat -x 1 5 Linux 6.8.12-pve (pve01) 12/26/2025 _x86_64_ (32 CPU) avg-cpu: %user %nice %system %iowait %steal %idle 10.12 0.00 3.01 8.33 0.00 78.54 Device r/s w/s rMB/s wMB/s avgrq-sz avgqu-sz await r_await w_await svctm %util nvme0n1 22.0 480.0 1.2 46.8 192.0 3.8 7.9 1.2 8.2 0.4 20.5 sda 0.0 95.0 0.0 6.0 128.0 18.2 191.3 0.0 191.3 1.1 99.8Ce que cela signifie :
awaitest la latence des requêtes ; un%utilproche de 100% avec unawaitélevé hurle « périphérique saturé ». Icisdaest le coupable.Décision : si la latence est mauvaise au niveau du périphérique hôte, les ajustements de contrôleur/cache dans la VM ne vous sauveront pas. Réparez le backend (migration, disque plus rapide, meilleur agencement du pool) avant d’ajuster.
-
Sur l’hôte : vérifiez si les threads QEMU sont bloqués en IO.
cr0x@server:~$ pgrep -a qemu-system-x86_64 | head -n 1 24891 /usr/bin/kvm -id 103 -name vm103 -m 8192 ... -drive file=/dev/zvol/rpool/vm-103-disk-0,if=none,id=drive-scsi0,format=raw,cache=none,aio=native ...Ce que cela signifie : la ligne de commande QEMU vous indique le mode de cache, le mode aio, et si vous utilisez virtio-scsi/virtio-blk.
Décision : si vous voyez
cache=writebacksur un hôte sans UPS et sans cache d’écriture stable, vous prenez un risque de perte de données ; si vous voyezaio=threadsavec CPU élevé et latence, envisagezaio=nativesi le backend le supporte.
Troisième étape : confirmez que l’invité voit un périphérique virtuel et un modèle de queue cohérents
-
Dans l’invité : identifiez le type de disque et si c’est virtio.
cr0x@server:~$ lsblk -o NAME,MODEL,TYPE,SIZE,ROTA,DISC-MAX,DISC-GRAN,MOUNTPOINTS NAME MODEL TYPE SIZE ROTA DISC-MAX DISC-GRAN MOUNTPOINTS vda QEMU HARDDISK disk 200G 0 2G 4K ├─vda1 part 1G 0 └─vda2 part 199G 0 /Ce que cela signifie :
vdaveut généralement dire virtio-blk.sdaà l’intérieur d’une VM signifie souvent SATA/SCSI émulé et a typiquement plus d’overhead.Décision : si vous êtes sur des contrôleurs émulés, planifiez une fenêtre de maintenance pour migrer vers virtio.
Cette méthode est volontairement courte. Le reste de l’article explique le « pourquoi » et vous donne les réglages qui réduisent de manière fiable les pics de latence sans en créer d’autres.
Ce que signifie réellement la « saccade » disque dans une VM
La saccade ne concerne pas le débit moyen. Votre monitoring affichera 50 MB/s et tout le monde félicitera le stockage. Pendant ce temps, votre VM se fige pendant 800 ms toutes les quelques secondes parce que de petites écritures synchrones s’accumulent derrière quelque chose de coûteux.
Dans le stockage virtualisé, il existe plusieurs files d’attente et points de vidage :
- Cache de pages de l’invité : les écritures tamponnées s’accumulent jusqu’à ce que le noyau décide de les vider.
- Couche bloc de l’invité : fusionne et planifie les IO (moins dramatique sur les noyaux récents, mais toujours réel).
- File du contrôleur virtuel : virtio-blk et virtio-scsi ont des modèles de queue et des comportements d’interruption différents.
- Couche bloc de QEMU : le mode cache contrôle si QEMU utilise le cache de pages de l’hôte, et où atterrissent les flushes.
- Système de fichiers / gestionnaire de volumes hôte : ZFS, LVM-thin, ext4-sur-SSD, Ceph RBD ont chacun des caractéristiques de latence différentes.
- Dispositif/cluster : le disque réel, le contrôleur RAID, NVMe, SAN ou les OSD Ceph.
La saccade correspond généralement à l’un de ces schémas :
- Orages de flush : un lot d’écritures rencontre une barrière de flush (
fsync(), commit du journal, commit de base de données), et la latence explose. - Étranglement d’une seule file : un modèle de disque/contrôleur utilise une seule queue, donc les charges parallèles se sérialisent.
- Pression mémoire sur l’hôte : le cache de pages de l’hôte thrash, et l’IO devient « synchronisé par surprise ».
- Amplification d’écriture côté backend : provisioning thin, copy-on-write ou réplication rendent les petites écritures coûteuses.
Une phrase à garder en tête : l’invité croit qu’il parle à un disque ; en réalité vous exécutez un système distribué composé de files d’attente.
Idée paraphrasée de Werner Vogels (fiabilité/ops) : « Tout finit par échouer ; concevez pour que les échecs soient attendus et gérables. »
Blague n°1 : La performance du stockage, c’est comme les commérages — tout va vite jusqu’à ce que vous demandiez un sync.
Faits et histoire qui expliquent les comportements par défaut surprenants
Certaines options « mystérieuses » de Proxmox/QEMU deviennent beaucoup plus sensées quand on sait d’où elles viennent.
- Virtio est né pour éviter d’émuler du matériel. La virtualisation précoce émulait souvent IDE/SATA ; virtio est apparu ensuite comme une interface paravirtualisée pour réduire l’overhead.
- virtio-blk est antérieur à virtio-scsi. virtio-blk était plus simple et largement supporté ; virtio-scsi est arrivé pour offrir des fonctionnalités proches du SCSI réel (plusieurs LUNs, hotplug, meilleurs schémas d’évolutivité).
- Les barrières d’écriture et les flushes se sont renforcés avec le temps. Les systèmes de fichiers et bases de données ont moins confiance dans les caches après des histoires de corruption dans les années 2000 ; la sémantique des flushes compte davantage aujourd’hui.
- Le cache de pages de l’hôte était autrefois une victoire facile. Sur disques tournants et petites mémoires, utiliser le cache hôte (writeback) pouvait lisser l’IO ; sur SSD/NVMe modernes avec plusieurs VMs, cela peut causer de la contention de cache bruyant-voisin.
- AIO en Linux a deux personnalités. « native » AIO (AIO du noyau) se comporte différemment de l’émulation basée sur des threads ; ce qui est meilleur dépend du backend et de l’alignement.
- NCQ et multi-queue n’étaient pas toujours courants. Beaucoup de folklore de tuning vient de l’époque du SATA mono-queue et du parallélisme limité.
- ZFS est devenu populaire en virtualisation pour les snapshots/clones. Le compromis : copy-on-write et checksumming ajoutent de l’overhead ; ça vaut le coup, mais il faut respecter le comportement des écritures sync.
- Ceph a popularisé le « stockage en cluster ». Excellent pour la résilience et l’échelle ; la latence est un produit de quorum, réseau et charge des OSD, pas seulement d’un disque.
Ce ne sont pas des anecdotes. Elles expliquent pourquoi un réglage fonctionne dans votre environnement et aggrave celui de votre collègue.
Choix du contrôleur : virtio-scsi vs virtio-blk (et quand SATA reste utile)
Si vous utilisez Proxmox et que le disque de votre VM Linux apparaît comme sda avec un contrôleur “Intel AHCI”, vous payez pour une imitation matérielle. Les contrôleurs émulés existent pour la compatibilité. La performance n’est pas leur rôle.
Que choisir par défaut
- Utilisez virtio-scsi-single pour la plupart des VM Linux modernes. C’est un bon défaut : bon support fonctionnel, bonne montée en charge, et comportement prévisible avec iothreads.
- Utilisez virtio-blk pour les configurations simples ou lorsque vous voulez moins de pièces mobiles. virtio-blk peut être très rapide. Il est aussi plus simple, ce qui compte parfois pour le débogage et la maturité des pilotes dans des invités exotiques.
- Utilisez SATA seulement pour le bootstrap ou des médias d’installation étranges. Une fois installé, passez à virtio.
virtio-scsi : ce que ça vous apporte
virtio-scsi modélise un HBA SCSI avec transport virtio. Dans Proxmox vous verrez des options comme :
- virtio-scsi-pci : peut attacher plusieurs disques ; peut partager une file, dépend de la configuration.
- virtio-scsi-single : crée un contrôleur séparé par disque (ou isole effectivement le queueing), réduisant souvent la contention de locks et améliorant l’équité entre disques.
En pratique : virtio-scsi-single est souvent la manière la plus simple d’obtenir une latence prévisible sur plusieurs disques occupés. Si vous avez un disque base de données et un disque logs, vous voulez qu’ils arrêtent de se gêner mutuellement dans une file partagée.
virtio-blk : ce que ça vous apporte
virtio-blk est un périphérique bloc paravirtualisé. Il est léger. Il peut fournir un fort débit et un faible overhead. Mais il peut être moins flexible quand vous voulez des fonctionnalités façon SCSI, et historiquement certains comportements avancés (comme certains patterns de discard/unmap) ont été plus simples avec virtio-scsi.
Quand le choix du contrôleur corrige vraiment la saccade
Les changements de contrôleur réduisent la saccade lorsque le goulot d’étranglement est dans le queueing du périphérique virtuel ou le traitement des interruptions. Signes typiques :
- Le périphérique hôte a un
awaitbas (le backend est correct), mais l’invité a un IO wait élevé et des pauses périodiques. - Une VM obtient des rafales de bon débit puis un silence total, même si le stockage hôte n’est pas saturé.
- Plusieurs disques occupés dans la même VM se gênent (les logs bloquent les commits DB, les fichiers temporaires bloquent les écritures applicatives).
Recommandation pragmatique
Si vous avez des saccades et que vous n’êtes pas sûr : passez le contrôleur de disque de la VM à virtio-scsi-single, activez iothread pour ce disque, et utilisez cache=none sauf raison spécifique contraire.
Ce n’est pas la seule bonne configuration. C’est celle qui corrige le plus souvent les pics de latence p99 sans transformer l’intégrité des données en un mode de vie.
Modes de cache : les réglages qui font ou défont votre p99
Le mode de cache est l’endroit où performance et sécurité se serrent la main avec maladresse. En termes Proxmox/QEMU, vous décidez si le cache de pages hôte se situe au milieu et comment les flushes sont gérés.
Les modes courants (ce qu’ils signifient vraiment)
- cache=none : QEMU utilise direct IO (lorsque possible). Le cache de pages de l’hôte est principalement contourné. Le cache invité existe toujours. Les flushes se traduisent plus directement sur le backend. Souvent meilleur pour la prévisibilité de la latence et pour éviter la double mise en cache.
- cache=writeback : les écritures de QEMU vont dans le cache de pages de l’hôte et sont acquittées rapidement ; elles sont flushées vers le stockage plus tard. Rapide, jusqu’à ce que ça ne le soit plus. Risqué sans protection d’alimentation ou caches stables, parce que l’invité croit que les données sont sûres plus tôt qu’elles ne le sont réellement.
- cache=writethrough : les écritures passent par le cache hôte mais sont flushées avant la fin de l’opération. Plus sûr que writeback, généralement plus lent, parfois source de saccades sous charges sync-intensives.
- cache=directsync : tente de rendre chaque écriture synchrone. En général une excellente façon d’apprendre la patience.
- cache=unsafe : ne le faites pas. Ça existe pour les benchs et les regrets.
Pourquoi writeback peut provoquer des saccades (même quand il « benchmarque bien »)
Writeback transforme souvent votre problème en un problème de temporalité. Le cache de pages hôte absorbe les écritures rapidement — donc l’invité produit plus d’écritures. Puis l’hôte décide qu’il est temps de flush. Le flush survient en rafales, et ces rafales peuvent bloquer de nouvelles écritures ou affamer les lectures. Votre VM perçoit cela comme des gels périodiques.
Sur un hôte peu chargé avec une VM unique, writeback peut donner une sensation agréable. En production avec plusieurs VMs et des charges mixtes, c’est l’équivalent IO de laisser tout le monde se rabattre en même temps sur une seule voie.
Pourquoi cache=none est ennuyeux dans le bon sens
Avec cache=none, vous réduisez la double mise en cache et vous rapprochez la perception de persistance de l’invité de la réalité. Cela stabilise souvent la latence. L’invité met toujours en cache agressivement, ce n’est pas « sans cache ». C’est « un cache, à un seul endroit, avec moins de surprises ».
Flushes, fsync et pourquoi les bases de données exposent les mauvais réglages
Les bases appellent fsync() parce qu’elles n’aiment pas perdre des données. Les systèmes de fichiers journalisés émettent aussi des barrières/flushes pour l’ordre. Si votre mode de cache et votre backend transforment les flushes en opérations globales coûteuses, vous obtenez le schéma de saccade classique : la VM va bien jusqu’à un point de commit, puis tout s’arrête.
Note sur la sécurité (parce que vous aimez vos week-ends)
cache=writeback peut être acceptable si vous avez :
- un UPS qui fonctionne réellement et est intégré (l’hôte s’éteint proprement),
- un stockage avec power-loss protection (PLP) ou un contrôleur avec cache sur batterie,
- et que vous comprenez les modes de défaillance.
Sinon, le réglage « rapide » est rapide jusqu’au moment où il devient un post-mortem.
AIO et iothreads : transformer l’IO parallèle en parallélisme réel
Même avec le bon contrôleur et le bon mode de cache, vous pouvez toujours subir des saccades parce que le traitement IO de QEMU est sérialisé ou en contention. Deux réglages comptent beaucoup : le mode AIO et les iothreads.
aio=native vs aio=threads
QEMU peut soumettre des IO en utilisant l’AIO native Linux ou un pool de threads qui effectuent des appels bloquants. Le gagnant dépend du backend et du comportement du noyau, mais une règle décente :
- aio=native : souvent moins d’overhead et meilleur pour les chemins direct IO ; peut réduire le jitter quand le backend le supporte proprement.
- aio=threads : plus compatible ; parfois plus gourmand en CPU et peut introduire du jitter de planification sous charge.
Si vous êtes sur des zvols ZFS ou des dispositifs bruts, native AIO est communément une bonne idée. Si vous êtes sur certains fichiers images ou configurations inhabituelles, threads peut mieux se comporter. Mesurez, ne vous fiez pas aux vibes.
iothreads : le stabilisateur de latence que vous devriez vraiment utiliser
Sans iothreads, QEMU peut traiter les IO dans le thread principal de la boucle d’événements (plus les helpers), ce qui fait que l’IO disque concurrence le travail d’émulation et la gestion des interruptions. Avec iothreads, chaque disque peut avoir son propre thread IO, réduisant la contention et lissant la latence.
Dans Proxmox, vous pouvez activer un iothread par disque. C’est particulièrement utile quand :
- vous avez plusieurs disques occupés dans une VM,
- vous avez un seul disque occupé effectuant de nombreuses petites écritures synchrones,
- vous essayez de garder le p99 sous contrôle plutôt que de gagner un concours de débit séquentiel.
Combien d’iothreads ?
Ne créez pas 20 iothreads parce que vous le pouvez. Chaque thread ajoute un coût de planification. Créez des iothreads pour les disques qui comptent : volume base de données, système de fichiers journalisé, disque de logs chargé. Pour une VM avec un disque, un iothread suffit généralement.
Blague n°2 : Ajouter des iothreads, c’est comme embaucher plus de baristas — super jusqu’à ce que le café devienne une réunion sur comment faire du café.
Implications du backend de stockage (ZFS, Ceph, LVM-thin, fichiers)
Vous pouvez choisir le contrôleur et les réglages de cache parfaits et toujours subir des saccades parce que le backend fait quelque chose de coûteux. Proxmox abstrait le stockage, mais la physique n’est pas impressionnée.
ZFS : écritures sync, ZIL/SLOG, et la question « pourquoi mon NVMe est encore lent ? »
ZFS est excellent pour l’intégrité et les snapshots. Il est aussi honnête sur les écritures sync. Si votre charge invité émet des écritures sync (bases de données, journaux, apps fsync-intensives), ZFS doit les committer de façon sûre. Sans un SLOG dédié rapide avec protection contre la perte d’alimentation, les charges sync-heavy peuvent saccader même sur des pools rapides.
Points clés :
- zvol vs dataset : Les VMs sur zvols se comportent généralement de manière plus prévisible que qcow2 sur datasets pour un IO intensif, bien que les deux puissent fonctionner.
- comportement sync : si un invité émet des flushes, ZFS les prend au sérieux. Si vous « résolvez » ça en réglant
sync=disabled, vous échangez durabilité contre vitesse. - recordsize/volblocksize : un mauvais appariement peut augmenter l’amplification d’écriture. Pour les zvols VM, le
volblocksizecompte à la création.
Ceph RBD : la latence est une propriété du cluster
Ceph est résilient et évolutif. Il a aussi plus d’endroits où la latence peut s’introduire : réseau, charge OSD, backfill/recovery, peering PG, et queueing côté client.
Les schémas de saccade sur Ceph viennent souvent de :
- recovery/backfill saturant disques ou réseau,
- OSDs à performances inégales (un disque lent ralentit une écriture répliquée),
- options client qui rendent les flushes coûteux,
- voisin bruyant sur des nœuds OSD partagés.
Les choix de contrôleur/cache comptent toujours, mais ils ne compenseront pas un cluster en recovery au pire moment. (Il l’est toujours.)
LVM-thin : pression métadonnées et comportement du discard
LVM-thin est rapide et simple, mais le provisioning thin introduit des écritures de métadonnées et peut devenir erratique sous de fortes écritures aléatoires ou quand le pool thin est presque plein. Discard/TRIM peut aider à récupérer de l’espace mais peut aussi générer des rafales de travail selon la configuration.
qcow2 en mode fichier : les snapshots sont pratiques, mais surveillez l’overhead
qcow2 est pratique : snapshots, compression, chiffrement. Il ajoute aussi des métadonnées copy-on-write et un risque de fragmentation. Sous charges d’écritures aléatoires, qcow2 peut saccader sensiblement comparé au raw sur un bloc.
Si vous avez besoin de performance et de latence stable, préférez raw sur un backend bloc. Si vous avez besoin de fonctionnalités, utilisez qcow2 mais acceptez que vous deviez surprovisionner et défragmenter/convertir périodiquement.
Tâches pratiques : commandes, sorties et décisions (12+)
Voici des tâches réelles que vous pouvez exécuter aujourd’hui. Chacune inclut : une commande, une sortie réaliste, ce que ça signifie, et la décision à prendre.
Tâche 1 : Identifier le contrôleur de disque et le mode de cache de la VM dans la config Proxmox
cr0x@server:~$ qm config 103
boot: order=scsi0
cores: 4
memory: 8192
name: api-prod-03
net0: virtio=DE:AD:BE:EF:10:03,bridge=vmbr0
ostype: l26
scsihw: virtio-scsi-single
scsi0: rpool:vm-103-disk-0,cache=none,discard=on,iothread=1
agent: 1
Ce que cela signifie : scsihw est le modèle du contrôleur ; la ligne du disque montre cache=none, discard activé, et iothread activé.
Décision : si vous voyez sata0 ou scsihw: lsi avec un IO lourd, planifiez une migration vers virtio-scsi-single ou virtio-blk. Si le cache est writeback, validez la protection d’alimentation et le domaine de défaillance.
Tâche 2 : Confirmer ce que QEMU a effectivement lancé (trust but verify)
cr0x@server:~$ ps -p $(pgrep -f "kvm -id 103") -o pid,cmd --cols 200
PID CMD
24891 /usr/bin/kvm -id 103 -name vm103 -m 8192 -smp 4 ... -device virtio-scsi-pci,id=scsihw0 ... -device scsi-hd,drive=drive-scsi0 ... -drive file=/dev/zvol/rpool/vm-103-disk-0,if=none,id=drive-scsi0,format=raw,cache=none,aio=native ...
Ce que cela signifie : confirme le cache effectif et le mode AIO ; parfois les réglages GUI diffèrent de la réalité en raison d’anciennes configs ou d’overrides.
Décision : si vous attendiez aio=native et que vous voyez threads, investiguez la compatibilité du backend et les réglages de version Proxmox.
Tâche 3 : Vérifier la latence et la saturation du stockage hôte sous charge
cr0x@server:~$ iostat -x -d 1 3
Device r/s w/s rMB/s wMB/s avgrq-sz avgqu-sz await svctm %util
nvme0n1 40.0 620.0 2.8 62.5 205.0 5.2 8.4 0.3 19.0
Ce que cela signifie : le périphérique n’est pas saturé (%util bas), la latence est acceptable.
Décision : si la latence du périphérique hôte est basse mais que l’invité saccade, suspectez le contrôleur/cache/queueing dans les couches de virtualisation.
Tâche 4 : Identifier quel processus génère l’IO sur l’hôte
cr0x@server:~$ pidstat -d 1 3
Linux 6.8.12-pve (pve01) 12/26/2025 _x86_64_ (32 CPU)
# Time UID PID kB_rd/s kB_wr/s kB_ccwr/s iodelay Command
12:01:01 0 24891 120.00 54000.00 0.00 12 kvm
12:01:01 0 1820 0.00 220.00 0.00 1 zfs
Ce que cela signifie : le processus QEMU de la VM est responsable de la majorité des écritures ; vous regardez la bonne cible.
Décision : si l’IO vient de pvestatd, des backups ou des jobs de réplication, planifiez/limitez cela avant d’ajuster les disques VM.
Tâche 5 : Vérifier la latence et la profondeur de file d’attente côté invité
cr0x@server:~$ iostat -x 1 3
Device r/s w/s rkB/s wkB/s aqu-sz await r_await w_await %util
vda 5.0 220.0 320.0 22000.0 8.4 38.0 2.0 38.8 98.0
Ce que cela signifie : l’invité voit le disque presque saturé avec un temps d’attente d’écriture élevé. Cela peut être une vraie latence backend, ou du queueing virtuel/du comportement de flush.
Décision : corrélez avec iostat sur l’hôte. Si la latence hôte est basse mais que l’invité attend, suspectez le queueing dans le contrôleur/l’émulation ou un orage de flush.
Tâche 6 : Vérifier si l’invité émet beaucoup de flushes
cr0x@server:~$ grep -R . /sys/block/vda/queue/* 2>/dev/null | egrep "write_cache|nomerges|max_sectors_kb"
/sys/block/vda/queue/max_sectors_kb:1280
/sys/block/vda/queue/nomerges:0
/sys/block/vda/queue/write_cache:write through
Ce que cela signifie : write through suggère que le dispositif présente un cache write-through ; le comportement des flushes est important.
Décision : si les charges sont fsync-heavy et que vous voyez des orages de flush, préférez cache=none et assurez-vous que le backend gère bien les écritures sync (ZFS SLOG, réglages Ceph, etc.).
Tâche 7 : Mesurer le comportement de commit du système de fichiers dans l’invité (pression du journal)
cr0x@server:~$ dmesg | tail -n 8
[ 9123.112233] EXT4-fs (vda2): re-mounted. Opts: (null)
[ 9450.774411] INFO: task jbd2/vda2-8:341 blocked for more than 120 seconds.
[ 9450.774419] Tainted: G W 6.5.0-28-generic #29-Ubuntu
[ 9450.774425] "echo 0 > /proc/sys/kernel/hung_task_timeout_secs" disables this message.
Ce que cela signifie : le thread du journal ext4 est bloqué — symptôme classique de stall stockage, souvent sur les chemins de flush/commit.
Décision : traitez cela comme un signal de gravité. Corrigez la latence IO d’abord ; ne « tunez » pas ext4 pour masquer le problème.
Tâche 8 : Vérifier la pression mémoire de l’hôte (le thrash du cache hôte provoque des saccades)
cr0x@server:~$ free -h
total used free shared buff/cache available
Mem: 125Gi 98Gi 1.2Gi 2.0Gi 26Gi 18Gi
Swap: 16Gi 12Gi 4.0Gi
Ce que cela signifie : l’utilisation du swap est élevée ; l’hôte peut être sous pression mémoire, rendant l’IO plus lent et plus instable.
Décision : réduisez la sursouscription, ajoutez de la RAM, ou cessez d’utiliser le cache de pages hôte pour les disques VM. cache=none aide en réduisant la dépendance au cache hôte, mais il faut toujours assez de mémoire.
Tâche 9 : Inspecter la santé du pool ZFS et les indicateurs de latence (si vous utilisez ZFS)
cr0x@server:~$ zpool status -v
pool: rpool
state: ONLINE
config:
NAME STATE READ WRITE CKSUM
rpool ONLINE 0 0 0
mirror-0 ONLINE 0 0 0
nvme0n1 ONLINE 0 0 0
nvme1n1 ONLINE 0 0 0
errors: No known data errors
Ce que cela signifie : le pool est sain ; aucun périphérique défaillant évident causant des retries.
Décision : si vous voyez des vdevs dégradés ou une augmentation des erreurs, arrêtez de tuner les VMs et remplacez le matériel / résilverisez d’abord.
Tâche 10 : Vérifier les réglages sync ZFS et si vous trichez sur la durabilité
cr0x@server:~$ zfs get -o name,property,value -s local,default sync rpool
NAME PROPERTY VALUE
rpool sync standard
Ce que cela signifie : les écritures sync sont honorées normalement.
Décision : si quelqu’un a mis sync=disabled pour « améliorer les performances », considérez cela comme un risque. Si la performance est inacceptable avec sync=standard, ajoutez un SLOG approprié ou repensez l’architecture de stockage.
Tâche 11 : Confirmer que discard/TRIM est activé de bout en bout (pools thin et SSDs)
cr0x@server:~$ qm config 103 | grep -E "discard|ssd|iothread"
scsi0: rpool:vm-103-disk-0,cache=none,discard=on,iothread=1
Ce que cela signifie : discard est activé au niveau du disque virtuel.
Décision : si vous utilisez du provisioning thin (LVM-thin, comportement thin-ish ZFS, Ceph), envisagez discard pour éviter l’explosion d’espace — mais testez, car discard peut introduire des rafales selon le comportement invité.
Tâche 12 : Vérifier que l’invité utilise bien les pilotes virtio et le multi-queue (côté invité)
cr0x@server:~$ lsmod | egrep "virtio_blk|virtio_scsi|scsi_mod" | head
virtio_scsi 28672 2
scsi_mod 274432 3 virtio_scsi,sd_mod,sg
virtio_pci 32768 0
virtio_ring 40960 2 virtio_net,virtio_scsi
Ce que cela signifie : virtio-scsi est chargé ; vous n’êtes pas sur des pilotes SATA émulés.
Décision : si les modules virtio ne sont pas présents, vous utilisez peut-être le mauvais contrôleur ou un initramfs ancien. Corrigez la disponibilité des pilotes avant de changer d’autres réglages.
Tâche 13 : Mesurer le comportement flush-heavy avec un rapide fio honnête (hôte ou invité)
cr0x@server:~$ fio --name=syncwrite --filename=/var/lib/testfile --size=1G --rw=randwrite --bs=4k --iodepth=1 --numjobs=1 --direct=1 --sync=1 --time_based --runtime=20
syncwrite: (groupid=0, jobs=1): err= 0: pid=2123: Thu Dec 26 12:10:01 2025
write: IOPS=3200, BW=12.5MiB/s (13.1MB/s)(250MiB/20001msec)
clat (usec): min=120, max=42000, avg=310.42, stdev=900.12
Ce que cela signifie : la latence max de complétion atteint 42ms sur cette exécution ; dans les cas de saccade vous verrez des centaines de ms voire des secondes.
Décision : si la latence max est énorme, concentrez-vous sur le chemin sync : mode de cache, gestion sync du backend (ZFS SLOG), santé Ceph, ou saturation hôte.
Tâche 14 : Vérifier les réglages de la file bloc de l’hôte (parfois un limiteur silencieux)
cr0x@server:~$ cat /sys/block/nvme0n1/queue/scheduler
[mq-deadline] none kyber bfq
Ce que cela signifie : l’hôte utilise mq-deadline, un bon défaut pour beaucoup de workloads SSD/NVMe.
Décision : si vous êtes sur des disques rotatifs, bfq ou des choix deadline peuvent changer la latence. Pour NVMe, changer le scheduler n’est généralement pas la première solution, mais peut aider l’équité sous charges mixtes.
Tâche 15 : Trouver si des backups/réplications entrent en collision avec l’IO VM
cr0x@server:~$ systemctl list-timers --all | egrep "pve|vzdump" || true
Thu 2025-12-26 12:30:00 UTC 20min left Thu 2025-12-26 12:00:01 UTC 9min ago vzdump.timer vzdump backup job
Ce que cela signifie : un job de backup est programmé et peut tourner fréquemment ; les backups peuvent créer des rafales de lecture et un overhead de snapshot.
Décision : si la saccade coïncide avec les fenêtres de backup, limitez/planifiez les backups, utilisez des modes snapshot adaptés au backend, et isolez l’IO de backup quand c’est possible.
Trois mini-histoires d’entreprise depuis les tranchées IO
Mini-histoire 1 : L’incident causé par une mauvaise hypothèse
L’équipe a hérité d’un cluster Proxmox « majoritairement OK » pour des services internes. Une nouvelle API client a été déployée dans une VM avec un petit PostgreSQL. La charge n’était pas massive ; juste constante. En une journée, on a commencé à voir des pics de latence périodiques et des timeouts bizarres. Les graphes CPU étaient polis. Le réseau semblait ennuyeux. La VM était « saine ».
Quelqu’un a supposé que le backend stockage était lent et a appliqué le remède habituel : « activez writeback cache, ça va lisser. » Ça a fonctionné — brièvement. Le débit a augmenté. La saccade est devenue moins fréquente, ce qui a rendu le changement brillant. Puis l’hôte a redémarré inopinément après un événement d’alimentation trop court pour déclencher un arrêt propre mais suffisant pour gâcher votre soirée.
La base a redémarré avec des symptômes de corruption : segments WAL manquants et état incohérent. Postgres a fait son travail et a refusé de prétendre que tout allait bien. La récupération a marché, mais ce n’était ni rapide ni agréable. Le plus inconfortable n’était pas la coupure ; c’était de réaliser que la « correction de performance » avait changé le contrat de durabilité sans qu’aucun responsable n’ait donné son accord.
Ce qui a réellement résolu la saccade plus tard n’a pas été writeback. Ils ont déplacé le disque VM d’un contrôleur émulé vers virtio-scsi-single, activé iothread, utilisé cache=none, et résolu la latence sync du backend avec un dispositif optimisé pour l’écriture. La latence s’est aplatie. La durabilité est restée intacte. La leçon est restée : ne jamais « supposer » qu’un mode de cache est juste un bouton vitesse.
Mini-histoire 2 : L’optimisation qui a mal tourné
Une autre organisation exécutait des workloads mixtes : runners CI, pipeline d’ingestion de logs, et quelques services stateful. Ils ont remarqué que les jobs CI étaient lents sur les phases disque, alors ils ont ajusté agressivement. Ils ont changé les disques en virtio-blk, mis un iodepth élevé dans l’invité, et activé discard partout pour garder les thin pools propres.
Les benchs semblaient meilleurs. L’équipe CI a célébré. Deux semaines plus tard, le système d’ingestion de logs a commencé à saccader aux heures de pointe. Ce n’était pas le CPU. Ce n’était pas le réseau. C’était des pics de latence IO — nets, périodiques, et péniblement difficiles à corréler. Le pire : ce n’était pas une seule VM. C’était un motif à travers plusieurs.
La cause racine était une combinaison de « bonnes idées » qui se sont alignées de manière défavorable : des opérations discard de nombreuses VMs généraient des rafales de travail backend, et le tuning iodepth a créé des files plus grandes, donc les queues tail ont allongé sous contention. Le cluster n’était pas « plus lent », il était plus bruyant. Le p50 s’était amélioré tandis que le p99 est devenu moche, ce qui est la façon dont on se trompe avec les moyennes.
La correction a été ennuyeuse : limiter le comportement discard (le rendre programmé plutôt que constant), réduire la profondeur de file pour les VMs sensibles à la latence, réintroduire virtio-scsi-single pour certains invités multi-disques, et utiliser iothreads sélectivement. Ils ont conservé les gains CI sans sacrifier le pipeline d’ingestion. La vraie victoire : apprendre que l’optimisation est une négociation entre workloads, pas un seul chiffre.
Mini-histoire 3 : La pratique ennuyeuse mais correcte qui a sauvé la journée
Une entreprise proche de la finance utilisait Proxmox avec des mirrors ZFS sur NVMe. Pas glamour, pas bon marché, mais sain. Ils avaient une habitude qui ressemblait à de la bureaucratie : chaque VM avait une petite fiche de runbook listant le type de contrôleur, le mode de cache, si iothread était activé, et pourquoi. Rien de fancy — juste assez pour empêcher l’improvisation.
Un jour, une mise à jour du noyau plus un changement de charge invité ont déclenché des stalls disques intermittents dans une VM critique. La VM était un broker de messages et elle détestait les pics de latence. Les utilisateurs ont vu des traitements retardés. Les ingénieurs ont commencé à chercher « ce qui a changé ». Le runbook a rendu cela rapide : pas de changement récent de config VM. Contrôleur et cache comme prévu. Cela a écarté la moitié des suspects habituels.
Ils sont allés directement aux métriques hôte et ont vu une latence élevée sur un NVMe pendant les rafales. Il ne tombait pas en panne, mais son comportement était étrange. Un bug firmware a été suspecté. Parce qu’ils avaient des réglages VM cohérents, ils ont pu reproduire le problème avec des tests ciblés et l’isoler au périphérique plutôt qu’à des flags QEMU.
La mitigation a été propre : migrer les disques VM hors du périphérique suspect, appliquer le firmware, et valider avec les mêmes profils fio utilisés en test d’acceptation. La coupure a été contenue. Le postmortem a été court. Personne n’a eu à expliquer pourquoi un changement rapide writeback avait été fait à 3h du matin. Les pratiques ennuyeuses ne font pas le buzz ; elles gardent le chiffre d’affaires attaché à la réalité.
Erreurs courantes : symptôme → cause racine → correction
1) Symptom : blocages périodiques de 1–5 secondes ; le débit moyen semble correct
Cause racine : orages de flush déclenchés par fsync/commits de journal + mode de cache/backend qui transforme le flush en blocage.
Correction : utilisez cache=none, activez iothread, assurez-vous que le backend gère les écritures sync (ZFS avec SLOG approprié, Ceph sain et sans recovery), évitez qcow2 pour les workloads sync-intensifs.
2) Symptom : l’invité affiche un await élevé, l’hôte affiche un await bas
Cause racine : contention de queueing/contrôleur virtuel (queue partagée), contention de la boucle principale QEMU, ou mode AIO sous-optimal.
Correction : passez à virtio-scsi-single ou vérifiez virtio-blk multiqueue si approprié ; activez iothread ; envisagez aio=native pour les chemins direct IO.
3) Symptom : les saccades commencent après avoir activé writeback « pour la vitesse »
Cause racine : flush et throttling du cache de pages hôte ; la pression mémoire amplifie cela.
Correction : revenez à cache=none sauf si vous avez une protection contre la perte d’alimentation ; ajoutez de la RAM ou réduisez la sursouscription mémoire VM ; isolez les VMs IO-intensives sur un stockage dédié.
4) Symptom : le thin pool se remplit de façon inattendue ; la VM ralentit à l’approche du plein
Cause racine : pression sur les métadonnées LVM-thin et comportement near-full ; discard non activé ou inefficace.
Correction : surveillez l’utilisation du thin pool, gardez de la marge, activez discard avec précaution, lancez périodiquement fstrim dans les invités, et évitez de sur-allouer les thin pools pour des workloads écriture-intensive.
5) Symptom : les VMs sur Ceph saccadent pendant la journée, correctes la nuit
Cause racine : recovery/backfill ou performance OSD inégale pendant la charge diurne ; contention réseau.
Correction : ajustez les limites/ordonnancement de recovery, corrigez les OSD lents, assurez une capacité réseau dédiée pour le stockage, et vérifiez que les réglages client de cache/flush n’exacerbent pas la latence tail.
6) Symptom : VM multi-disques ; la charge d’un disque perturbe l’autre
Cause racine : contrôleur/queue partagé et absence d’isolation iothread ; effets du merge/scheduler IO.
Correction : utilisez virtio-scsi-single, activez iothreads par disque, et séparez les disques par usage (DB vs logs) avec des priorités claires.
7) Symptom : les benchs montrent un excellent débit séquentiel, l’appli saccade toujours
Cause racine : profil de benchmark incorrect ; la charge réelle est de petites écritures aléatoires sync avec exigences strictes de latence.
Correction : testez avec des écritures aléatoires 4k/8k, faible iodepth, et des patterns sync/fsync. Optimisez pour les queues tail, pas pour le MB/s maximal.
Checklists / plan étape par étape
Étape par étape : le plan « corriger la saccade sans jouer avec la durabilité »
- Mesurez d’abord. Capturez
iostat -xde l’invité etiostat -xde l’hôte pendant la saccade. Sauvegardez les sorties dans le ticket. - Vérifiez le type de contrôleur. Si vous êtes en SATA/IDE/LSI émulé sans raison, planifiez un changement vers virtio.
- Choisissez un contrôleur :
- Par défaut, virtio-scsi-single pour les VMs Linux polyvalentes.
- Utilisez virtio-blk si vous voulez simplicité et avez un seul disque occupé, et que vous avez testé la latence.
- Réglez le mode de cache sur
none. C’est l’option performance la plus sûre pour la plupart des systèmes de production. - Activez iothread pour les disques occupés. Commencez par un iothread par disque important.
- Confirmez le mode AIO. Préférez
aio=nativequand supporté et stable ; sinon acceptez threads et comptez sur iothreads. - Vérifiez la mémoire hôte. Si l’hôte swappe, vous aurez des bizarreries IO. Résolvez la pression mémoire.
- Corrections spécifiques au backend :
- ZFS : assurez-vous que le chemin d’écriture sync est assez rapide ; envisagez un SLOG approprié pour les VMs sync-heavy.
- Ceph : assurez-vous que le cluster est sain et n’est pas en recovery ; trouvez les OSD lents.
- LVM-thin : gardez de la marge libre ; surveillez les métadonnées ; gérez le discard.
- Retestez avec de l’IO réaliste. Utilisez fio avec des patterns sync, pas seulement des écritures séquentielles.
- Déployez progressivement. Changez une VM à la fois, validez la latence p95/p99, puis standardisez.
Checklist : ce qu’il faut éviter quand vous êtes fatigué et en astreinte
- Ne pas activer
cache=unsafe. Jamais. - Ne pas définir ZFS
sync=disabledcomme « fix temporaire » sans approbation écrite du risque. - Ne pas tuner uniquement pour le débit ; ce sont les queues tail de latence que les utilisateurs ressentent.
- Ne pas activer discard partout sans comprendre le comportement de votre backend thin.
- Ne pas supposer que « NVMe » signifie « faible latence ». Ça veut dire « plus rapide pour échouer bruyamment quand saturé ».
Exemples de changements rapides (avec commandes)
Ces exemples sont des opérations CLI typiques Proxmox. Faites toujours cela en fenêtre de maintenance et avec un plan de sauvegarde/snapshot adapté à votre backend.
cr0x@server:~$ qm set 103 --scsihw virtio-scsi-single
update VM 103: -scsihw virtio-scsi-single
Ce que cela signifie : définit le modèle du contrôleur SCSI.
Décision : procédez si l’invité supporte virtio et que vous pouvez redémarrer si nécessaire.
cr0x@server:~$ qm set 103 --scsi0 rpool:vm-103-disk-0,cache=none,iothread=1,discard=on
update VM 103: -scsi0 rpool:vm-103-disk-0,cache=none,iothread=1,discard=on
Ce que cela signifie : impose le mode de cache et active iothread sur ce disque.
Décision : après redémarrage, validez la latence invité et le comportement applicatif.
FAQ
1) Dois-je utiliser virtio-scsi-single ou virtio-blk pour les VMs Linux ?
Par défaut, préférez virtio-scsi-single si vous tenez à une latence prévisible et avez plusieurs disques ou des IO mixtes. Utilisez virtio-blk pour des VMs simples à disque unique où vous avez mesuré et constaté que le comportement est bon.
2) cache=none est-il toujours le meilleur choix ?
Pour la plupart des environnements de production Proxmox : oui. Il évite la double mise en cache et réduit les effets secondaires mémoire hôte. Les exceptions sont rares — généralement quand vous utilisez volontairement le cache hôte pour des charges en lecture et que vous avez assez de RAM et de discipline opérationnelle.
3) Pourquoi cache=writeback a rendu ma VM « plus rapide » mais moins stable ?
Parce qu’il acquitte les écritures plus tôt en les mettant en tampon dans la RAM hôte, puis les flush vers le backend plus tard en rafales. Cela peut créer des saccades périodiques et augmente le risque en cas de coupure d’alimentation ou de crash de l’hôte.
4) Les iothreads aident-ils toujours ?
Ils aident souvent la latence et la concurrence, surtout sous charge. Ils peuvent être neutres ou légèrement négatifs pour des très petites charges où l’overhead domine. Activez-les pour les disques qui comptent, pas automatiquement pour chaque disque de chaque VM.
5) Comment savoir si la saccade vient du backend (ZFS/Ceph) ou des réglages VM ?
Comparez la latence du périphérique hôte (iostat -x sur l’hôte) avec la latence de l’invité. Si l’hôte est correct mais l’invité non, suspectez le contrôleur virtuel/queueing/mode cache. Si la latence hôte est mauvaise, corrigez d’abord le backend.
6) Puis-je corriger la saccade ZFS en mettant sync=disabled ?
Vous pouvez « corriger » comme on désamorce une alarme incendie en retirant la pile. Cela échange la durabilité contre la vitesse. Si vous avez besoin de performance avec des charges sync-heavy, utilisez un SLOG approprié avec protection contre la perte d’alimentation ou repensez l’architecture.
7) qcow2 provoque-t-il des saccades ?
Ça peut, surtout sous écritures aléatoires et fragmentation. Si vous avez besoin d’une latence stable pour des bases de données, préférez raw sur un backend bloc (zvol, LV LVM, RBD) sauf si vous avez absolument besoin des fonctionnalités qcow2.
8) Dois-je activer discard/TRIM pour les disques VM ?
Souvent oui pour les SSD en thin provisioning, mais faites-le de façon délibérée. Le discard continu peut créer des rafales de travail backend. Beaucoup d’équipes préfèrent exécuter périodiquement fstrim dans l’invité avec discard activé au niveau virtuel, puis mesurer.
9) Pourquoi mon disque VM s’appelle sda alors que j’ai choisi virtio ?
À l’intérieur de l’invité, le nommage dépend du pilote et du contrôleur. virtio-blk apparaît souvent comme vda. virtio-scsi apparaît souvent comme sda mais utilise toujours le transport virtio. Vérifiez avec lspci/lsmod, pas seulement le nom du périphérique.
10) Quelle est la raison la plus fréquente des saccades « aléatoires » dans Proxmox ?
La contention au niveau hôte : backups, réplication, scrubs, recovery Ceph, ou disque saturé. En deuxième position : cache writeback associé à une pression mémoire créant des orages de flush.
Conclusion : prochaines étapes réalisables aujourd’hui
Si votre VM Linux Proxmox saccade au disque, ne partez pas de la tradition. Commencez par une boucle de mesure courte : vmstat/iostat invité, iostat hôte, et les vrais flags QEMU de la VM. Ensuite appliquez les changements qui améliorent de façon fiable les queues tail :
- Passez des contrôleurs émulés à virtio-scsi-single ou virtio-blk.
- Préférez cache=none sauf si vous pouvez justifier writeback avec protection d’alimentation et garanties opérationnelles.
- Activez iothread pour les disques qui portent la douleur de votre workload.
- Faites des corrections spécifiques au backend plutôt que de blâmer la VM : chemin sync ZFS, santé Ceph, marge des thin pools.
Faites-en une VM, mesurez p95/p99 avant et après, puis standardisez le pattern. Si ça saccade encore après ça, félicitations : vous avez éliminé les causes faciles, et maintenant vous pouvez faire du vrai ingénierie. C’est le boulot.