...

softirq cpu hosting: Server-Performance und Netzwerkdurchsatz optimieren

Ich zeige, wie softirq cpu gemeinsam mit NAPI, IRQ-Verteilung und Queue-Design den Netzwerkdurchsatz im Hosting limitiert oder entfesselt. Mit klaren Messpunkten, gezieltem Tuning und sauberen Affinitäten senke ich Latenzen und erhöhe konsistent den pps‑Durchsatz auf produktiven Servern.

Zentrale Punkte

Diese Kernideen tragen Netzwerkpakete effizient über CPU, Kernel und NIC – und halten Antwortzeiten konstant niedrig.

  • NAPI-Budget feinjustieren: Mehr Pakete pro Poll senken Overhead und glätten die CPU-Last.
  • IRQ-Balancing und Affinität: Hotspots vermeiden, Cache-Treffer erhöhen, Latenzspitzen drücken.
  • Multi-Queue, RSS/RPS/XPS: Flows parallelisieren, NUMA-Ausrichtung wahren, pps anheben.
  • Offloads bewusst nutzen: GRO/LRO, TSO, Coalescing bewerten, Jitter im Blick behalten.
  • Isolation und Busy Polling: Vorhersagbare Reaktionszeiten auf dedizierten Kernen.

Grundlagen: Was im Kernel beim Netzwerktraffic passiert

Ein Paket landet zuerst in einem Hardware-Interrupt, danach übernimmt der Kernel die Arbeit in SoftIRQs und NAPI-Poll-Schleifen. Ich achte darauf, dass die schnelle HardIRQ-Phase wirklich kurz bleibt und die eigentliche Logik in den richtigen Kontext wandert, damit die CPU-Zeit nicht verpufft. Die ksoftirqd-Threads springen nur ein, wenn direkte Abarbeitung nicht möglich ist, was bei Dauerlast schnell zu Warteschlangen führt. Genau hier entsteht Wartezeit, die sich in erhöhtem TTFB und schwankendem Durchsatz zeigt. Wer tiefer einsteigen will, findet Praxiswissen zur IRQ-Verarbeitung in diesem Beitrag zu Interrupt-Handling und CPU-Performance, den ich für die Einordnung nutze.

NAPI, SoftIRQs und ksoftirqd: Latenz steuern statt verwalten

NAPI reduziert Interrupt-Stürme, indem es in einem definierten Budget mehrere Pakete pro Durchlauf abholt und damit den Overhead senkt. Reicht das Budget nicht, stauen sich Pakete, ksoftirqd läuft heiß und die Latenz steigt messbar an. In solchen Situationen prüfe ich systematisch /proc/softirqs und /proc/net/softnet_stat, um Drops, time_squeeze oder überlaufende Queues sichtbar zu machen. Danach erhöhe ich schrittweise net.core.netdev_budget oder net.core.netdev_budget_usecs und beobachte parallel CPU-Last, p95/p99-Verteilung und Paketverluste. Die Kunst besteht darin, genug Arbeit pro Poll zu erledigen, ohne die interaktive Ausführung von Userland-Threads zu verdrängen.

IRQ-Balancing und Affinität: Hotspots vermeiden, Cache-Treffer erhöhen

Ein einzelner Kern mit allen NIC-IRQs wird zum Flaschenhals, weil er sowohl Interrupts, SoftIRQs als auch App-Threads bedienen muss; ich verteile daher IRQs gezielt. Der Dienst irqbalance hilft, doch für hohe pps-Raten mappe ich RX/TX-Queues per Affinität explizit auf passende Kerne. Auf NUMA-Systemen binde ich Queues an Kerne desselben Knotens, um Remote-Memory-Zugriffe zu vermeiden. Anwendungsthreads laufen auf benachbarten, aber getrennten Cores, wodurch sich Cache-Lokalität und Planbarkeit verbessern. Einen guten Überblick zur strategischen Verteilung liefert dieser Leitfaden zum IRQ-Balancing im Datacenter, den ich als Referenz für Feinschliff heranziehe.

Multi-Queue, RSS/RPS/XPS: Parallelisierung richtig einsetzen

