...

Receive Side Scaling bei 10- und 25-Gbit/s: Performance-Tuning für moderne Linux-Servernetzwerke

Receive Side Scaling verteilt an 10- und 25-Gbit/s-Links den Netzwerkempfang gezielt über mehrere Kerne, damit Linux-Server hohe Durchsatzraten mit niedriger Latenz abwickeln. Ich zeige praxisnah, wie ich RSS aktiviere, die Queues auf Cores mappe und so Engpässe bei Interrupts und Cache-Treffern vermeide.

Zentrale Punkte

Ich fasse die wichtigsten Aspekte knapp zusammen, damit du die nächsten Schritte schnell planen kannst.

  • Lastverteilung: Pakete landen über mehrere Queues auf mehreren Cores.
  • Cache-Lokalität: Ein Flow bleibt konsistent auf derselben Queue.
  • Hashing: 4-Tupel-Hash verteilt Flows gleichmäßig über Queues.
  • Affinität: Gezieltes IRQ-Mapping reduziert Latenzen.
  • Skalierung: Ab 10/25 Gbit/s sichert RSS hohen Durchsatz.

Diese Punkte greifen ineinander und tragen die Performance über reale Workloads. Ich priorisiere zuerst die korrekte Queue-Anzahl, dann die CPU-Affinität. Anschließend prüfe ich Hash-Parameter und Feineinstellungen.

Was Receive Side Scaling leistet

RSS zerlegt den Paketempfang in mehrere Receive-Queues, die ich spezifischen CPU-Kernen zuordne, damit kein einzelner Kern zum Flaschenhals wird. Das senkt harte Interrupt-Spitzen und glättet die Verarbeitung über SoftIRQs, was Latenzspitzen reduziert und den Durchsatz hebt. Jede Queue triggert eigene Interrupts, die ich fest an Kerne binde, um Datenpfade konsistent zu halten. Diese Konsistenz fördert die Cache-Lokalität, weil ein Flow stets auf denselben Kern trifft. Genau dieses Zusammenspiel zahlt bei hohen PPS-Raten direkt in messbare Effizienz ein.

So arbeitet RSS technisch

Die NIC bildet aus Quell/Ziel-IP sowie Quell/Ziel-Port einen Hash und nutzt ihn als Index für die Indirection-Table, die auf Queues zeigt. Auf diese Weise landen Pakete eines Flows immer in derselben Queue und bleiben damit an denselben Kern gebunden. Unterschiedliche Flows verteilen sich gleichmäßig, sofern Hash-Keys und Protokollfelder sinnvoll konfiguriert sind. Die Arbeit wandert damit nahe an die Hardware, wodurch der Kernel weniger balancieren muss und Overhead sinkt. Genau das möchte ich, um bei 10G/25G die Paketverarbeitung pro Kern niedrig zu halten.

Warum RSS ab 10 und 25 Gbit/s zählt

Bei 1 Gbit/s trägt oft ein einzelner Kern die Paketlast, doch ab 10 Gbit/s kippt die Balance schnell. Kleine Pakete lassen die PPS-Zahl steigen, wodurch ein Kern rasch 100 Prozent Last erreicht und Drops auftreten. Genau dann wirkt RSS wie ein Multiplikator für nutzbare Bandbreite. Ich verteile die Last über mehrere Cores, senke Kontextwechsel und halte Latenzkurven stabiler. Das Ergebnis: realer Durchsatz nähert sich erst mit sauberem RSS der Linkrate.

RSS auf dem Linux-Server einrichten

Unter Linux steuere ich RSS vor allem über ethtool, Treiberoptionen und sysfs, damit die NIC-Fähigkeiten wirklich greifen. Zuerst lese ich die maximalen RX-Channels aus, dann setze ich eine zur CPU passenden Queue-Anzahl. Im Anschluss prüfe ich den RSS-Hash für TCP/UDP und optional für VLAN oder Tunneling, damit Lastprofile sauber verteilt bleiben. Für das Grundrauschen der Interrupt-Verteilung hilft mir IRQ-Balancing, auch wenn ich kritische Queues lieber manuell pinne. So kopple ich Queues eng an die Topologie des Hosts und verhindere störende Wanderungen.

Indirection-Table, RSS-Key und Hash-Feintuning: konkrete Kommandos

