Avec I/O Scheduler Tuning, j'optimise de manière ciblée le Noyau-pour l'accès à la mémoire et réduire la latence dans les environnements d'hébergement. L'article montre de manière pratique comment j'adapte la planification des disques Linux au matériel et à la charge de travail pour hébergement d'augmenter la performance en toute sécurité.
Points centraux
Les points clés suivants te donneront un aperçu rapide du contenu de cet article.
- Choix de l'ordonnanceur: Noop/none, mq-deadline, BFQ, Kyber selon le matériel et la charge de travail
- stratégie de mesure: Fio, iostat, P95/P99, IOPS et débit avant/après modifications
- Réglages fins: Readahead, RQ-Affinity, Cgroups, ionice pour QoS
- Persistance: règles udev et paramètres GRUB pour les profils permanents
- Cabinet médical: Dépannage des pics de latence, équité et spécificités NVMe
Comment fonctionne la planification des disques Linux
Je considère le programmateur d'E/S comme un centre de commande qui transforme les demandes en Queues de billard trie, fusionne et donne la priorité. Sur les disques durs, j'évite les mouvements de tête coûteux en classant les demandes par adresse de bloc et en réduisant ainsi les temps de recherche. Sur les SSD et NVMe, le parallélisme domine, c'est pourquoi le sous-système multi-files blk-mq rend le chemin plus large et le répartit sur plusieurs CPUs est distribué. Cela permet de réduire les latences, de lisser les pics et de maintenir le débit sur la bonne voie, même si de nombreux services écrivent et lisent en même temps. Dans l'hébergement, les serveurs web, les bases de données et les tâches de sauvegarde se rencontrent, c'est pourquoi j'aligne toujours l'ordonnancement sur les modèles d'accès dominants.
Les programmateurs les plus courants expliqués en bref
Pour NVMe et les disques SSD modernes, je choisis souvent none (équivalent à Noop dans le contexte blk-mq), car le contrôleur optimise en interne et chaque surcharge supplémentaire coûte. mq-deadline fixe des délais fixes pour les lectures et les écritures, donne la priorité aux processus de lecture et fournit des temps de réponse constants dans les charges de serveurs mixtes. BFQ répartit la bande passante de manière équitable sur les processus et convient aux configurations multi-locataires dans lesquelles des VM individuelles occuperaient sinon le disque. Kyber vise des latences faibles et freine les requêtes entrantes lorsque les temps cibles sont dépassés. CFQ est considéré comme un poids mort et ne s'adapte guère à NVMe ; je n'ai recours à CFQ que si des configurations héritées l'exigent ou si des tests montrent des avantages clairs ; je donne ici un aperçu détaillé : Guide du planificateur d'E/S.
Réglage de l'ordonnanceur I/O étape par étape
Je commence avec un objectif clair Ligne de base-pour que je puisse montrer les gains de manière objective. Pour cela, j'utilise fio pour les modèles synthétiques, iostat pour les statistiques des appareils et je collecte les latences P95/P99 pour les lectures et les écritures. Ensuite, je vérifie l'ordonnanceur actif par appareil et le modifie en cours d'exécution afin de procéder à des contre-tests rapides. Je ne mets en place des adaptations persistantes que lorsque les mesures montrent de manière stable que le choix convient. J'évite ainsi de prendre des décisions erronées, qui pourraient ensuite entraîner des retours en arrière coûteux.
# Vérifier le scheduler actuel
cat /sys/block//queue/scheduler
# Changer en cours (exemple : nvme0n1 sur mq-deadline)
echo mq-deadline | sudo tee /sys/block/nvme0n1/queue/scheduler
# Comparaison rapide avec fio (lectures aléatoires 4k)
fio --name=rr --rw=randread --bs=4k --iodepth=32 --numjobs=4 --runtime=60 --filename=/dev/nvme0n1
Je surveille la charge de l'unité centrale, parce qu'un système inadapté planificateur génère des commutateurs contextuels supplémentaires, ce qui fait baisser les performances nettes. Dès que les latences diminuent et que le débit augmente, je sécurise la décision et je documente les profils de test. Chaque étape est suivie d'une modification, puis d'une mesure, afin que je puisse séparer proprement la cause et l'effet. Cette discipline s'avère payante lorsque plusieurs classes de disques sont installées dans le serveur et que les différents appareils réagissent différemment.
Réglages de précision : Readahead, RQ-Affinity, Cgroups
Après avoir choisi l'ordonnanceur, j'ajuste les Queue-paramètres pour la charge. Pour les sauvegardes séquentielles, j'augmente Readahead, pour les IO aléatoires, je le diminue afin de ne pas charger de pages inutiles. Avec RQ-Affinity, je veille à ce que les complétions atterrissent sur le noyau qui a généré la requête, ce qui améliore la latence et la localité du cache. Les processus tels que les sauvegardes et l'indexation sont classés avec ionice, afin que les requêtes web ne souffrent pas. Dans les environnements multi-locataires, je régule la bande passante et les IOPS via Cgroups v2 afin de fixer des limites strictes par client.
# Readahead pour les motifs séquentiels
echo 128 | sudo tee /sys/block//queue/read_ahead_kb
# Affinité RQ : 2 = complétion sur le noyau générateur
echo 2 | sudo tee /sys/block//queue/rq_affinity
# Abaisser le processus de sauvegarde
ionice -c2 -n7 -p
# Cgroup v2 : poids et limite (exemple major:minor 8:0)
echo 1000 | sudo tee /sys/fs/cgroup/hosting/io.weight
echo "8:0 rbps=50M wbps=25M" | sudo tee /sys/fs/cgroup/hosting/io.max
Quel choix convient aux profils d'hébergement ?
Je décide de la planificateur-choix en fonction de la classe de matériel, du modèle d'accès et de la taille cible (latence vs. débit vs. équité). Les SSD NVMe dans les VM à un seul locataire profitent généralement de none, car le contrôleur prend en charge une optimisation étendue et chaque couche logicielle compte. Pour les charges mixtes de lecture/écriture sur les SSD, je mise souvent sur mq-deadline, car il donne la priorité aux demandes de lecture et protège ainsi les temps de réponse. Dans les environnements d'hébergement partagés, je choisis BFQ pour garantir l'équité entre les clients et éviter les monopoles de bande passante. J'utilise Kyber lorsque les temps de latence cibles sont critiques et que je dois respecter des limites strictes pour certaines charges de travail.
| planificateur | Matériel informatique approprié | Charges de travail typiques | Avantages | Remarques |
|---|---|---|---|---|
| Noop/aucun | NVMe, SSD moderne | Beaucoup de lectures/écritures parallèles, VMs | frais généraux minimes, parallélisme élevé | Le contrôleur prend en charge le tri ; tester dans SAN/RAID |
| mq-deadline | SSD, SAS, disque dur rapide | Charges mixtes Web/DB | Latence de lecture priorisée, bon débit | Valeurs deadline conservatrices ; possibilité de réglage fin |
| BFQ | SSD/HDD en multitenant | Beaucoup d'utilisateurs, cgroups | Une équité claire et un contrôle de la bande passante | Un peu de travail administratif, pondérer proprement |
| Kyber | SSD, NVMe | Services critiques en termes de latence | Latences cibles contrôlables | Mesurer avec précision pour définir correctement le throttling |
| CFQ | Matériel hérité | Anciennes charges de travail | Ancienne solution standard | Rarement utile sur un NVMe/SSD moderne |
Profils et valeurs de mesure pratiques
Pour les serveurs web avec de nombreux petits Lire la latence P95 compte plus que les IOPS purs, c'est pourquoi je teste les requêtes Get avec Keep-Alive et TLS en interaction. Les bases de données font intervenir des écritures de synchronisation, c'est pourquoi je simule ici le comportement de flush et les coûts de fsync avec des jobfiles fio. Les fenêtres de sauvegarde ont souvent des flux séquentiels ; ici, je mesure le débit en MB/s et je veille à ce que les requêtes frontales n'attendent pas trop longtemps. Dans mes tests, je vois des temps de réponse de 20 à 50 % plus courts, selon la situation de départ, si l'ordonnanceur et le readahead correspondent aux charges de travail. Si vous avez besoin de plus de contexte pour mesurer le débit des disques, vous trouverez ici une introduction : Débit disque dans l'hébergement.
Configuration et automatisation persistantes
J'ancre les Choix de manière permanente par règle udev, afin que les périphériques démarrent directement dans le mode approprié après un redémarrage. Pour NVMe, je mets souvent none, pour les SSD mq-deadline et pour les supports rotatifs BFQ, si l'équité est au premier plan. Sur GRUB, je mets en option une valeur par défaut globale si j'exploite une configuration homogène. Je garde les règles concises et je les documente dans le référentiel de configuration pour que l'équipe puisse les suivre. Pour une optimisation plus approfondie du noyau, cette contribution complète la configuration : Performance du noyau dans l'hébergement.
# /etc/udev/rules.d/60-ioschedulers.rules
# NVMe : none
ACTION=="add|change", KERNEL=="nvme[0-9]*", ATTR{queue/scheduler}="none"
# SSDs : mq-deadline
ACTION=="add|change", KERNEL=="sd[a-z]|vd[a-z]", ATTR{rotationnel}=="0", ATTR{queue/scheduler}="mq-deadline"
# HDDs : BFQ
ACTION=="add|change", KERNEL=="sd[a-z]", ATTR{rotational}="1", ATTR{queue/scheduler}="bfq"
# Recharger/tester les règles
udevadm control --reload
udevadm trigger
# Défaut global optionnel via GRUB
# /etc/default/grub
GRUB_CMDLINE_LINUX_DEFAULT="elevator=mq-deadline"
update-grub
QoS avec Cgroups v2 et ionice
Pour qu'aucun travail n'affecte la plaque bloque, Pour les utilisateurs de l'Internet, je mise sur les règles de QoS avec Cgroups v2 et j'ajoute des priorités via ionice. Pour les ténors, j'augmente io.weight, tandis que pour les voisins bruyants, je fixe des limites strictes avec io.max. Je lie les unités Systemd directement aux Cgroups, de sorte que les services se glissent automatiquement dans la bonne classe au démarrage. Je limite temporairement les travaux de maintenance à court terme afin que les demandes des clients continuent à être traitées de manière fluide. Cette interaction entre la pondération, les limites et la priorité des processus permet d'obtenir des temps de réponse prévisibles, même sous charge.
# Créer un cgroup et définir des limites
mkdir -p /sys/fs/cgroup/hosting
echo 1000 | tee /sys/fs/cgroup/hosting/io.weight
echo "8:0 rbps=100M wbps=60M" | tee /sys/fs/cgroup/hosting/io.max
Déplacer le processus # dans le Cgroup
echo | tee /sys/fs/cgroup/hosting/cgroup.procs
# Priorité d'E/S basse pour les tâches secondaires
ionice -c2 -n7 -p
Surveillance et dépannage
Je considère toujours la télémétrie proche sur les charges de travail, sinon je rate des décisions. Avec iostat, je lis les temps de service et les profondeurs de file d'attente, avec blktrace, j'analyse les flux de requêtes, et avec sar/dstat, je vois la charge du système au fil du temps. Pour les latences, je ne regarde pas seulement les valeurs moyennes, mais toujours P95/P99, car c'est là que les hakers sensibles sont visibles. Si P95 est bon, mais que P99 se dégrade, j'ajuste la profondeur de la file d'attente ou l'affinité RQ et j'examine les tâches concurrentes. Après chaque correction, je compare les mêmes indicateurs pour que l'effet reste fiable.
Points d'achoppement typiques et remèdes
Haute Latence sur les SSD indique souvent un ordonnanceur inadapté ; je teste alors immédiatement mq-deadline et vérifie si les lectures sont plus rapides. Je résous les répartitions inéquitables dans les configurations multi-locataires à l'aide de BFQ et de pondérations de cgroupes claires, afin que les clients forts n'évincent pas les plus faibles. Les timeouts NVMe indiquent un micrologiciel ou un polling trop agressif ; dans de tels cas, je désactive io_poll et je baisse la profondeur jusqu'à ce que la stabilité revienne. Les fluctuations de débit dans les fenêtres de sauvegarde peuvent souvent être lissées avec un readahead adapté, en particulier lorsque les fichiers volumineux dominent. Si plusieurs facteurs tournent en même temps, je procède par étapes : un changement, puis des mesures, puis le suivant.
Paramètres de planification détaillés
Une fois le choix de base effectué, je tourne les vis de réglage de chaque ordonnanceur. Je commence toujours par regarder les paramètres disponibles par périphérique, car ils varient selon le noyau et la distro.
# Afficher les tunables disponibles
ls -1 /sys/block//queue/iosched
cat /sys/block//queue/iosched/*
# Exemple : mq-deadline plus conservateur pour les travaux à forte charge d'écriture
echo 100 | sudo tee /sys/block//queue/iosched/read_expire
echo 500 | sudo tee /sys/block//queue/iosched/write_expire
echo 1 | sudo tee /sys/block//queue/iosched/front_merges
# Exemple : BFQ pour une équité plus stricte et des temps d'inactivité plus faibles
echo 1 | sudo tee /sys/block//queue/iosched/low_latency
echo 0 | sudo tee /sys/block//queue/iosched/slice_idle
Chez mq-deadline, je régule surtout read_expire/write_expire (en millisecondes) et front_merges pour la fusion des requêtes en attente. Pour BFQ, je commute selon la densité de Tenant faible latence et slice_idle, pour réduire les temps d'attente entre les flux. Je documente chaque modification avec des valeurs de mesure, car des expirations erronées peuvent déclencher des pics de latence involontaires en cas de charge en rafale.
Options de système de fichiers et de montage
Le réglage de l'ordonnanceur ne se déploie vraiment que si le système de fichiers s'y prête. Je fais attention à
- relatime/noatime: éviter les accès inutiles en écriture aux métadonnées.
- discard vs. fstrim: Sur les SSD/NVMe, j'utilise généralement fstrim périodique au lieu de Online-Discard pour éviter les pics de latence.
- JournalingPour ext4, les options suivantes ont fait leurs preuves data=ordonné (Default) et un commit=-(par ex. 10-30s selon la tolérance de perte de données).
- Barrières: les barrières d'écriture restent actives ; je ne les désactive pas, sauf si le matériel garantit une protection contre les coupures de courant (batterie/capacitor).
# Exemple de /etc/fstab pour ext4
UUID= /data ext4 defaults,noatime,commit=20 0 2
# Activer le TRIM périodique au lieu de l'option discard
systemctl enable fstrim.timer
systemctl start fstrim.timer
Pour XFS, je définis également noatime et je préfère fstrim.timer. Les options de journal ou de barrière dépendent de la distribution ; je teste toujours la combinaison concrète noyau/FS et mesure P95/P99.
RAID, LVM, DM-crypt et Multipath
Dans les configurations empilées (Device Mapper, LVM, mdraid, Multipath), je définis l'ordonnanceur à l'endroit où l'application voit les E/S, c'est-à-dire au niveau de l'interface utilisateur. Périphérique de premier niveau - et empêche le double tri en dessous.
# Définir le planificateur au niveau supérieur (par ex. dm-0)
echo mq-deadline | sudo tee /sys/block/dm-0/queue/scheduler
# Périphériques NVMe/SAS subordonnés "none" pour éviter le double scheduling
for d in /sys/block/nvme*n1 /sys/block/sd* ; do echo none | sudo tee $d/queue/scheduler ; done
# mdraid : optimiser le readahead et le cache de bandes (RAID5/6)
sudo blockdev --setra 4096 /dev/md0
echo 4096 | sudo tee /sys/block/md0/md/stripe_cache_size
Pour les volumes cryptés (dm-crypt/LUKS), je fais attention à la charge de travail du CPU (AES-NI) et à ce que le chemin d'E/S ne se déplace pas inutilement à travers les files d'attente. Je mesure de manière ciblée les latences de sync-write, car elles peuvent augmenter plus fortement en raison de la couche crypto. Dans les environnements multipath (SAN/iSCSI), je place le planificateur sur le périphérique multipath (dm-X) et je vérifie que le basculement de chemin ne génère pas de valeurs aberrantes.
Virtualisation et conteneurs : hôte vs invité
Dans la pile KVM, je sépare délibérément l'hôte et l'invité. Dans Invité pour les appareils virtio, je mets en général none, pour que l'hyperviseur prenne en charge l'optimisation. Sur le Hôte je choisis alors pour chaque périphérique physique le scheduler adapté au matériel (souvent none/mq-deadline sur SSD/NVMe).
# Invité (virtio-blk/virtio-scsi) : Mettre le scheduler "none".
echo none | sudo tee /sys/block/vda/queue/scheduler
# hôte : QEMU avec iothreads et multi-tranches pour virtio-blk
qemu-system-x86_64 \
-drive if=none,id=vd0,file=/var/lib/libvirt/images/guest.qcow2,cache=none,aio=native \
-objet iothread,id=ioth0 \
-device virtio-blk-pci,drive=vd0,num-queues=8,iothread=ioth0
Je lie les conteneurs directement à Cgroups v2 et j'utilise les propriétés systemd (IOWeight, IOReadBandwidthMax/IOWriteBandwidthMax) pour que les services démarrent automatiquement avec les budgets E/S corrects. Important : ne donner la priorité qu'à un seul niveau - soit dans le conteneur, soit dans le service hôte - afin d'éviter des règles contradictoires.
Optimisation NUMA, IRQ et Polling
Sur les systèmes multi-sockets, je considère que les E/S et le CPU sont des éléments importants. NUMA-proche. Je vérifie la répartition des interruptions NVMe et l'ajuste si nécessaire si irqbalance ne fonctionne pas de manière optimale. De plus, j'utilise les options blk-mq pour garder les complétions en local.
# Vérifier les interruptions NVMe et définir les masques du noyau (exemple)
grep -i nvme /proc/interrupts
echo | sudo tee /proc/irq//smp_affinity
# blk-mq : complétions sur le noyau en construction
echo 2 | sudo tee /sys/block//queue/rq_affinity
# Facultatif : tester le polling d'E/S en fonction de la charge de travail (à utiliser avec précaution)
echo 0 | sudo tee /sys/block//queue/io_poll
Pour NVMe, je peux adapter les paramètres de coalescence des interruptions via les fonctions du contrôleur afin de lisser le rapport entre la charge du processeur et la latence. Je tâtonne ici par petites étapes et vérifie si P99 reste stable ou si le coalescing entraîne une inertie visible.
Exemples de jobfiles fio et de plan de mesure
Je crée des fichiers de tâches reproductibles et je note le noyau, le planificateur, les paramètres de la file d'attente et les montages du système de fichiers. Cela permet de comparer les résultats sur plusieurs semaines.
# db-sync.fio - Écritures de synchronisation de type DB (ext4/XFS)
[global]
ioengine=libaio
direct=1
filename=/dev/
time_based=1
runtime=90
thread=1
numjobs=8
iodepth=1
[randwrite-sync4k]
rw=randwrite
bs=4k
fsync=1
# web-randread.fio - Lectures de type web
[global]
ioengine=libaio
direct=1
filename=/dev/
time_based=1
runtime=90
thread=1
numjobs=8
iodepth=32
[randread-4k]
rw=randread
bs=4k
# Cadre de mesure
# 1) échauffement 60s, 2) mesure 90s, 3) cooldown 30s
# En parallèle : faire tourner iostat, pidstat et blktrace
iostat -x 1 | tee iostat.log &
pidstat -dl 1 | tee pidstat.log &
blktrace -d /dev/ -o - | blkparse -i - -d trace.dump &
# Post-traitement : tirer P95/P99 de fio-JSON
fio --output-format=json --output=fio.json db-sync.fio
jq '.jobs[].lat_ns["percentile"]|{p95:.["95.000000"],p99:.["99.000000"]}' fio.json
Je ne modifie qu'une variable à la fois, par exemple Scheduler ou read_ahead_kb, et je compare à nouveau les jobfiles identiques. Ce n'est que lorsque les améliorations sont cohérentes sur plusieurs exécutions que je fixe les paramètres.
Gestion du changement : introduire et réintroduire en toute sécurité
Dans les environnements d'hébergement de production, je répercute les modifications d'E/S sur les utilisateurs. échelonné un hôte Canary, puis une petite partie AZ/Cluster, ensuite seulement le déploiement à grande échelle. Je versionne les règles Udev et j'attache chaque modification à un ticket avec des valeurs de mesure. Pour le rollback, je tiens à disposition un script qui joue les valeurs précédentes (scheduler, read_ahead_kb, limites de Cgroup). Ainsi, les interventions restent réversibles lorsque les charges de travail changent à court terme.
Résumé : Voici comment je procède
Je commence avec un objectif clair Valeur réelle, Je mesure les latences et le débit et je documente la configuration. Ensuite, je choisis un ordonnanceur approprié pour chaque périphérique : none pour les SSD NVMe/virtuels, mq-deadline pour les charges de serveur mixtes, BFQ pour les environnements partagés avec de nombreux utilisateurs. Ensuite, je peaufine le readahead, l'affinité RQ et les priorités des processus afin de donner la priorité aux charges de travail frontales. Lorsque les mesures montrent que le choix est cohérent, je le fixe via udev/GRUB et j'écris les paramètres. Le monitoring reste actif, car les charges de travail changent et, avec de petites corrections, je maintiens les Performance durablement élevé.