Moderne NICs bringen mehrere RX/TX-Queues mit, die ich über RSS auf Flows verteile und damit echte Parallelität erreiche. Wenn die Karte zu wenige Queues bietet, steuere ich mit RPS/XPS softwareseitig nach, um Pakete sinnvoll über Kerne zu schieben. Wichtig ist die saubere Hash-Verteilung, damit ein Flow stets auf derselben CPU bleibt und keine teuren Cache-Verwerfungen entstehen. Zugleich halte ich TX- und RX-Pfade nahe beieinander, um Lock-Kontention und unnötige Cross-Node-Zugriffe zu vermeiden. So wächst der pps-Durchsatz, ohne dass ein einzelner Kern die Bremse zieht.

CPU-Affinity bis in den Userspace: End-to-End denken

Ich plane den Datenpfad vom NIC-IRQ über NAPI-Queues bis zu den Worker-Threads der App, damit Pakete ohne unnötige Haken ans Ziel gelangen und die Antwortzeit konstant bleibt. Dafür trenne ich Kerne für Interrupts/SoftIRQs konsequent von App-Kernen und lege klare Affinity-Regeln fest. Webserver, Reverse-Proxies und Datenbanken bekommen feste CPU-Sets, die nahe an den IRQ-Kernen liegen, um die Laufwege kurz zu halten. Zusätzlich setze ich den CPU-Governor auf performance, damit Taktwechsel keine Jitter in p99 schieben. Diese konsequente Zuordnung macht Verhalten vorhersehbar und hilft, Engstellen sauber zu diagnostizieren.

Offloads, GRO/LRO, Firewall und eBPF: Last sparen ohne Blindflug

Checksum-Offload, TSO und Coalescing sparen CPU-Zeit, können aber Paketgrößen, Burst-Verhalten und Jitter verändern, weshalb ich Effekte gezielt messe. GRO/LRO bündeln Frames und entlasten den Stack, doch bei Echtzeitanforderungen entscheide ich situativ über Deaktivierung oder limitierte Nutzung. Conntrack-Tabellen und tiefe nftables/iptables-Ketten kosten Takte, daher räume ich überflüssige Regeln auf und vereinfache Pfade. Bei Bedarf greife ich zu eBPF (XDP, tc-BPF), um frühe Entscheidungen an der NIC zu treffen und kostspielige Pfade zu umgehen. Ein guter Startpunkt zur Feintuning-Praxis ist diese Übersicht zu Interrupt Coalescing, die ich bei sensiblen Latenzbudgets berücksichtige.

Busy Polling und CPU-Isolation: Reaktionszeiten festzurren

Für harte Latenzziele setze ich Busy Polling ein, damit Userspace-Sockets Pakete noch früher abholen und Wartezeiten verkürzen. Das erhöht die Last, liefert mir dafür sehr enge p99-Verteilungen bei API- oder Trading-Workloads auf dedizierten Kernen. Zusätzlich isoliere ich Cores mit isolcpus=, nohz_full= und rcu_nocbs=, sodass Timer, RCU und Systemdienste nur auf Housekeeping-CPUs laufen. Diese Trennung verhindert Störungen auf den Latenz-Kernen und macht Verhalten reproduzierbar. In Summe entsteht ein klarer Fahrplan: dedizierte Kerne, frühe Paketabholung, definierte Budgets.

Monitoring und Troubleshooting: Vom Symptom zur Ursache

Ich starte mit pps, Durchsatz und Kernlast, prüfe dann Drops und die Aktivität der ksoftirqd-Threads im Zeitverlauf, um Muster sicher zu erkennen. Tools wie sar, htop, ss, nload und ethtool zeigen mir, wann und wo sich Staus bilden und ob die Queues an ihre Grenzen laufen. Wichtig sind Verteilungen statt Mittelwerten, damit abendliche Peaks, Cronfenster oder Kampagnen nicht untergehen. Ich korreliere TTFB-Spitzen mit IRQ-Verteilung, NAPI-Budget und Offload-Settings, um gezielt an Stellschrauben zu drehen. Oft reicht eine angepasste IRQ-Affinität oder ein neu zugeschnittenes NAPI-Budget, um Timeouts spürbar zu senken.

Tuning-Parameter im Überblick