Ich prüfe zuerst die aktuelle Verteilung und den Schlüssel der NIC:

ethtool -x eth0           # Indirection-Table (RX-Queues) und RSS-Key anzeigen
ethtool -n eth0 rx-flow-hash tcp4
ethtool -n eth0 rx-flow-hash udp4

Für eine saubere, gleichmäßige Verteilung setze ich die Indirection-Table auf die gewünschte Queue-Anzahl. Bei 16 Queues wähle ich ein gleichmäßiges Mapping:

ethtool -X eth0 equal 16  # Verteilung gleichmäßig auf 16 Queues

Falls nötig, passe ich die Hash-Felder an. Für TCP4 mit 4-Tupel (s=src-ip, d=dst-ip, f=src-port, n=dst-port):

ethtool -N eth0 rx-flow-hash tcp4 sdfn
ethtool -N eth0 rx-flow-hash udp4 sdfn
ethtool -N eth0 rx-flow-hash tcp6 sdfn
ethtool -N eth0 rx-flow-hash udp6 sdfn

Manche Treiber erlauben auch das Setzen eines eigenen RSS-Keys (z. B. zur besseren Streuung in Spezialfällen):

ethtool -X eth0 hkey <hex-seq>  # nur, wenn Treiber/NIC es unterstützen

CPU-Affinität und NUMA sauber setzen

Ich mappe jede RX-Queue per IRQ-Affinität an dedizierte Kerne und beachte dabei NUMA, damit Daten kurz über den Memory-Controller laufen. Läuft die NIC auf Node 0, binde ich die Hauptqueues ebenfalls an Kerne auf Node 0 und parke Workloads nahebei. Diese Nähe reduziert Fernzugriffe und senkt Speicherlatenzen deutlich. Nützlich ist dazu ein Profil für produktive Queues sowie getrennte Kerne für Management und Offload-Aufgaben. Wer tiefer einsteigen will, findet zur Feinausrichtung Hinweise unter IRQ-Affinität, was die Planung pro Core vereinfacht.

IRQ-Affinitäts-Playbook: von IRQs zur stabilen Core-Bindung

Ich ermittle zuerst, welche IRQs zu den RX-Queues gehören, und binde sie dann fest:

grep -E "eth0.*Rx" /proc/interrupts
cat /sys/class/net/eth0/device/numa_node

Die Zuordnung setze ich über smp_affinity_list, damit ich keine Hexmasken rechnen muss. Beispiel: RX-Queues 0–7 auf Kerne 2–9:

# Beispiel: IRQs den Cores 2-9 zuordnen (eine Zeile pro IRQ)
echo 2  > /proc/irq/<irq_of_RxQ0>/smp_affinity_list
echo 3  > /proc/irq/<irq_of_RxQ1>/smp_affinity_list
echo 4  > /proc/irq/<irq_of_RxQ2>/smp_affinity_list
...
echo 9  > /proc/irq/<irq_of_RxQ7>/smp_affinity_list

Wichtig: MSI-X muss aktiv sein, damit jede Queue eigene Interrupts hat. Verwende ich manuelles Pinning, sperre ich irqbalance für diese IRQs (z. B. per Blacklist) oder deaktiviere den Dienst gezielt auf Hosts mit statischem Layout. NUMA prüfe ich zusätzlich mit lscpu und der PCIe-Zuordnung, damit ich keine Cross-Node-Pfade erzeuge.

Hash-Konfiguration und Protokolle

Die Hash-Felder lege ich so fest, dass echte Traffic-Muster gut streuen, anstatt auf wenigen Queues zu landen. Für TCP/UDP nutze ich das 4-Tupel, für IPv6 ähnlich, während ich bei VXLAN oder GRE zusätzliche Felder der Kapselung berücksichtige. Manche NICs bieten konfigurierbare Hash-Keys, die ich an die dominante Workload anpasse. Sobald ich Lastcluster auf einzelnen Queues sehe, justiere ich die Hash-Auswahl nach. Dieser Schritt kostet wenig Zeit, verhindert aber eine Schieflage bei hoher Verbindungszahl.

Interrupt-Coalescing und PPS

