J'optimise les chemins d'accès au réseau d'un serveur en IRQ Affinity de mapper les files d'attente RX/TX sur les cœurs et de contrôler ainsi la latence, le débit et la gigue p99. Celui qui utilise les processeurs multicœurs de manière conséquente, orchestre les interruptions, les SoftIRQs, NAPI et NUMA de manière à ce que les flux restent affinés au niveau du noyau, que les changements de contexte diminuent et que l'application réagisse de manière mesurable plus rapidement.
Points centraux
- Distribution de l'IRQ détermine quels noyaux portent des interruptions matérielles et évite les zones sensibles.
- Proximité du NUMA réduit les accès à distance et diminue les pics de latence.
- SoftIRQs & NAPI contrôlent le traitement par lots et déchargent les noyaux.
- RPS/RFS maintient les flux proches des fils de consommation.
- Mesure & épinglage rend la performance plus déterministe.
Pourquoi IRQ Affinity compte dans le fonctionnement du serveur
Des débits de paquets élevés surchargent rapidement certains cœurs lorsque toutes les interruptions atterrissent sur quelques CPU, c'est pourquoi je répartis la charge de manière ciblée pour Points chauds d'éviter les problèmes. J'attribue les queues RX/TX aux cœurs appropriés afin que les chemins de données restent courts et que les caches restent chauds. Ainsi, les latences p95/p99 diminuent, car j'évite les migrations inutiles et je conserve les étapes de traitement sur les mêmes cœurs. Je tiens compte de la proximité physique de la carte réseau, des canaux de mémoire et des socles de l'unité centrale afin que le chemin du paquet jusqu'à l'Application-Worker reste constamment rapide. Cette affinité du noyau crée une stabilité mesurable en cas de pics de trafic, sans devoir mettre immédiatement à niveau le matériel.
Équilibrage IRQ vs. affinité fixe
Le service standard irqbalance distribue automatiquement les interruptions, mais il ne connaît pas la logique de mon application, les objectifs NUMA et les budgets de latence. Je fixe les IRQ réseau critiques sur des cœurs sélectionnés, tandis que les interruptions bruyantes ou moins importantes se déplacent vers d'autres cœurs. Ce lien s'harmonise avec l'épinglage des processus d'application, de sorte que le pipeline reste cohérent par flux. En cas de fort trafic, j'évite ainsi les redistributions qui génèrent des surcharges supplémentaires et affaiblissent l'effet de cache. Si vous souhaitez aller plus loin, vous trouverez des informations pratiques dans ce guide : Équilibrage IRQ dans le centre de données.
Affinité CPU, NUMA et le chemin de données court
J'épingle de préférence les Application-Worker et les IRQ de réseau sur le même NUMA-pour que l'accès à la mémoire reste local. Si une carte réseau dépend du nœud 0, je place également les queues RX correspondantes à cet endroit et je lie les processus pertinents à ces noyaux. J'évite ainsi les accès à la mémoire à distance coûteux qui ont un impact important sur la latence lorsque les taux de paquets sont élevés. J'intègre également des paires d'hyperthreading afin que les threads frères ne se perturbent pas mutuellement. Ce triangle formé par l'épinglage des processus, l'affinité IRQ et la topologie NUMA rend les chemins du réseau plus prévisibles et augmente le débit.
Comprendre les SoftIRQs, NAPI et la conception des files d'attente
Après l'interruption matérielle, le noyau prend en charge le traitement en SoftIRQs, souvent sur le même noyau que celui qui a reçu l'IRQ. En cas de charge élevée, je répartis volontairement la charge de l'IRQ logiciel afin de désamorcer les goulets d'étranglement sans fragmenter inutilement le chemin de données. Les NIC multi-queues m'aident, car je peux attribuer à chaque file des cœurs clairement définis et obtenir ainsi une véritable parallélisation. Grâce à NAPI, je traite les paquets par lots afin d'éviter les tempêtes d'interruptions et d'utiliser efficacement le temps de l'unité centrale. Cet article me fournit des informations de fond sur ce chemin : SoftIRQ et débit réseau.
RPS/RFS et localité du flux
J'utilise RPS pour une distribution plus large des paquets et je mets RFS pour que les flux arrivent aux threads qui les consomment. Ainsi, les accès au cache restent efficaces et l'application bénéficie de temps de réponse cohérents. J'harmonise la stratégie de hachage de la carte réseau, le nombre de files d'attente et les ensembles RPS-CPU afin qu'aucune file d'attente du noyau ne déborde. L'affinité de flux agit surtout en cas de nombreuses requêtes courtes, comme celles générées par les API et les microservices. Je construis ainsi un pipeline dans lequel chaque flux touche le plus souvent possible le même noyau et évite les migrations inutiles.
RSS, table d'indirection et XPS : contrôler le hachage de manière ciblée
Pour que la distribution commence proprement à la carte réseau, je règle RSS (Receive Side Scaling) et la table d'indirection de manière à ce que les queues RX soient affectées exactement aux cœurs qui porteront plus tard les threads de l'application. Je veille à ce que le nombre de files d'attente corresponde au nombre de cœurs utilisés et que les clés de hachage restent stables afin que les flux ne se déplacent pas de manière inattendue. Si l'algorithme de hachage change ou si la table d'indirection est écrasée de manière dynamique, cela déchire sinon la localité du flux et favorise les échecs de cache.
Sur le chemin TX, j'active en plus XPS (Transmit Packet Steering), afin que les paquets sortants soient envoyés par le noyau qui traite l'application. De cette manière, les caches TX restent proches du travailleur et le chemin de la file d'attente des sockets à la file d'attente de la carte réseau reste court. Je garde les affectations RX et TX cohérentes, je les documente par interface et je les définis dans les scripts de démarrage afin qu'un redémarrage ne brouille pas l'architecture.
Interrupt Coalescing : mettre en balance la latence et le débit
Avec Coalescence je regroupe les interruptions pour réduire les frais généraux, mais je fais attention aux limites de latence de mon application. Pour le streaming et la VoIP, je laisse les intervalles plutôt courts, alors que les transferts en vrac supportent bien des lots plus longs. Je teste par étapes, mesure p95/p99 et vérifie les drops, les retransmissions ainsi que l'utilisation du CPU par noyau. Ce n'est qu'ensuite que je fixe les paramètres et que je les documente par hôte et par carte réseau. Cet article pratique propose une approche plus approfondie du trade-off : Interrupt Coalescing expliqué.
Bien doser les offloads et l'agrégation
Je mets GRO/LRO pour réduire l'overhead CPU, mais je vérifie si mes charges de travail bénéficient de lots plus importants. Les API sensibles à la latence réagissent souvent mieux lorsque GRO est modéré et LRO désactivé, car les gros super-paquets peuvent aggraver les effets de blocage en tête de ligne. Pour les transferts en vrac, la réplication ou les sauvegardes, j'utilise GRO/GSO/TSO de manière plus agressive, tant que le côté récepteur reste stable et que l'utilisation du CPU diminue.
Téléchargements de checksum et TSO/GSO déchargent considérablement le CPU, mais je m'assure que les middleboxes, les tunnels ou les incompatibilités de déchargement (par exemple pour certaines encapsulations) fonctionnent correctement. Si des anomalies apparaissent, je réduis progressivement certains offloads et je mesure les effets sur le débit, les retransmissions et le temps CPU. L'objectif est d'obtenir un ensemble qui reste stable en largeur et prévisible en pointe.
Isolation du CPU, ordonnanceur et états d'énergie
Pour les budgets de latence difficiles, j'isole des noyaux pour les chemins de réseau et les app-workers. Avec Isolation du CPU et une stratégie de housekeeping allégée, j'évite que les tâches système, les Kthreads ou les interruptions de timer ne se retrouvent sur les cœurs „chauds“. En outre, je fixe le Gouverneur de l'unité centrale sur „performance“ et limite la profondeur États C, Je ne veux pas que cela provoque des latences de réveil. Je garde un œil sur les températures à cœur, car la pourriture thermique peut sinon réduire à néant toute finition.
Le choix de la Classes d'ordonnancement affecte la prédictibilité. Je laisse les threads proches du réseau s'exécuter en priorité, mais sans exclusivité agressive, afin qu'ils ne soient pas en concurrence avec ksoftirqd pour le temps CPU. Je vérifie régulièrement si ksoftirqd s'active sur certains cœurs - un signe clair que la charge de SoftIRQ est trop élevée ou mal répartie.
Busy-polling et chemins à faible latence
Quand les microsecondes comptent, je mets Busy-Polling de manière ciblée. Les applications peuvent définir des fenêtres d'interrogation pour des sockets sélectionnés, de sorte qu'elles tirent des paquets directement des budgets NAPI sans attendre les interruptions. Je choisis des intervalles de poll courts pour ne pas brûler le temps de l'unité centrale et je limite cette technique aux hot paths avec un trafic constant. En parallèle, j'adapte Budgets netdev modérément, afin que les lots soient suffisamment grands sans affamer le reste du système.
Discipline de la file d'attente réseau et pacing
Je mets en place qdisc par interface en fonction de la charge de travail. Avec des disciplines modernes comme fq/fq_codel, je régule le pacing et la longueur des files d'attente pour lisser les bursts et éviter le bufferbloat. Dans les configurations multi-queues, je combine cela avec mqprio, pour que les classes de trafic restent affectées de manière cohérente aux bonnes files d'attente HW. Avec BQL (Byte Queue Limits) sur le pilote permet de réduire les latences à pleine charge, car la file d'attente n'augmente pas de manière incontrôlée.
L'important, c'est l'interaction avec XPS sur le chemin TX : Je mappe les files d'attente d'envoi aux cœurs sur lesquels atterrissent également les flux RX correspondants. Ainsi, les deux directions d'un flux restent proches de l'unité centrale et j'obtiens des temps de réponse plus stables avec les protocoles bidirectionnels (par ex. HTTP/2, gRPC).
Flux de travail du cabinet sous Linux
Je commence par une prise de charge, je vérifie la répartition du CPU dans top/htop, je regarde /proc/interrupts et /proc/softirqs et je lis les statistiques d'ethtool pour identifier les goulots d'étranglement et préparer le prochain Flux de travail-à l'étape suivante. Ensuite, je détermine les IRQ-ID des queues NIC pertinentes et je définis des masques CPU appropriés qui occupent les cœurs de manière égale et qui tiennent compte de NUMA. Ensuite, j'épingle les Application-Worker par taskset ou systemd-CPUAffinity sur les mêmes noyaux que ceux qui servent les files d'attente correspondantes. Je n'active RPS/RFS que là où il renforce la localité du flux et je garde la configuration cohérente par interface. Enfin, je mesure à nouveau le débit, la latence et la gigue avant de déployer les modifications de manière uniforme sur plusieurs hôtes.
Éviter la mesure, p95/p99 et les régressions
Je ne me fie pas à mon instinct, mais je mesure les latences, les taux d'erreur et l'utilisation des cœurs avant et après chaque session de réglage, afin que p99 reste stable. En outre, j'effectue un suivi des changements de contexte, des taux de migration et de la charge par type de SoftIRQ afin de détecter rapidement les effets secondaires cachés. J'assure la reproductibilité des tests, j'utilise des ensembles de données identiques et des versions fixes pour que les résultats restent comparables. Je démasque les régressions à l'aide de tests croisés dans des conditions de pic et d'inactivité, ainsi qu'avec de longs tests d'endurance. Ce n'est que lorsque les métriques, les logs et les traces d'application concordent que je déclare la configuration comme nouvelle situation de référence.
Virtualisation, conteneurs et SR-IOV
Dans les environnements virtualisés, je veille à ce que vCPU, J'ai choisi de placer la mémoire et les vNIC de la VM sur le même nœud NUMA que la NIC physique correspondante. Dans la mesure du possible, j'utilise SR-IOV, Je fais en sorte que le chemin de données soit court et que les IRQ soient directement liées aux noyaux invités. J'épingle les vCPU des machines virtuelles critiques sur des noyaux hôtes dédiés et je veille à ce que les IRQ hôtes et les IRQ invités ne se chevauchent pas. Dans les configurations de conteneurs, je place cpusets et des classes de qualité de service „garanties“, afin que les conteneurs de travail et leurs IRQ réseau obtiennent du temps d'unité centrale de manière planifiable.
Je vérifie si irqbalance doit être le chef de file dans l'invité ou sur l'hôte - un double „automatisme“ produit sinon des imprécisions. Avec virtio, je définis plusieurs files d'attente et les mappe proprement sur les vCPU afin de permettre le travail en parallèle. Si vhost-net charge certains cœurs d'hôtes, je redistribue les backends et garde les threads vhost proches de la NUMA de la carte réseau physique.
Dépistage des erreurs : identifier rapidement les modèles
- Noyaux saturés, ksoftirqd actifs : Épingler plus étroitement les queues RX, vérifier le nombre de queues, adapter RPS/RFS ou augmenter légèrement le coalescing.
- Instabilité de p99 : Vérifier la dérive NUMA, vérifier les C-States/Governor, ajuster progressivement les offloads et les tailles GRO.
- Beaucoup de retransmissions/drops : Contrôler les tailles d'anneau RX/TX, qdisc et BQL, vérifier la cohérence de la table d'indirection et du XPS.
- Flux inégalement répartis : Équilibrer le hash RSS et la table d'indirection, envisager l'épinglage à chaud, maintenir la stabilité du hash seed.
- Problème de VM seule : Placer les backends vhost/virtio à proximité de NUMA, évaluer SR-IOV, désenchevêtrer les IRQ entre hôte et invité.
Intégrer les applications et les bases de données
Un chemin réseau propre ne sert pas à grand-chose si les serveurs d'apps ou les bases de données ne fonctionnent pas en parallèle, c'est pourquoi je configure les Travailleur-Le nombre de threads, les pools de threads et les limites de connexion sont définis sur les cœurs disponibles. J'épingle les workers NGINX ou HAProxy sur les cœurs appropriés pour qu'ils correspondent aux files d'attente RX. Je mets à l'échelle PHP-FPM, Node.js, Java ou Go de manière à ce qu'ils privilégient le domaine NUMA local et utilisent plusieurs instances si nécessaire. J'intègre les caches comme Redis ou Memcached à proximité du CPU et je fais attention à leurs propres paramètres de réseau et de thread. Ce n'est qu'en combinant l'affinité IRQ, l'épinglage des processus et la mise à l'échelle des applications que l'on obtient une amélioration sensible de la latence et du débit.
Des scénarios d'hébergement à forte valeur ajoutée
J'investis surtout dans le tuning profond lorsque les API génèrent un grand nombre de requêtes courtes ou lorsque Temps réel-comme la VoIP et les chats exigent de faibles valeurs de gigue. Les configurations de commerce électronique avec des pics de charge en profitent, car les flux de sortie sont sensibles à la latence. Les hôtes multi-locataires à haute densité sont gagnants, car les cœurs dédiés par file d'attente réduisent les effets de voisinage. Les services de streaming peuvent également obtenir plus de débit par euro sans devoir acheter immédiatement du nouveau matériel. Les coûts restent prévisibles tant que je garde les changements mesurables et que je les déploie de manière ciblée.
Tableau de référence rapide : noyaux, files d'attente, outils
J'utilise la suivante Tableau comme aide-mémoire lorsque je configure de nouveaux hôtes ou recalibre des configurations existantes. Elle montre des objectifs typiques, des mesures appropriées, des outils Linux courants et l'effet prévu sur la latence et le débit. Je ne l'utilise pas de manière dogmatique, mais comme point de départ pour des séries de mesures avec du trafic réel. Si l'architecture de la carte réseau ou la topologie NUMA varie, j'adapte la sélection de base. Il est important de conserver la documentation pour chaque hôte et d'assurer la traçabilité des modifications.
| Objectif | Mesure | Outil/lieu Linux | Effet attendu |
|---|---|---|---|
| Répartir la charge de l'IRQ | Lier les queues aux noyaux | /proc/irq/*/smp_affinity | Moins de points chauds, latence plus constante |
| Augmenter la localité du flux | Définir les ensembles RPS/RFS CPU | /sys/class/net/*/queues/*/rps_cpus | Moins de migrations, de meilleurs caches |
| Contrôler le traitement par lots | Ajuster finement NAPI/Coalescing | ethtool -C / Défauts du pilote | Moins d'overhead, gigue contrôlée |
| Coupler l'application et l'IRQ | Épingler un travailleur | taskset, systemd CPUAffinity | Chemin plus court, p99 plus faible |
| Éviter la NUMA | Co-localiser les appareils et les noyaux | numactl, lscpu, lspci -vv | Moins d'accès à distance, plus de débit |
Des bonnes pratiques qui portent leurs fruits à long terme
Je ne modifie qu'un seul levier de réglage par tour de test, je documente les métriques et je sécurise les Documentation dans le repo de l'hôte. Je garde les configurations cohérentes en décrivant clairement les affectations de file d'attente à cœur et en utilisant des scripts pour la réplication. J'observe les logs pour les drops, les retransmissions et les timeouts et je les corrèle avec les métriques du noyau. J'intègre les niveaux de l'hyperviseur et du stockage dans l'analyse afin qu'il ne reste pas de goulots d'étranglement. Je tiens les rollbacks à disposition si les tests montrent des effets négatifs ou si les charges de travail changent.
En bref
J'obtiens une performance maximale du réseau en utilisant des interruptions, Queues de billard et Worker, ce qui permet de maintenir la stabilité du chemin de données par flux. IRQ Affinity répartit judicieusement la charge matérielle, tandis que SoftIRQs, NAPI et RPS/RFS rendent le traitement efficace. La proximité de NUMA protège contre les détours de mémoire évitables et réduit la gigue. Un réglage progressif avec des mesures reproductibles évite les configurations erronées et montre de réels progrès. Celui qui pense ces éléments ensemble exploite souverainement les capacités des serveurs multicœurs modernes pour les services critiques en termes de latence.