Die folgende Übersicht hilft mir, Änderungen bedacht einzusetzen und Effekte klar zuzuordnen, bevor ich dauerhaft Rollouts plane. Jede Anpassung prüfe ich iterativ, messe Latenzverteilungen und beobachte Nebenwirkungen auf CPU und Speicher. Ich ändere immer nur einen Punkt pro Testfenster, damit Ursache und Wirkung eindeutig bleiben. Danach dokumentiere ich Ergebnisse und lege Schwellenwerte für Alarmierung fest. So erreiche ich reproduzierbare Verbesserungen, ohne Überraschungen im Produktivverkehr zu riskieren.

Parameter/Feature Wirkung im Datenpfad Wann anheben/aktivieren Risiken/Nebenwirkungen
net.core.netdev_budget Mehr Pakete pro NAPI-Poll Bei Drops in softnet_stat Längere Polls verdrängen User-Threads
net.core.netdev_budget_usecs Zeitfenster pro Poll begrenzen Bei Jitter durch große Bursts Zu klein: mehr Kontextwechsel
RSS/RPS/XPS Flows über Kerne verteilen Bei Hotspots auf einem Kern Falsche Hashes: Cache-Verwerfungen
IRQ-Affinität IRQs gezielt kernnah binden Bei NUMA-Missmatch Fehlzuordnung erzeugt neue Hotspots
GRO/LRO/TSO Reduziert Paketanzahl Bei CPU-Bottleneck Jitter, größere Bursts
Busy Polling Frühe Paketabholung Für harte p99-Ziele Mehr CPU-Verbrauch

RX/TX-Ringe und Queue-Tiefe: Puffer richtig dimensionieren

Selbst mit sauber verteilten IRQs und passenden Budgets können zu kleine oder zu große NIC-Ringe die Performance drücken. Ich prüfe deshalb die RX/TX-Ringgrößen der Karte und passe sie an Burst-Charakter und Latenzziele an. Zu kleine Ringe führen bei Verkehrsspitzen zu Drops in der NIC, sichtbar als rx_missed_errors oder fifo_errors in den Treiberstatistiken. Zu große Ringe verschleiern Staus, erhöhen die Latenz und erzeugen lange Schleppkanten in p95/p99. Ich suche die Mitte: genug Puffer, um kurze Bursts aufzufangen, aber nicht so viel, dass Pakete in Queues “altern”.

Ergänzend betrachte ich die Host-seitige tx_queue_len und den verwendeten Qdisc. Mit sch_fq oder fq_codel kann ich Burst-Verhalten glätten und TSO-Großpakete über Pacing verteilen. Das reduziert Mikrobursts am Switchport und macht die Latenzkurve ruhiger – wichtig bei gemischten Workloads, in denen kleine RPCs neben großen Uploads laufen. Ich beobachte dabei ethtool-Statistiken und korreliere sie mit softnet_stat, um zu erkennen, ob die Staus im NIC-Ring, im netdev-Backlog oder im Qdisc entstehen.

MTU, Jumbo Frames und Segmentierung

Die MTU ist ein klassischer Hebel, der oft unterschätzt wird. Jumbo Frames reduzieren die Paketanzahl pro Gbit/s und entlasten die CPU – aber nur, wenn der Pfad wirklich end-to-end jumbo-fähig ist. Ich validiere darum systematisch die Gegenstellen, Switches und Tunnels. Sobald irgendwo wieder auf 1500 fragmentiert wird, drohen Path-MTU-Probleme, Retransmits und unnötiger Jitter. In Rechenzentren mit dominanter East/West-Kommunikation lohnt sich eine homogene 9k-Strategie, während für Internet-facing Workloads 1500 oft die stabilere Wahl ist.

Ich bewerte die MTU immer im Zusammenspiel mit TSO/GSO/GRO: Eine zu aggressive Bündelung kann zu großen Bursts im TX führen, die Upstream-Puffer füllen und Latenzspitzen erzeugen. Ziel ist ein konsistenter Pfad: sinnvolle Segmentierung am Sender, ausreichende Pacing-Mechanismen und GRO, das auf der Empfängerseite Arbeit spart, ohne Echtzeit-Anforderungen zu konterkarieren.

UDP, QUIC und Streaming-Workloads: Spezifika berücksichtigen