Ich kombiniere RSS mit moderatem Interrupt-Coalescing, um PPS-intensiven Verkehr in handhabbare Batches zu bündeln. Das senkt Interrupt-Overhead, darf aber die Latenz empfindlicher Dienste nicht verwässern. Deshalb messe ich Round-Trip-Zeiten und ändere Coalescing-Werte schrittweise. Wer Storage- oder Backup-Last fährt, kann deutlicher bündeln als bei L7-APIs oder VoIP. In Summe balanciere ich Latenz gegen Durchsatz, bis beides stimmig ist.

Coalescing in der Praxis: Profile und Messpunkte

Ich starte mit moderaten Defaults und taste mich an das Optimum pro Workload heran. Drei bewährte Ausgangsprofile:

  • API/Low-Latency: rx-usecs 2–6, rx-frames 16–32, adaptive aus
  • Allround: rx-usecs 8–16, rx-frames 32–64, adaptive an
  • Bulk/Storage: rx-usecs 24–48, rx-frames 128–256, adaptive an
ethtool -c eth0
ethtool -C eth0 rx-usecs 12 rx-frames 64 adaptive-rx on

Ich messe dazu p95/p99-Latenzen, PPS, CPU-Last pro Core und Retransmits. Sobald ich bei API/VoIP steigende Varianz sehe, gehe ich mit rx-usecs wieder runter. Bei Storage skaliere ich eher über Frames hoch, um Interrupts zu sparen.

RSS in 10-Gbit-Umgebungen

Auf 10G-NICs arbeite ich meist mit 8 bis 16 Queues pro Port, sofern die CPU genug Kerne bereitstellt. Webserver, Storage-Gateways und Virtualisierungshosts skalieren damit sauber über viele Parallelverbindungen. Ich pinne Hauptqueues an freie Kerne und messe anschließend PPS, Latenz sowie Retransmits. Treten Drops auf, prüfe ich Coalescing, Hash und die Ausnutzung je Queue. Danach finetune ich die Affinität, bis die Auslastung gleichmäßig aussieht.

RSS in 25-Gbit- und Multi-25G-Setups

Mit 25 Gbit/s steigen PPS und Buslast, weshalb ich NUMA-Bewusstsein, genügend Queues und Offloads strenger beachte. Large Receive Offload (LRO) oder RSC können den Paketdruck auf den Stack senken, sofern Anwendungen das tolerieren. Außerdem prüfe ich PCIe-Lanes, um Engpässe abseits des Netzwerks auszuschließen. In Hosts mit mehreren 25G-Links trenne ich Queues und Affinität strikt nach Aufgaben und Nodes. So nutze ich Bandbreite und Kerne effizient, ohne in Cross-Node-Verkehr zu rutschen.

Hardware-/Treiber-Details: was ich beachte

Nicht jede NIC verhält sich gleich. Intel-Generationen (z. B. ixgbe, i40e, ice) bieten Features wie Flow Director/ATR, die Flows gezielt an Queues binden – hilfreich, wenn ich Hotspots glätten will. Mellanox mlx5 kann aRFS in Hardware unterstützen, was CPU-Last senkt, wenn der Stack viele Sockets bedient. Ich entscheide fallweise, ob ich diese Funktionen aktiviere, und messe, ob sie die Verteilung verbessern. Auf Routing/NAT-Systemen lasse ich LRO häufig aus und setze auf GRO, um Header-Konsistenz zu wahren; auf reinen Server-Workloads kann LRO/GRO helfen, den PPS-Druck zu dämpfen. Wichtig sind zudem ausreichend MSI-X-Vektoren pro Queue und korrekte Firmwarestände.

RSS in Virtualisierung und Containern

Im Hypervisor kombiniere ich physische RSS-Queues mit Multi-Queue-fähigen vNICs, etwa virtio-net, damit Gäste keine künstlichen Engpässe sehen. Ich achte auf CPU-Pinning der VMs und lege deren vCPU-NUMA-Nähe zur physischen NIC fest. So bleiben Daten lokal und der Host bezahlt weniger für Speicherzugriffe. Für Container binde ich kritische Pods an passende Cores und halte Host-Queues frei von Störlast. Diese Ordnung erhöht die Effizienz bei Mikroservices, wo viele kleine Flows entstehen.

SR-IOV und VF-RSS richtig nutzen

