Je montre comment softirq cpu avec NAPI, la répartition IRQ et la conception de la file d'attente, limite ou libère le débit du réseau dans l'hébergement. Avec des points de mesure clairs, un réglage ciblé et des affinités propres, je réduis Latence et augmente de manière cohérente le débit pps sur les serveurs de production.
Points centraux
Ces idées centrales transportent efficacement les paquets réseau via le CPU, le noyau et la carte réseau - et maintiennent les temps de réponse constant bas.
- Budget du NAPI un réglage fin : Un plus grand nombre de paquets par poll réduit l'overhead et lisse la qualité. Charge CPU.
- Équilibrage de l'IRQ et affinité : éviter les points chauds, augmenter les occurrences de la cache, Pics de latence Appuyez sur .
- Multi-queues, RSS/RPS/XPS : paralléliser les flux, préserver l'alignement NUMA, pps soulever.
- Offloads utiliser en connaissance de cause : Évaluer GRO/LRO, TSO, Coalescing, Jitter garder à l'esprit.
- Isolation et Busy Polling : temps de réponse prévisibles sur des sites dédiés Noyaux.
Principes de base : ce qui se passe dans le noyau lors du trafic réseau
Un paquet atterrit d'abord dans une interruption matérielle, puis le noyau prend le relais dans SoftIRQs et des boucles NAPI Poll. Je veille à ce que la phase rapide de HardIRQ reste vraiment courte et que la logique proprement dite se déplace dans le bon contexte, afin que les temps CPU ne s'évapore pas. Les threads ksoftirqd n'interviennent que lorsque le traitement direct n'est pas possible, ce qui conduit rapidement à des files d'attente en cas de charge continue. C'est précisément là que se produit le temps d'attente, qui se traduit par une augmentation du TTFB et un débit fluctuant. Pour ceux qui souhaitent aller plus loin, des connaissances pratiques sur le traitement des IRQ sont disponibles dans cet article sur Gestion des interruptions et performances du CPU, que j'utilise pour le classement.
NAPI, SoftIRQs et ksoftirqd : contrôler la latence au lieu de la gérer
NAPI réduit les tempêtes d'interruptions en récupérant plusieurs paquets par passage dans un budget défini et en réduisant ainsi Overhead diminue la qualité du service. Si le budget n'est pas suffisant, les paquets s'accumulent, ksoftirqd chauffe et les Latence augmente de manière mesurable. Dans de telles situations, je vérifie systématiquement /proc/softirqs et /proc/net/softnet_stat pour mettre en évidence les drops, time_squeeze ou les files d'attente qui débordent. Ensuite, j'augmente progressivement net.core.netdev_budget ou net.core.netdev_budget_usecs et j'observe en parallèle la charge CPU, la répartition p95/p99 et les pertes de paquets. Tout l'art consiste à effectuer suffisamment de travail par poll sans supplanter l'exécution interactive des threads userland.
Équilibrage IRQ et affinité : éviter les hotspots, augmenter les hits en cache
Un seul noyau avec toutes les IRQ de la carte réseau devient un goulot d'étranglement, car il doit servir à la fois les interruptions, les IRQ logicielles et les threads d'applications ; je distribue donc IRQs de manière ciblée. Le service irqbalance aide, mais pour les débits pps élevés, je mappe explicitement les queues RX/TX par affinité sur des queues appropriées. noyaux. Sur les systèmes NUMA, je lie les files d'attente aux cœurs du même nœud afin d'éviter les accès à la mémoire à distance. Les threads d'application s'exécutent sur des cœurs voisins mais séparés, ce qui améliore la localité du cache et la prévisibilité. Un bon aperçu de la répartition stratégique est fourni par ce guide sur le Équilibrage IRQ dans le centre de données, Je l'utilise comme référence pour les finitions.
Multi-files, RSS/RPS/XPS : bien utiliser la parallélisation
Les NIC modernes apportent plusieurs files d'attente RX/TX, que je peux gérer par le biais de RSS sur les flux et obtenir ainsi un véritable parallélisme. Si la carte n'offre pas assez de files d'attente, j'utilise le logiciel RPS/XPS pour répartir les paquets de manière judicieuse sur les flux. noyaux de pousser les flux. Il est important de répartir proprement le hachage afin qu'un flux reste toujours sur la même unité centrale et qu'il n'y ait pas de distorsions coûteuses du cache. En même temps, je garde les chemins TX et RX proches les uns des autres afin d'éviter la contention et les accès cross-node inutiles. Ainsi, le débit pps augmente sans qu'un seul cœur ne freine.
L'affinité CPU jusque dans l'espace utilisateur : penser de bout en bout
Je planifie le chemin des données depuis l'IRQ NIC jusqu'aux threads des travailleurs de l'application en passant par les files d'attente NAPI, afin que les paquets arrivent à destination sans crochets inutiles et que les Temps de réponse reste constant. Pour cela, je sépare systématiquement les noyaux pour les interruptions/SoftIRQs des noyaux d'applications et je définis des règles claires pour les applications. Affinity-Les règles d'utilisation. Les serveurs web, les proxies inversés et les bases de données reçoivent des ensembles fixes de CPU, proches des noyaux IRQ, afin de réduire les trajets. De plus, je place le CPU-Governor sur performance, afin que les changements d'horloge n'introduisent pas de gigue dans p99. Cette affectation cohérente rend le comportement prévisible et aide à diagnostiquer proprement les goulots d'étranglement.
Offloads, GRO/LRO, pare-feu et eBPF : économiser la charge sans voler à l'aveuglette
Économiser le checksum-offload, le TSO et le coalescing temps CPU, Mais ils peuvent modifier la taille des paquets, le comportement des bursts et la gigue, c'est pourquoi je mesure les effets de manière ciblée. GRO/LRO regroupe les trames et allège la pile, mais pour les exigences en temps réel, je décide en fonction de la situation de Désactivation ou une utilisation limitée. Les tables Conntrack et les chaînes profondes de nftables/iptables coûtent des mesures, je nettoie donc les règles superflues et simplifie les chemins. Si nécessaire, j'ai recours à eBPF (XDP, tc-BPF) pour prendre des décisions précoces au niveau de la carte réseau et éviter les chemins coûteux. Un bon point de départ pour la pratique du réglage fin est cet aperçu de Coalescence d'interruption, Je tiens compte de ces éléments pour les budgets de latence sensibles.
Busy Polling et isolation du CPU : fixer les temps de réaction
Pour les objectifs de latence difficile, j'utilise le busy polling pour que les sockets userspace récupèrent les paquets encore plus tôt et Temps d'attente de temps. Cela augmente la charge, mais me donne des distributions p99 très étroites pour les charges de travail API ou de trading sur des serveurs dédiés. Noyaux. De plus, j'isole les cœurs avec isolcpus=, nohz_full= et rcu_nocbs=, de sorte que les temporisateurs, RCU et services système ne fonctionnent que sur les CPU de gardiennage. Cette séparation empêche les perturbations sur les noyaux de latence et rend le comportement reproductible. En somme, il en résulte une feuille de route claire : des cœurs dédiés, une collecte précoce des paquets, des budgets définis.
Surveillance et dépannage : du symptôme à la cause
Je commence avec pps, débit et charge de base, puis je vérifie les drops et l'activité des ksoftirqd-threads au fil du temps pour identifier avec certitude des modèles. Des outils comme sar, htop, ss, nload et ethtool me montrent quand et où se forment les embouteillages et si les Queues de billard atteignent leurs limites. Il est important d'avoir des répartitions plutôt que des moyennes, afin que les pics du soir, les fenêtres Cron ou les campagnes ne soient pas noyés. Je corrèle les pics TTFB avec la répartition de l'IRQ, le budget NAPI et les paramètres de déchargement afin d'agir de manière ciblée sur les vis de réglage. Souvent, il suffit d'adapter l'affinité IRQ ou de redéfinir le budget NAPI pour réduire sensiblement les temps morts.
Aperçu des paramètres de réglage
L'aperçu suivant m'aide à utiliser les modifications de manière réfléchie et à attribuer clairement les effets avant de procéder à des modifications permanentes. déploiements planifie les choses. Je vérifie chaque adaptation de manière itérative, je mesure les distributions de latence et j'observe les effets secondaires sur CPU et de la mémoire. Je ne modifie qu'un point à la fois par fenêtre de test, afin que la cause et l'effet restent clairs. Ensuite, je documente les résultats et je définis des seuils d'alerte. J'obtiens ainsi des améliorations reproductibles sans risquer de surprises dans le trafic productif.
| Paramètre/fonction | Effet dans le chemin de données | Quand lever/activer | Risques/effets secondaires |
|---|---|---|---|
| net.core.netdev_budget | Plus de paquets par poll NAPI | Pour les drops dans softnet_stat | Les sondages plus longs supplantent les fils de discussion des utilisateurs |
| net.core.netdev_budget_usecs | Limiter la fenêtre de temps par poll | En cas de gigue due à de grandes salves | Trop petit : plus de changements de contexte |
| RSS/RPS/XPS | Répartir les flux sur les noyaux | Pour les hotspots sur un noyau | Mauvais hashs : distorsions de la mémoire cache |
| Affinité IRQ | Lier les IRQ de manière ciblée à proximité du noyau | Chez NUMA-Missmatch | Une mauvaise affectation crée de nouvelles zones sensibles |
| GRO/LRO/TSO | Réduit le nombre de paquets | En cas de bottleneck du CPU | Jitter, bursts plus importants |
| Polling busy | Collecte précoce des colis | Pour des cibles p99 difficiles | Consommation accrue du CPU |
Anneaux RX/TX et profondeur de la file d'attente : bien dimensionner les tampons
Même avec des IRQ bien répartis et des budgets adaptés, des anneaux de carte réseau trop petits ou trop grands peuvent faire baisser les performances. C'est pourquoi je vérifie la taille des anneaux RX/TX de la carte et les adapte au caractère des bursts et aux objectifs de latence. Des anneaux trop petits entraînent des drops dans la carte réseau lors des pics de trafic, visibles sous forme de rx_missed_errors ou fifo_errors dans les statistiques du pilote. Les anneaux trop grands masquent les congestions, augmentent la latence et génèrent de longues traînées dans p95/p99. Je cherche le juste milieu : assez de tampons pour absorber les courtes rafales, mais pas assez pour que les paquets “vieillissent” dans les files d'attente.
En complément, je considère le côté hôte tx_queue_len et le Qdisc utilisé. Avec sch_fq ou fq_codel, je peux lisser le comportement des bursts et répartir les gros paquets TSO via le pacing. Cela réduit les micro-bursts au niveau du port de commutation et rend la courbe de latence plus calme - ce qui est important pour les charges de travail mixtes dans lesquelles de petits RPC sont exécutés à côté de gros uploads. Ce faisant, j'observe les statistiques d'ethtool et je les corrèle avec softnet_stat pour voir si les congestions se produisent dans l'anneau NIC, dans le backlog netdev ou dans le Qdisc.
MTU, Jumbo Frames et segmentation
Le MTU est un levier classique qui est souvent sous-estimé. Les trames jumbo réduisent le nombre de paquets par Gbit/s et déchargent l'unité centrale - mais uniquement si le chemin est vraiment compatible avec les trames jumbo de bout en bout. C'est pourquoi je valide systématiquement les contreparties, les commutateurs et les tunnels. Dès que l'on fragmente à nouveau à 1500 quelque part, des problèmes de MTU de chemin, des retransmissions et des coûts inutiles menacent. Jitter. Dans les centres de données où les communications Est/Ouest dominent, une stratégie homogène 9k est rentable, tandis que pour les charges de travail Internet, 1500 est souvent le choix le plus stable.
J'évalue toujours le MTU en interaction avec TSO/GSO/GROUn regroupement trop agressif peut conduire à de grandes rafales dans le TX, qui remplissent les tampons en amont et génèrent des pics de latence. L'objectif est d'obtenir un chemin cohérent : une segmentation judicieuse à l'émetteur, des mécanismes de pacing suffisants et un GRO qui économise du travail du côté du récepteur sans contrecarrer les exigences en temps réel.
UDP, QUIC et charges de travail en streaming : tenir compte des spécificités
Tout le trafic n'est pas TCP. UDP-Les profils à forte charge (DNS, VoIP, QUIC, télémétrie) se comportent différemment dans RSS/RPS et GRO. Les piles modernes supportent UDP-GRO/GSO, ce qui peut soulager le CPU - je l'utilise de manière sélective et je mesure si les risques de réordonnancement ou la gigue augmentent. Pour les charges QUIC/HTTP3, une répartition propre des flux est décisive : RPS peut aider si la carte réseau n'offre pas assez de files d'attente RSS, mais ne doit pas “balancer” des flux de cache à chaud. Côté TX, je mets XPS afin de regrouper les chemins de transmission et de réduire la contention. Dans la pratique, une affectation calme et affinée au niveau du noyau est particulièrement payante dans le cas de nombreux flux UDP de taille moyenne, pour lesquels chaque succès de la mémoire cache compte.
Virtualisation et conteneurs : intégrer proprement l'hôte, l'invité et le vhost
Dans les environnements virtualisés, le travail se déplace entre l'hôte, les threads vhost et les IRQ invités. Je veille à ce que vhost-net-Les threads doivent avoir leurs propres noyaux et ne pas entrer en collision avec les App-Works. Leurs affinités doivent correspondre aux files d'attente physiques RX/TX, sinon il y a des migrations croisées inutiles entre les processeurs. Dans l'invité, je vérifie les files d'attente virtio-net, j'active la multi-files d'attente et je configure RSS/RPS de manière analogue au bare metal. Là où la latence et le pps sont au premier plan, on peut SR-IOV réduire encore l'overhead - à condition que la topologie NUMA soit cohérente : VF, vCPU et mémoire appartiennent au même nœud.
Dans la pile de conteneurs, les réseaux superposés, les chaînes NAT profondes et les topologies CNI complexes provoquent des sauts supplémentaires. Pour les services critiques en termes de latence, je préfère hostNetwork ou des réseaux légers (macvlan/ipvlan), j'égare les chemins NAT et je garde Conntrack aussi petit que possible. Il est important d'avoir une stratégie CPU cohérente : les cœurs IRQ et NAPI de l'hôte doivent se trouver à proximité des cœurs sur lesquels fonctionnent les travailleurs vhost/container - c'est la seule façon de conserver un chemin de données court et planifiable.
Ordonnancement, C-States et IRQ-Threading
Parce que la latence n'est pas seulement un temps de calcul, mais aussi un temps d'attente. Temps de réveil je minimise les C-States profonds sur les noyaux de latence. Un powersave agressif peut coûter des millisecondes avant qu'une SoftIRQ ne soit réellement exécutée. Je mise donc sur le gouverneur de performance, limite les C-States profonds et maintiens la cohérence du turbo afin de rendre les sauts de fréquence prévisibles. Il est également important de Threading IRQLà où les pilotes le permettent, je déplace le travail dans les threads IRQ et j'attribue des priorités de telle sorte que RX démarre avant le travail en aval, sans pour autant supplanter complètement Userland. L'interaction entre les politiques de planification, les affinités et les budgets est délicate ; je teste par étapes, j'enregistre p99 et je fais attention aux interférences avec ksoftirqd, qui devient sinon un goulot d'étranglement secret.
Observation en profondeur : points de trace, compteurs, histos
Si les métriques restent vagues, je descends d'un cran : j'utilise des points de trace du noyau autour de netif_receive_skb, napi_poll et net_dev_queue, pour voir les durées des polls, les quantités de paquets et les temps d'attente sous forme d'histogrammes. De telles distributions montrent si 1 % des polls durent trop longtemps ou si des files d'attente individuelles s'échappent. En complément, ethtool-rx/tx-Counters, TCP Retransmits, Busy Poll Hits et softnet_stat indiquent clairement où les paquets sont perdus. Grâce à l'analyse drop, je peux voir si la carte d'interface réseau est en train d'abandonner (anneau plein), si le backlog netdev s'effondre (time_squeeze) ou si Qdisc/Firewall freine. Ce n'est que lorsque ces pièces du puzzle s'assemblent que j'agis sur les anneaux, les budgets ou les offloads.
Épurer les chemins de sécurité et de filtrage
Les ACL complexes, les chaînes profondes de nftables/iptables et les larges tables de conntrack ajoutent une latence constante par paquet. Je consolide les règles, travaille avec des ensembles/maps et déplace les drops génériques le plus en amont possible dans le chemin - idéalement le plus tôt possible à la NIC (XDP/clsact) lorsque la latence est critique. Les flux sans état, la télémétrie ou les ports “sûrs” connus peuvent être utilisés de manière ciblée. sans suivi afin d'éviter les recherches coûteuses. En même temps, je garde les tables d'état fraîches, j'adapte les tailles de hachage aux pics de charge et je nettoie agressivement les entrées orphelines. L'objectif est d'obtenir un chemin de politique propre et compréhensible, qui ne se manifeste pas dans le profil comme une charge permanente.
Les anti-modèles typiques et comment je les évite
- Tous les IRQ sur un noyau : entraîne une congestion et un ksoftirqd chaud. Antidote : affinités ciblées par file d'attente, NUMA-cohérent.
- Maximiser aveuglément les bagues/budgets : masque la congestion, augmente les queues de latence. Antidote : augmenter de manière incrémentielle, mesurer les distributions.
- Configuration de hachage de flux imprécise : Les flux sautent entre les cœurs, les caches s'évaporent. Remède : clés RSS stables, RPS/XPS uniquement avec un objectif clair.
- Threads d'applications sur les mêmes cœurs que les SoftIRQ : Interférence et gigue. Contre-mesures : séparation dure, affectation à des voisins.
- Overlays/NAT sans budget : s'ajoute à chaque saut. Remède : alléger les chemins, réseaux proches de l'hôte pour les charges de travail à latence.
- Économie d'énergie sur les noyaux de latence : les C-States profonds ralentissent la réaction. Antidote : gouverneur de performance, limitation des C-states.
- Offloads sans mesure : TSO/GRO peuvent aggraver les bursts et la gigue. Antidote : activer de manière spécifique à la charge de travail, observer p99.
Pratique de l'hébergement : des étapes qui font leur effet
Je commence par une phase de mesure propre, j'établis des lignes de base et je maintiens toutes les modifications à un niveau faible dans des fenêtres de temps courtes afin de pouvoir Causes peut séparer. Ensuite, j'active irqbalance, je vérifie la répartition automatique et, si nécessaire, je définis des affinités manuelles jusqu'à ce qu'il n'y ait plus de Points chauds ne sont plus visibles. Ensuite, je mets en place la multi-queue, le RSS et - si nécessaire - le RPS/XPS, en accord avec NUMA. Je lie les app-workers à des noyaux voisins de leurs noyaux IRQ, mais sans collision directe. Enfin, j'épure les chemins de pare-feu, je vérifie les tables de conntrack et je décide consciemment des offloads en fonction des objectifs de latence.
Exemple de playbook pour les latences p99
Tout d'abord, je mesure p95/p99 via une charge représentative et des logs sécurisés à partir de /proc/softirqs et /proc/net/softnet_stat, afin de Drops et time_squeeze clairement. Ensuite, j'augmente pas à pas netdev_budget ou netdev_budget_usecs et je garde p99 après chaque changement, pour avoir de vrais Tendances de la même manière. En parallèle, je lie les IRQ aux noyaux d'un nœud NUMA et je déplace les app-workers vers des voisins appropriés. Si p99 continue à sauter, je teste des variantes GRO/LRO et des profils de coalescence d'interruption, à chaque fois avec une courte distance de mesure. Ce n'est que lorsque la distribution reste calme que je transfère la configuration dans des rôles Ansible ou des dropins systemd.
Résumé pour les admins
J'obtiens le plus grand effet de levier en SoftIRQs, Je considère le budget NAPI, les affinités IRQ et les threads d'applications comme un chemin de données cohérent. Je répartis le travail en réseau sur les noyaux, je maintiens des files d'attente cohérentes NUMA et je lie les travailleurs de manière judicieuse afin que Parcours rester court. Je place les offloads de manière consciente et je mesure la gigue au lieu d'optimiser aveuglément le débit. Pour les objectifs de latence difficiles, je mise sur le busy polling et l'isolation du CPU, tandis que les CPU housekeeping interceptent les tirs parasites. En appliquant ces étapes de manière disciplinée, on obtient un débit constant, des répartitions de latence plus étroites et un environnement d'hébergement qui réagit de manière prévisible aux pics de charge.