Nicht jeder Traffic ist TCP. UDP-lastige Profile (DNS, VoIP, QUIC, Telemetrie) verhalten sich in RSS/RPS und GRO anders. Moderne Stacks unterstützen UDP-GRO/GSO, was die CPU entlasten kann – ich nutze das selektiv und messe, ob Reordering-Risiken oder Jitter zunehmen. Für QUIC/HTTP3-Lasten ist eine saubere Flow-Verteilung entscheidend: RPS kann helfen, wenn die NIC zu wenige RSS-Queues bietet, darf aber keine Hot-Cache-Flows “herumwerfen”. Auf TX-Seite setze ich XPS ein, um Sendewege zu bündeln und Lock-Kontention zu verringern. In der Praxis zahlt sich eine ruhige, kernaffine Zuordnung besonders bei vielen mittelgroßen UDP-Flows aus, bei denen jeder Cache-Treffer zählt.

Virtualisierung und Container: Host, Gast und vhost sauber verzahnen

In virtualisierten Umgebungen verschiebt sich Arbeit zwischen Host, vhost-Threads und Gast-IRQs. Ich achte darauf, dass vhost-net-Threads eigene Kerne erhalten und nicht mit App-Workern kollidieren. Ihre Affinitäten müssen zu den physischen RX/TX-Queues passen, sonst entstehen unnötige Cross-CPU-Wanderungen. Im Gast prüfe ich virtio-net-Queues, aktiviere Multi-Queue und richte RSS/RPS analog zum Bare Metal ein. Wo Latenz und pps im Vordergrund stehen, kann SR-IOV den Overhead weiter senken – Voraussetzung ist eine konsistente NUMA-Topologie: VF, vCPU und Speicher gehören auf denselben Knoten.

Im Container-Stack verursachen Overlay-Netze, tiefe NAT-Ketten und komplexe CNI-Topologien zusätzliche Hops. Für latenzkritische Dienste bevorzuge ich hostNetwork oder schlanke Netze (macvlan/ipvlan), entzerre NAT-Pfade und halte Conntrack so klein wie möglich. Wichtig ist eine durchgängige CPU-Strategie: IRQ- und NAPI-Kerne des Hosts sollten in Nachbarschaft zu den Cores liegen, auf denen vhost/Container-Worker laufen – nur so bleibt der Datenpfad kurz und planbar.

Scheduling, C-States und IRQ-Threading

Weil Latenz nicht nur Rechenzeit, sondern auch Aufwachzeit ist, minimiere ich tiefe C-States auf den Latenz-Kernen. Ein aggressiver Powersave kann Millisekunden kosten, bevor eine SoftIRQ wirklich läuft. Ich setze daher auf performance-Governor, begrenze tiefe C-States und halte Turbo konsistent, um Frequenzsprünge vorhersehbar zu machen. Ebenso wichtig ist IRQ-Threading: Wo Treiber es erlauben, verschiebe ich Arbeit in IRQ-Threads und vergebe Prioritäten so, dass RX vor nachgelagerter Arbeit anläuft, ohne Userland komplett zu verdrängen. Das Zusammenspiel aus Sched-Policies, Affinitäten und Budgets ist heikel; ich teste schrittweise, protokolliere p99 und achte auf Interferenzen mit ksoftirqd, das sonst zur heimlichen Engstelle wird.

Beobachtung in der Tiefe: Tracepoints, Counters, Histos

Wenn Metriken vage bleiben, gehe ich eine Stufe tiefer: Ich nutze Kernel-Tracepoints rund um netif_receive_skb, napi_poll und net_dev_queue, um Poll-Dauern, Paketmengen und Wartezeiten als Histogramme zu sehen. Solche Verteilungen zeigen, ob 1 % der Polls zu lange dauern oder ob einzelne Queues ausreißen. Ergänzend liefern ethtool-rx/tx-Counters, TCP-Retransmits, Busy-Poll-Hits und softnet_stat klare Fingerzeige, wo Pakete verloren gehen. Mit Drop-Analyse erkenne ich, ob die NIC abwirft (Ring voll), der netdev-Backlog kollabiert (time_squeeze) oder Qdisc/Firewall bremst. Erst wenn diese Puzzleteile zusammenpassen, drehe ich an Ringen, Budgets oder Offloads.

Security- und Filtering-Pfade entschlacken