Mit SR-IOV weise ich VMs eigene VFs zu, die wiederum mehrere Queues und RSS bereitstellen können. Ich plane genügend VFs pro Port, achte auf MSI-X-Kapazität und pinne die VF-IRQs in der VM passend zu deren vCPUs. In Linux-Gästen aktiviere ich Multi-Queue explizit, sonst bleibt die vNIC oft einstufig:

# im Gast (virtio-net-Beispiel)
ethtool -l eth0
ethtool -L eth0 combined 4

Hosts mit mehreren VFs verteile ich strikt pro NUMA-Node und Workload, damit sich VMs nicht gegenseitig in denselben physischen RX-Pfaden stören.

Monitoring und Fehlersuche

Ich überwache die Auslastung pro Queue, einzelne Kerne, Paketverluste und Retransmits, um Schieflagen früh zu erkennen. Wenn ein Kern abhebt und andere frei bleiben, stimmt die Affinität oder die Queue-Anzahl oft nicht. In solchen Fällen prüfe ich Hash-Felder, IRQ-Masken und Coalescing-Werte nacheinander. Zusätzlich schaue ich auf die SoftIRQ-Last, weil sie Hinweise auf Verdrängungseffekte liefert. Erst wenn diese Signale stabil aussehen, skaliere ich Traffic oder erweitere Queues weiter.

RPS, RFS und XPS: Software-Ergänzungen zu RSS

Wenn eine NIC nur wenige Queues hat oder ich Bonding/Tunneling nutze, ergänze ich RSS mit RPS (Receive Packet Steering) und RFS (Receive Flow Steering). RPS verteilt SoftIRQs über Cores, RFS bindet Flows an den Core, auf dem der zugehörige Socket aktiv ist. Ich aktiviere beides gezielt:

# Anzahl Flow-Entries global erhöhen (RFS)
echo 32768 > /proc/sys/net/core/rps_sock_flow_entries

# Pro RX-Queue CPUs für RPS setzen (Beispielmaske, anpassen!)
for f in /sys/class/net/eth0/queues/rx-*/rps_cpus; do echo ffff > "$f"; done

# Pro RX-Queue Flow-Tabelle für RFS setzen
for f in /sys/class/net/eth0/queues/rx-*/rps_flow_cnt; do echo 4096 > "$f"; done

Auf TX-Seite nutze ich XPS (Transmit Packet Steering), damit ausgehende Pakete von dem Core gesendet werden, der sie erzeugt hat:

for f in /sys/class/net/eth0/queues/tx-*/xps_cpus; do echo ffff > "$f"; done

RPS/RFS/XPS kosten etwas CPU, helfen aber, wenn mir auf Hardwareseite Queues fehlen oder wenn ich die Socket-Lokalität strikt halten will.

Single-Flow-Leistung, GRO/TSO und Busy-Polling

Ein einzelner Flow bleibt aus gutem Grund an einen Core gebunden. Will ich die Bandbreite eines Einzelstroms heben, setze ich auf Offloads (GRO/TSO), eine hohe Core-Frequenz und stimmiges Coalescing. Für Latenz-kritische Pfade kann Busy-Polling helfen:

# niedrig ansetzen und messen
sysctl -w net.core.busy_read=25
sysctl -w net.core.busy_poll=25

Busy-Polling reduziert Kontextwechsel, belegt aber CPU-Zeit. Ich aktiviere es nur dort, wo p99-Latenzen zählen, und validiere stets die Effekte auf Gesamtauslastung und Tail-Latency. GRO halte ich auf Servern in der Regel an, LRO setze ich je nach Rolle ein; bei Middleboxes bleibe ich konservativ, um Header-Verarbeitung und Hash-Konsistenz nicht zu stören.

Empfehlungen und Beispiele: Queues, Affinität, Kommandos

Als Ausgangspunkt wähle ich eine Queue-Anzahl, die zur CPU passt, beobachte dann die Last je Queue und passe in Schritten an. Bei 10G reichen häufig 8–16 Queues, bei 25G setze ich oft höher an, sofern die Kerne vorhanden sind. Für Affinität verwende ich klare Masken pro IRQ, damit ich die Pfade später leichter analysiere. Die folgende Tabelle liefert kompakte Richtwerte, die ich anschließend per Messung verifiziere. Erst das Messbild entscheidet, ob ich weiter erhöhe oder reduziere.

