Ich optimiere die Netzwerkpfade eines Servers, indem ich IRQ Affinity gezielt einsetze, RX/TX-Queues auf Kerne abbilden und so Latenz, Durchsatz und p99-Jitter kontrollieren. Wer Multi-Core-CPUs konsequent nutzt, orchestriert Interrupts, SoftIRQs, NAPI und NUMA so, dass Flows kernaffin bleiben, Kontextwechsel sinken und die Anwendung messbar schneller reagiert.
Zentrale Punkte
- IRQ-Verteilung bestimmt, welche Kerne Hardware-Interrupts tragen und verhindert Hotspots.
- NUMA-Nähe reduziert Remote-Zugriffe und senkt Latenzspitzen.
- SoftIRQs & NAPI steuern die Batch-Verarbeitung und entlasten Kerne.
- RPS/RFS hält Flows nah an den konsumierenden Threads.
- Messung & Pinning macht Performance deterministischer.
Warum IRQ Affinity im Serverbetrieb zählt
Hohe Paketraten belasten schnell einzelne Kerne, wenn alle Interrupts auf wenigen CPUs landen, daher verteile ich die Last gezielt, um Hotspots zu vermeiden. Ich ordne RX/TX-Queues den passenden Cores zu, damit Datenpfade kurz bleiben und Caches warm bleiben. So sinken p95/p99-Latenzen, weil ich unnötige Migrationen spare und Verarbeitungsschritte auf denselben Kernen halte. Ich berücksichtige dabei die physische Nähe von NIC, Speicherkanälen und CPU-Sockeln, damit der Pfad vom Paket bis zum Application-Worker konstant schnell bleibt. Diese Kernaffinität schafft messbare Stabilität bei Traffic-Peaks, ohne die Hardware sofort aufrüsten zu müssen.
IRQ Balancing vs. feste Affinität
Der Standarddienst irqbalance verteilt Interrupts automatisch, doch er kennt meine Anwendungslogik, NUMA-Ziele und Latenzbudgets nicht. Ich lege kritische Netzwerk-IRQs fest auf ausgewählte Kerne, während laute oder weniger wichtige Interrupts auf andere Cores wandern. Diese Bindung harmoniert mit dem Pinning der Anwendungsprozesse, sodass die Pipeline je Flow konsistent bleibt. Bei starkem Traffic vermeide ich so Neuverteilungen, die zusätzlichen Overhead erzeugen und den Cache-Effekt schwächen. Wer tiefer einsteigen will, findet praxisnahe Hintergründe in diesem Leitfaden: IRQ-Balancing im Datacenter.
CPU-Affinität, NUMA und der kurze Datenpfad
Ich pinne Application-Worker und Network-IRQs bevorzugt auf demselben NUMA-Knoten, damit Speicherzugriffe lokal bleiben. Wenn eine NIC auf Node 0 hängt, setze ich die zugehörigen RX-Queues ebenfalls dort und binde die relevanten Prozesse an diese Kerne. So vermeide ich teure Remote-Memory-Zugriffe, die sich bei hohen Paketraten stark auf die Latenz auswirken. Auch Hyper-Threading-Paare beziehe ich ein, damit Schwester-Threads sich nicht gegenseitig stören. Dieses Dreieck aus Prozess-Pinning, IRQ-Affinität und NUMA-Topologie macht die Netzpfade berechenbarer und steigert den Durchsatz.
SoftIRQs, NAPI und Queue-Design verstehen
Nach dem Hardware-Interrupt übernimmt der Kernel die Verarbeitung in SoftIRQs, oft auf demselben Kern, der den IRQ erhielt. Bei hoher Last verteile ich die SoftIRQ-Last bewusst, um Engpässe zu entschärfen, ohne den Datenpfad unnötig zu fragmentieren. Multi-Queue-NICs helfen, weil ich jeder Queue klar definierte Cores zuweisen kann und damit echtes Parallelisieren erreiche. Über NAPI arbeite ich Pakete in Batches ab, damit keine Interrupt-Stürme entstehen und die CPU-Zeit effizient genutzt wird. Hintergrundwissen zu diesem Pfad liefert mir dieser Artikel: SoftIRQ und Netzwerkdurchsatz.
RPS/RFS und Flow-Lokalität
Ich nutze RPS für eine breitere Verteilung der Pakete und setze RFS ein, damit Flows bei den konsumierenden Threads landen. Dadurch bleiben Cache-Zugriffe effizient und die Applikation profitiert von konsistenten Antwortzeiten. Ich stimme die Hash-Strategie der NIC, die Queue-Anzahl und die RPS-CPU-Sets aufeinander ab, damit keine Kernel-Queue überläuft. Die Flow-Affinität wirkt vor allem bei vielen kurzen Requests, wie sie APIs und Microservices erzeugen. So baue ich eine Pipeline, in der jeder Flow möglichst oft denselben Kern berührt und unnötige Migrationen vermeidet.
RSS, Indirection-Table und XPS: Hashing gezielt steuern
Damit die Verteilung schon an der NIC sauber beginnt, justiere ich RSS (Receive Side Scaling) und die Indirection-Table so, dass RX-Queues genau den Kernen zugeordnet sind, die später auch die App-Threads tragen. Ich achte darauf, dass die Anzahl der Queues zur Zahl der genutzten Kerne passt und dass die Hash-Keys stabil bleiben, damit Flows nicht unerwartet wandern. Wechselt der Hash-Algorithmus oder wird die Indirection-Table dynamisch überschrieben, zerreißt das sonst die Flow-Lokalität und fördert Cache-Misses.
Auf dem TX-Pfad aktiviere ich ergänzend XPS (Transmit Packet Steering), damit ausgehende Pakete von dem Kern versendet werden, der die Anwendung bearbeitet. So bleiben auch die TX-Caches nah am Worker, und der Pfad von der Socket-Queue bis zur NIC-Queue bleibt kurz. Ich halte RX- und TX-Zuordnungen konsistent, dokumentiere sie pro Interface und lege sie in Startskripten fest, damit ein Reboot die Architektur nicht verwischt.
Interrupt Coalescing: Latenz gegen Durchsatz abwägen
Mit Coalescing fasse ich Interrupts zusammen, um den Overhead zu senken, achte dabei aber auf die Latenzgrenzen meiner Anwendung. Für Streaming und VoIP belasse ich die Intervalle eher kurz, während Bulk-Transfers längere Batches gut vertragen. Ich teste schrittweise, messe p95/p99 und überprüfe Drops, Retransmissions sowie CPU-Utilization pro Kern. Erst danach schreibe ich die Einstellungen fest und dokumentiere sie pro Host und NIC. Einen tieferen Einstieg in den Trade-off bietet dieser Praxisbeitrag: Interrupt Coalescing erklärt.
Offloads und Aggregation richtig dosieren
Ich setze GRO/LRO gezielt ein, um CPU-Overhead zu senken, prüfe aber, ob meine Workloads von größeren Batches profitieren. Latency-sensitive APIs reagieren häufig besser, wenn GRO moderat und LRO ausgeschaltet ist, weil große Super-Pakete die Head-of-Line-Blocking-Effekte verschärfen können. Für Bulk-Transfers, Replikation oder Backups nutze ich GRO/GSO/TSO aggressiver, solange die Empfängerseite stabil bleibt und die CPU-Auslastung fällt.
Checksum-Offloads und TSO/GSO entlasten die CPU deutlich, ich stelle jedoch sicher, dass Middleboxes, Tunnel oder Offload-Unverträglichkeiten (z. B. bei bestimmten Encapsulations) sauber funktionieren. Treten Anomalien auf, reduziere ich schrittweise einzelne Offloads und messe Effekte auf Durchsatz, Retransmits und CPU-Zeit. Ziel ist ein Set, das in der Breite stabil und im Peak vorhersehbar bleibt.
CPU-Isolation, Scheduler und Energiezustände
Für harte Latenzbudgets isoliere ich Kerne für Netzpfade und App-Worker. Mit CPU-Isolation und schlanker Housekeeping-Strategie verhindere ich, dass System-Tasks, Kthreads oder Timer-Interrupts auf die „heißen“ Kerne geraten. Zusätzlich fixiere ich den CPU-Governor auf „performance“ und begrenze tiefe C-States, wenn diese Aufwachlatenzen verursachen. Ich behalte die Kerntemperaturen im Blick, denn Thermalthrottling kann sonst jeden Feinschliff zunichtemachen.
Auch die Wahl der Scheduling-Klassen beeinflusst die Vorhersagbarkeit. Netzwerk-nahe Threads lasse ich priorisiert, aber nicht aggressiv exklusiv laufen, damit sie nicht mit ksoftirqd um CPU-Zeit konkurrieren. Ich kontrolliere regelmäßig, ob ksoftirqd auf einzelnen Cores anspringt – ein klares Zeichen dafür, dass die SoftIRQ-Last zu hoch oder falsch verteilt ist.
Busy-Polling und Low-Latency-Pfade
Wenn Mikrosekunden zählen, setze ich Busy-Polling gezielt ein. Applikationen können für ausgewählte Sockets Polling-Fenster definieren, sodass sie Pakete direkt aus NAPI-Budgets ziehen, ohne auf Interrupts zu warten. Ich wähle kurze Poll-Intervalle, um CPU-Zeit nicht zu verbrennen, und beschränke diese Technik auf hot paths mit konstantem Traffic. Parallel passe ich netdev-Budgets moderat an, damit Batches groß genug sind, ohne den Rest des Systems zu verhungern.
Netzwerk-Queue-Disziplin und Pacing
Ich richte die qdisc pro Interface passend zum Workload ein. Mit modernen Disziplinen wie fq/fq_codel reguliere ich Pacing und Queue-Längen, um Bursts zu glätten und Bufferbloat zu vermeiden. In Multi-Queue-Setups kombiniere ich dies mit mqprio, damit Traffic-Klassen konsistent den richtigen HW-Queues zugeordnet bleiben. Zusammen mit BQL (Byte Queue Limits) auf dem Treiber senkt das Latenzen unter Volllast, weil die Queue nicht unkontrolliert wächst.
Wichtig ist das Zusammenspiel mit XPS auf dem TX-Pfad: Ich mappe die Sende-Queues an die Kerne, auf denen auch die zugehörigen RX-Flows landen. So bleiben beide Richtungen eines Flows CPU-nah und ich erreiche bei bidirektionalen Protokollen (z. B. HTTP/2, gRPC) stabilere Antwortzeiten.
Praxis-Workflow unter Linux
Ich starte mit einer Lastaufnahme, prüfe die CPU-Verteilung in top/htop, schaue auf /proc/interrupts und /proc/softirqs und lese ethtool-Statistiken, um Engpässe zu erkennen und den nächsten Workflow-Schritt abzuleiten. Danach ermittle ich die IRQ-IDs der relevanten NIC-Queues und setze passende CPU-Masken, die die Kerne gleichmäßig belegen und NUMA berücksichtigen. Im Anschluss pinne ich die Application-Worker per taskset oder systemd-CPUAffinity auf dieselben Kerne, die auch die zugehörigen Queues bedienen. Ich aktiviere RPS/RFS nur dort, wo es die Flow-Lokalität stärkt, und halte die Konfiguration pro Interface konsistent. Zum Schluss messe ich erneut Durchsatz, Latenz und Jitter, bevor ich Änderungen über mehrere Hosts einheitlich ausrolle.
Messung, p95/p99 und Regressionen vermeiden
Ich verlasse mich nicht auf Bauchgefühl, sondern messe Latenzen, Fehlerquoten und Core-Auslastung vor und nach jeder Tuningrunde, damit p99 stabil bleibt. Zusätzlich tracke ich Kontextwechsel, Migrationsraten und Last pro SoftIRQ-Typ, um versteckte Nebenwirkungen früh zu sehen. Ich halte Tests reproduzierbar, nutze gleiche Datensätze und fixierte Versionsstände, damit die Ergebnisse vergleichbar bleiben. Regressionen enttarne ich mit Gegenproben unter Peak- und Idle-Bedingungen sowie mit langen Dauerläufen. Erst wenn Metriken, Logs und Applikations-Traces zusammenpassen, erkläre ich die Konfiguration als neuen Baseline-Stand.
Virtualisierung, Container und SR-IOV
In virtualisierten Umgebungen sorge ich dafür, dass vCPUs, Memory und vNICs der VM auf demselben NUMA-Knoten liegen, auf dem die zugehörige physische NIC endet. Wo möglich, nutze ich SR-IOV, damit der Datenpfad kurz und die IRQs direkt an Gast-Kerne gebunden werden können. Ich pinne vCPUs der kritischen VMs auf dedizierte Host-Kerne und achte darauf, dass Host-IRQs und Gast-IRQs sich nicht überlagern. In Container-Setups setze ich cpusets und „guaranteed“-QoS-Klassen ein, damit Worker-Container und ihre Netzwerk-IRQs planbar CPU-Zeit bekommen.
Ich prüfe, ob irqbalance im Gast oder auf dem Host die Federführung haben soll – doppelte „Automatik“ produziert sonst Unschärfen. Bei virtio setze ich mehrere Queues und mappe sie sauber auf vCPUs, um Parallelarbeit zu ermöglichen. Lastet vhost-net einzelne Host-Kerne aus, verteile ich die Backends neu und halte vhost-Threads NUMA-nah zur physischen NIC.
Fehlersuche: Muster schnell erkennen
- Kerne saturiert, ksoftirqd aktiv: RX-Queues enger pinnen, Queue-Anzahl prüfen, RPS/RFS anpassen oder Coalescing leicht erhöhen.
- Sprunghafter p99-Jitter: NUMA-Drift prüfen, C-States/Governor verifizieren, Offloads und GRO-Größen schrittweise justieren.
- Viele Retransmissions/Drops: RX/TX-Ringgrößen, qdisc und BQL gegenprüfen, Indirection-Table und XPS auf Konsistenz prüfen.
- Ungleich verteilte Flows: RSS-Hash und Indirection-Table balancieren, Hot-Flow-Pinning erwägen, Hash-Seed stabil halten.
- VM-only Problem: vhost-/virtio-Backends NUMA-nah legen, SR-IOV evaluieren, IRQs zwischen Host und Gast entflechten.
Anwendungen und Datenbanken einbeziehen
Ein sauberer Netzpfad bringt wenig, wenn App-Server oder Datenbanken nicht parallel arbeiten, deshalb richte ich die Worker-Zahl, Thread-Pools und Connection-Limits an den verfügbaren Cores aus. Ich pinne NGINX- oder HAProxy-Worker an die passenden Kerne, damit sie zu den RX-Queues passen. PHP-FPM, Node.js, Java oder Go skaliere ich so, dass sie die lokale NUMA-Domain bevorzugen und bei Bedarf mehrere Instanzen nutzen. Caches wie Redis oder Memcached binde ich CPU-nah ein und achte auf ihre eigenen Netz- und Thread-Parameter. Erst das Zusammenspiel aus IRQ-Affinität, Prozess-Pinning und App-Skalierung bringt den spürbaren Schub bei Latenz und Durchsatz.
Hosting-Szenarien mit hohem Nutzen
Ich investiere vor allem dann in tiefes Tuning, wenn APIs sehr viele kurze Requests erzeugen oder wenn Echtzeit-Kommunikation wie VoIP und Chats geringe Jitter-Werte verlangt. E-Commerce-Setups mit Lastspitzen profitieren, weil Checkout-Flows empfindlich auf Latenz reagieren. Multi-Tenant-Hosts mit hoher Dichte gewinnen, da dedizierte Kerne pro Queue die Nachbarschaftseffekte mindern. Auch Streaming-Dienste können mehr Durchsatz pro Euro erzielen, ohne sofort neue Hardware einzukaufen. Die Kosten bleiben kalkulierbar, solange ich Änderungen messbar halte und zielgenau ausrolle.
Tabellarische Kurzreferenz: Kerne, Queues, Tools
Ich nutze die folgende Tabelle als Gedächtnisstütze, wenn ich neue Hosts einrichte oder bestehende Setups neu kalibriere. Sie zeigt typische Ziele, passende Maßnahmen, übliche Linux-Tools und die beabsichtigte Wirkung auf Latenz und Durchsatz. Ich verwende sie nicht dogmatisch, sondern als Startpunkt für Messreihen mit realem Traffic. Variiert die NIC-Architektur oder die NUMA-Topologie, passe ich die Kernauswahl an. Wichtig bleibt, die Dokumentation pro Host mitzuführen und Änderungen nachvollziehbar zu halten.
| Ziel | Maßnahme | Linux-Tool/Ort | Erwarteter Effekt |
|---|---|---|---|
| IRQ-Last verteilen | Queues an Kerne binden | /proc/irq/*/smp_affinity | Weniger Hotspots, konstantere Latenz |
| Flow-Lokalität erhöhen | RPS/RFS CPU-Sets setzen | /sys/class/net/*/queues/*/rps_cpus | Weniger Migrationen, bessere Caches |
| Batch-Verarbeitung steuern | NAPI/Coalescing feinjustieren | ethtool -C / Treiber-Defaults | Geringerer Overhead, kontrollierte Jitter |
| App und IRQ koppeln | Worker pinnen | taskset, systemd CPUAffinity | Kürzerer Pfad, geringere p99 |
| NUMA vermeiden | Geräte und Kerne co-lokalisieren | numactl, lscpu, lspci -vv | Weniger Remote-Zugriffe, mehr Durchsatz |
Best Practices, die langfristig tragen
Ich ändere nur einen Stellhebel pro Testrunde, dokumentiere die Metriken und sichere die Dokumentation im Repo des Hosts. Konfigurationen halte ich konsistent, indem ich Queue-zu-Core-Zuordnungen eindeutig beschreibe und Skripte zur Replikation nutze. Ich beobachte Logs auf Drops, Retransmissions und Timeouts und korreliere sie mit Kernel-Metriken. Hypervisor- und Storage-Ebene beziehe ich in die Analyse ein, damit keine Schattenengpässe übrig bleiben. Rollbacks halte ich bereit, falls Tests negative Effekte zeigen oder Workloads sich ändern.
Kurz zusammengefasst
Ich erreiche maximale Netzwerkleistung, indem ich Interrupts, Queues und Worker aufeinander abstimme und so den Datenpfad pro Flow stabil halte. IRQ Affinity verteilt die Hardware-Last sinnvoll, während SoftIRQs, NAPI und RPS/RFS die Verarbeitung effizient machen. NUMA-Nähe schützt vor vermeidbaren Speicherumwegen und senkt Jitter. Schrittweises Tuning mit reproduzierbaren Messungen verhindert Fehlkonfigurationen und zeigt echte Fortschritte. Wer diese Bausteine gemeinsam denkt, schöpft die Fähigkeiten moderner Multi-Core-Server für latenzkritische Dienste souverän aus.