Komplexe ACLs, tiefe nftables/iptables-Ketten und breite Conntrack-Tabellen addieren konstante Latenz pro Paket. Ich konsolidiere Regeln, arbeite mit Sets/Maps und verschiebe generische Drops möglichst weit nach vorn im Pfad – idealerweise so früh wie möglich an der NIC (XDP/clsact), wenn Latenz kritisch ist. Stateless Flows, Telemetrie oder bekannte “Safe”-Ports können gezielt ohne Tracking laufen, damit kostspielige Lookups entfallen. Gleichzeitig halte ich State-Tabellen frisch, passe Hashgrößen an Lastspitzen an und räume verwaiste Einträge aggressiv auf. Ziel ist ein sauberer, nachvollziehbarer Policy-Pfad, der sich im Profil nicht als Dauerlast bemerkbar macht.

Typische Anti-Pattern und wie ich sie vermeide

  • Alle IRQs auf einem Kern: führt zu Stau und heißem ksoftirqd. Gegenmittel: gezielte Affinitäten pro Queue, NUMA-kohärent.
  • Blindes Maximieren von Ringen/Budgets: kaschiert Staus, erhöht Latenzschwänze. Gegenmittel: inkrementell erhöhen, Verteilungen messen.
  • Unsaubere Flow-Hashing-Konfiguration: Flows hüpfen zwischen Cores, Caches verpuffen. Gegenmittel: stabile RSS-Keys, RPS/XPS nur mit klarer Zielsetzung.
  • App-Threads auf denselben Cores wie SoftIRQs: Interferenz und Jitter. Gegenmittel: harte Trennung, nachbarschaftliche Zuordnung.
  • Overlays/NAT ohne Budget: jedes Hop addiert. Gegenmittel: Pfade verschlanken, hostnahe Netze für Latenz-Workloads.
  • Power-Saving auf Latenz-Kernen: tiefe C-States verlangsamen Reaktion. Gegenmittel: performance-Governor, C-State-Begrenzung.
  • Offloads ohne Messung: TSO/GRO können Bursts und Jitter verschärfen. Gegenmittel: Workload-spezifisch aktivieren, p99 beobachten.

Praxis im Hosting: Schritte, die wirken

Ich beginne mit einer sauberen Messphase, setze Baselines und halte alle Änderungen in kurzen Zeitfenstern klein, damit ich Ursachen trennen kann. Danach aktiviere ich irqbalance, prüfe die automatische Verteilung und lege bei Bedarf manuelle Affinitäten fest, bis keine Hotspots mehr sichtbar sind. Im Anschluss richte ich Multi-Queue, RSS und – wenn nötig – RPS/XPS ein, abgestimmt auf NUMA. Die App-Worker binde ich an Kerne in Nachbarschaft zu ihren IRQ-Kernen, aber ohne direkte Kollision. Zuletzt entschlacke ich Firewall-Pfade, prüfe Conntrack-Tabellen und entscheide bewusst über Offloads basierend auf Latenzzielen.

Beispiel-Playbook für p99-Latenzen

Zuerst messe ich p95/p99 über repräsentative Last und sichere Logs aus /proc/softirqs und /proc/net/softnet_stat, um Drops und time_squeeze klar zu sehen. Dann erhöhe ich Schritt für Schritt netdev_budget oder netdev_budget_usecs und halte nach jeder Änderung p99 fest, damit ich echte Trends erkenne. Parallel binde ich IRQs auf Kerne eines NUMA-Knotens und verschiebe App-Worker auf passende Nachbarn. Wenn p99 weiterhin springt, teste ich GRO/LRO-Varianten und Interrupt-Coalescing-Profile, jeweils mit kurzer Messstrecke. Erst wenn die Verteilung ruhig bleibt, überführe ich die Konfiguration in Ansible-Rollen oder systemd‑Dropins.

Kurzfassung für Admins

Die größte Hebelwirkung erreiche ich, indem ich SoftIRQs, NAPI-Budget, IRQ-Affinitäten und App-Threads als zusammenhängenden Datenpfad betrachte. Ich verteile Netzwerkarbeit über Kerne, halte NUMA-kohärente Queues und binde Worker sinnvoll an, damit Laufwege kurz bleiben. Offloads setze ich bewusst und messe Jitter statt blind auf Throughput zu optimieren. Bei harten Latenzzielen setze ich auf Busy Polling und CPU-Isolation, während Housekeeping-CPUs Störfeuer abfangen. Wer diese Schritte diszipliniert umsetzt, erhält konstanten Durchsatz, engere Latenzverteilungen und eine Hosting-Umgebung, die planbar auf Lastspitzen reagiert.

Aktuelle Artikel