Linkgeschwindigkeit Typische RX-Queues Beispiel-Befehle Hinweise
10 Gbit/s 8–16 ethtool -l eth0 | ethtool -L eth0 rx 16 Coalescing moderat halten, L7-Latenz prüfen
25 Gbit/s 16–32+ grep . /proc/interrupts | IRQ-Masken per echo NUMA beachten, PCIe-Lanes prüfen
Multi-25G Pro Port separat vNIC Multi-Queue aktivieren (z. B. virtio) Queues auf Kerne und Workloads aufteilen

Diese Richtwerte bilden nur den Startpunkt, nicht das Ziel, denn Workloads unterscheiden sich stark. Ich protokolliere Änderungen, messe vor und nach der Anpassung und halte die Umgebung ansonsten unverändert. Sobald das System unter Produktionslast stabil bleibt, friere ich die Konfiguration ein. Später erneuere ich Messungen nach Kernel- oder Treiberupdates. So bleibe ich bei RSS sauber auf Kurs und sichere reproduzierbare Ergebnisse.

Typische Stolpersteine und Gegenmaßnahmen

Zu wenige Queues überlasten einzelne Kerne, zu viele erhöhen Verwaltungsaufwand und schaden der Cache-Trefferquote. Eine unglückliche Affinität verlagert Interrupts auf bereits belastete Kerne oder falsche NUMA-Nodes. Auch ein unpassender Hash führt dazu, dass dominante Flows Queues verstopfen. Ich löse das schrittweise: Queue-Anzahl anpassen, Affinität korrigieren, Hash-Felder erweitern, Coalescing feintrimmen. Jede Änderung belege ich mit Metriken, bevor ich zum nächsten Hebel gehe.

Praxisnahe Szenarien

Ein Storage-Server mit 10G profitiert schnell von 8–12 Queues plus moderatem Coalescing, um Bulk-Transfers glatt durchzuziehen. Ein API-Server mit hoher Verbindungszahl braucht oft feinere Hash-Felder und niedrigere Latenz bei Interrupts. Virtualisierungshosts gewinnen deutlich, wenn vNIC-Multi-Queue auf Gastseite aktiv ist und zum Host-Layout passt. Container-Workloads arbeiten runder, wenn kritische Pods nahe an NIC und NUMA-Speicher laufen. Diese Muster erweitere ich situativ, indem ich PPS, Retransmits und Queue-Verteilung vergleiche.

Leistungsfähige Plattformen als Vorteil

Hosting-Setups mit konsequent konfiguriertem RSS, Multi-Queue-NICs und sauberer Affinität liefern spürbare Reserven bei Spitzenlast. Wer Serverangebote bewertet, sollte gezielt nach Multi-Queue-Fähigkeit, NUMA-Pinning und Monitoring fragen. Ein Anbieter, der diese Punkte sichtbar umsetzt, erzielt oft merklich bessere Durchsatzkurven. Für leistungsstarke Server- und Hostinglösungen nenne ich hier webhoster.de als klare Empfehlung. Diese Ausrichtung zahlt sich in Performance und Stabilität aus, besonders bei vielen parallelen Flows.

Zusammenfassung für die Praxis

Ich aktiviere Receive Side Scaling, setze eine vernünftige Queue-Anzahl, binde IRQs an passende Kerne und prüfe die Hash-Konfiguration. Danach optimiere ich Coalescing gegen Latenz, achte auf NUMA-Nähe und verteile Workloads konsistent. In Virtualisierung nutze ich Multi-Queue bis in die Gäste hinein und halte Pinning und Affinität synchron. Messungen zu PPS, Queue-Last, Retransmits und Latenz entscheiden über den nächsten Schritt. Wer so vorgeht, schöpft 10G und 25G real aus, hält Latenz im Rahmen und gewinnt verlässlich Netzwerkrendite.

Aktuelle Artikel

Uncategorized

Wie wählt man den besten Webhosting-Anbieter aus?

Die Wahl des Webhosting-Anbieters zählt zu den wichtigsten Entscheidungen beim Aufbau einer Website. Wer ein Online-Projekt plant – ob Blog, Unternehmensseite oder Shop – steht