...

Receive Side Scaling bij 10 en 25 Gbit/s: prestatieoptimalisatie voor moderne Linux-servernetwerken

Receive Side Scaling verdeelt het netwerkverkeer via 10- en 25-Gbit/s-verbindingen gericht over meerdere cores, zodat Linux-servers hoge doorvoersnelheden met een lage latentie kunnen verwerken. Ik laat in de praktijk zien hoe ik RSS activeer, de Cues op de map van Core en zo knelpunten bij interrupts en cache-hits vermijd.

Centrale punten

Ik vat de belangrijkste punten kort samen, zodat je snel de volgende stappen kunt plannen.

  • Belastingverdeling: Pakketten worden via verschillende wachtrijen naar verschillende cores gestuurd.
  • Cache-locatie: Een flow blijft altijd in dezelfde wachtrij.
  • Hashing: 4-tupel-hash verdeelt de flows gelijkmatig over de wachtrijen.
  • affiniteit: Gerichte IRQ-toewijzing vermindert de latentie.
  • Schalen: Vanaf 10/25 Gbit/s garandeert RSS een hoge doorvoersnelheid.

Deze punten zijn onderling met elkaar verweven en vormen de basis voor de Prestaties over reële workloads. Ik geef eerst prioriteit aan het juiste aantal wachtrijen, daarna aan de CPU-affiniteit. Vervolgens controleer ik de hash-parameters en de fijnafstellingen.

Wat Receive Side Scaling te bieden heeft

RSS verdeelt de ontvangst van pakketten in meerdere Ontvangstwachtrijen, die ik aan specifieke CPU-kernen toewijs, zodat geen enkele kern een bottleneck wordt. Dit vermindert scherpe interruptpieken en zorgt voor een gelijkmatige verwerking via SoftIRQ’s, waardoor latentiepieken worden verminderd en de doorvoer wordt verhoogd. Elke wachtrij activeert eigen interrupts, die ik vast aan kernen koppel om datapaden consistent te houden. Deze consistentie bevordert de Cache-Lokaliteit, omdat een flow altijd op dezelfde kern stuit. Juist dit samenspel draagt bij hoge PPS-snelheden direct bij aan meetbare efficiëntie.

Zo werkt RSS technisch gezien

De NIC vormt op basis van het bron-/doel-IP-adres en de bron-/doelpoort een Hash en gebruikt deze als index voor de Indirection-Table, die naar wachtrijen verwijst. Op deze manier komen pakketten van een flow altijd in dezelfde wachtrij terecht en blijven ze dus aan dezelfde kernel gekoppeld. Verschillende flows worden gelijkmatig verdeeld, mits de hash-keys en protocolvelden op de juiste manier zijn geconfigureerd. Het werk komt daarmee dicht in de buurt van de Hardware, waardoor de kernel minder hoeft te balanceren en de overhead afneemt. Dat is precies wat ik wil, om bij 10G/25G de pakketverwerking per kern laag te houden.

Waarom RSS vanaf 10 en 25 Gbit/s telt

Bij 1 Gbit/s is er vaak slechts één Kern de pakketbelasting, maar vanaf 10 Gbit/s verschuift het evenwicht snel. Kleine pakketten zorgen ervoor dat het PPS-cijfer stijgt, waardoor een kern al snel een belasting van 100 procent bereikt en er drops optreden. Precies op dat moment werkt RSS als een vermenigvuldiger voor de bruikbare bandbreedte. Ik verdeel de belasting over meerdere Kernen, verminder contextwisselingen en houd de latentiekrommen stabieler. Het resultaat: de werkelijke doorvoersnelheid benadert pas met een zuivere RSS de linkrate.

RSS instellen op de Linux-server

Onder Linux bedien ik RSS vooral via ethtool, stuurprogramma-opties en sysfs, zodat de mogelijkheden van de netwerkkaart daadwerkelijk worden benut. Eerst lees ik het maximale aantal RX-kanalen uit, daarna stel ik het aantal wachtrijen in dat bij de CPU past. Vervolgens controleer ik de RSS-hash voor TCP/UDP en optioneel voor VLAN of tunneling, zodat de belastingprofielen netjes verdeeld blijven. Voor de achtergrondruis van de interruptverdeling helpt mij IRQ-balancering, ook al geef ik er de voorkeur aan om kritieke wachtrijen handmatig te pinnen. Zo koppel ik Cues nauw aansluitend op de topologie van de host en voorkom storende migraties.

Indirection-Table, RSS-Key en hash-fijnafstemming: concrete commando’s

Ik controleer eerst de huidige toewijzing en de sleutel van de NIC:

ethtool -x eth0 # Indirection-Table (RX-wachtrijen) en RSS-sleutel weergeven
ethtool -n eth0 rx-flow-hash tcp4
ethtool -n eth0 rx-flow-hash udp4

Voor een nette, gelijkmatige verdeling stel ik de Indirection-Table in op het gewenste aantal wachtrijen. Bij 16 wachtrijen kies ik voor een gelijkmatige toewijzing:

ethtool -X eth0 equal 16  # gelijkmatige verdeling over 16 wachtrijen

Indien nodig pas ik de hash-velden aan. Voor TCP4 met 4-tupels (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

Bij sommige drivers kun je ook een eigen RSS-sleutel instellen (bijvoorbeeld voor een betere verspreiding in speciale gevallen):

ethtool -X eth0 hkey   # alleen als het stuurprogramma/de netwerkkaart dit ondersteunt

CPU-affiniteit en NUMA correct instellen

Ik breng elke RX-wachtrij in kaart via IRQ affiniteit aan specifieke kernen en houd daarbij rekening met NUMA, zodat gegevens slechts kort via de geheugencontroller lopen. Als de NIC op node 0 draait, koppel ik de hoofdwachtrijen ook aan kernen op node 0 en plaats ik workloads in de buurt. Deze nabijheid vermindert toegang op afstand en verlaagt de geheugenlatentie aanzienlijk. Hierbij is het handig om een profiel voor productieve wachtrijen te gebruiken, evenals aparte cores voor beheer- en offload-taken. Wie zich hier verder in wil verdiepen, vindt aanwijzingen voor de fijnafstemming onder IRQ affiniteit, wat betreft de planning per Kern vereenvoudigd.

IRQ-affiniteitshandleiding: van IRQ's naar een stabiele core-toewijzing

Ik zoek eerst uit welke IRQ's bij de RX-wachtrijen horen, en koppel ze vervolgens vast:

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

Ik stel de toewijzing in via smp_affinity_list, zodat ik geen hex-maskers hoef te berekenen. Voorbeeld: RX-wachtrijen 0–7 op kernen 2–9:

# Voorbeeld: IRQ’s toewijzen aan de cores 2-9 (één regel per IRQ)
echo 2  > /proc/irq//smp_affinity_list
echo 3  > /proc/irq//smp_affinity_list
echo 4  > /proc/irq//smp_affinity_list
...
echo 9  > /proc/irq//smp_affinity_list

Belangrijk: MSI-X moet ingeschakeld zijn, zodat elke wachtrij zijn eigen interrupts heeft. Als ik handmatige pinning gebruik, blokkeer ik irqbalans voor deze IRQ's (bijvoorbeeld via een blacklist) of schakel de dienst gericht uit op hosts met een statische lay-out. NUMA controleer ik bovendien met lscpu en de PCIe-toewijzing, zodat ik geen cross-node-paden creëer.

Hash-configuratie en protocollen

Ik stel de hash-velden zo in dat echte Verkeer-Zorg ervoor dat de patronen goed worden gespreid, in plaats van op slechts enkele wachtrijen terecht te komen. Voor TCP/UDP gebruik ik het 4-tupel, voor IPv6 iets vergelijkbaars, terwijl ik bij VXLAN of GRE rekening houd met extra velden van de inkapseling. Sommige NIC’s bieden configureerbare hash-keys, die ik aanpas aan de dominante workload. Zodra ik lastclusters op afzonderlijke wachtrijen zie, pas ik de hash-selectie aan. Deze stap kost weinig tijd, maar voorkomt een onevenwicht bij een hoog aantal verbindingen.

Interrupt-coalescing en PPS

Ik combineer RSS met een gematigde Interrupt samenvoegen, om PPS-intensief verkeer te bundelen in beheersbare batches. Dit vermindert de interrupt-overhead, maar mag de latentie van gevoelige diensten niet verslechteren. Daarom meet ik round-trip-tijden en pas ik de coalescing-waarden stapsgewijs aan. Wie opslag- of back-upverkeer verwerkt, kan duidelijker bundelen dan bij L7-API’s of VoIP. Al met al breng ik Latency tegen de doorvoer, totdat beide kloppen.

Coalescing in de praktijk: profielen en meetpunten

Ik begin met gematigde standaardinstellingen en zoek langzaam naar de optimale instellingen voor elke workload. Drie beproefde startprofielen:

  • API/lage latentie: rx-usecs 2–6, rx-frames 16–32, adaptief uit
  • Allround: rx-usecs 8–16, rx-frames 32–64, adaptief aan
  • Bulk/Opslag: rx-usecs 24–48, rx-frames 128–256, adaptief aan
ethtool -c eth0
ethtool -C eth0 rx-usecs 12 rx-frames 64 adaptive-rx on

Ik meet hiervoor p95/p99-latenties, PPS, CPU-belasting per core en heruitzendingen. Zodra ik bij API/VoIP een toenemende variantie zie, ga ik over op rx-usecs weer omlaag. Bij opslag schaal ik eerder op via frames om interrupts te besparen.

RSS in 10-Gbit-omgevingen

Op 10G-NIC's werk ik meestal met 8 tot 16 Cues per poort, mits de CPU voldoende kernen ter beschikking stelt. Webservers, opslaggateways en virtualisatiehosts schalen hiermee soepel over vele parallelle verbindingen. Ik koppel hoofdwachtrijen aan vrije kernen en meet vervolgens PPS, latentie en het aantal hertransmissies. Als er drops optreden, controleer ik coalescing, hash en de benutting per wachtrij. Daarna verfijn ik de affiniteit, totdat de bezettingsgraad gelijkmatig lijkt.

RSS in 25-Gbit- en Multi-25G-configuraties

Bij 25 Gbit/s stijgen de PPS en de busbelasting, en daarom NUMA-Er meer aandacht besteden aan voldoende wachtrijen en offloads. Large Receive Offload (LRO) of RSC kunnen de pakketdruk op de stack verlagen, mits applicaties dit toelaten. Daarnaast controleer ik de PCIe-lanes om knelpunten buiten het netwerk uit te sluiten. Op hosts met meerdere 25G-verbindingen scheid ik wachtrijen en affiniteit strikt op basis van taken en knooppunten. Zo maak ik gebruik van Bandbreedte en kernen efficiënt, zonder in cross-node-verkeer terecht te komen.

Hardware-/stuurprogramma-details: waar ik op let

Niet elke NIC gedraagt zich hetzelfde. Intel-generaties (bijv. ixgbe, i40e, ice) bieden functies zoals Flow Director/ATR, die flows gericht aan wachtrijen koppelen – handig als ik hotspots wil afvlakken. Mellanox mlx5 kan aRFS in de hardware ondersteunen, wat de CPU-belasting verlaagt wanneer de stack veel sockets bedient. Ik beslis per geval of ik deze functies activeer en meet of ze de verdeling verbeteren. Op routing-/NAT-systemen schakel ik LRO vaak uit en gebruik ik GRO om de consistentie van headers te behouden; bij zuivere server-workloads kan LRO/GRO helpen om de PPS-druk te temperen. Daarnaast zijn voldoende MSI-X-vectoren per wachtrij en de juiste firmwareversies belangrijk.

RSS in virtualisatie en containers

In de hypervisor combineer ik fysieke RSS-Wachtrijen met vNIC’s die meerdere wachtrijen ondersteunen, zoals virtio-net, zodat gasten geen kunstmatige knelpunten ondervinden. Ik let op CPU-pinning van de VM’s en stel hun vCPU-NUMA-nabijheid tot de fysieke NIC in. Zo blijven gegevens lokaal en betaalt de host minder voor geheugentoegang. Voor containers koppel ik kritieke pods aan geschikte cores en houd ik host-wachtrijen vrij van storende belasting. Deze ordening verhoogt de Efficiëntie bij microservices, waar veel kleine processen ontstaan.

SR-IOV en VF-RSS correct gebruiken

Met SR-IOV wijs ik VM’s eigen VF’s toe, die op hun beurt weer meerdere wachtrijen en RSS’en kunnen aanbieden. Ik plan voldoende VF's per poort, let op de MSI-X-capaciteit en pin de VF-IRQ's in de VM af op hun vCPU's. In Linux-gasten schakel ik Multi-Queue expliciet in, anders blijft de vNIC vaak eentraps:

# in de gast (virtio-net-voorbeeld)
ethtool -l eth0
ethtool -L eth0 combined 4

Hosts met meerdere virtuele machines verdeel ik strikt per NUMA-node en per workload, zodat virtuele machines elkaar niet hinderen in dezelfde fysieke RX-paden.

Bewaking en probleemoplossing

Ik houd de bezettingsgraad per Wachtrij, afzonderlijke kernen, pakketverlies en hertransmissies, om onevenwichtigheden in een vroeg stadium te herkennen. Als één kern uit de toon valt en andere vrij blijven, klopt de affiniteit of het aantal wachtrijen vaak niet. In dergelijke gevallen controleer ik achtereenvolgens hash-velden, IRQ-maskers en coalescing-waarden. Daarnaast kijk ik naar de SoftIRQ-belasting, omdat het aanwijzingen geeft voor verdringingseffecten. Pas als deze signalen stabiel lijken, schaal ik het verkeer op of breid ik uit Cues doorgaan.

RPS, RFS en XPS: aanvullingen op RSS

Als een NIC maar een paar wachtrijen heeft of als ik bonding/tunneling gebruik, vul ik RSS aan met RPS (Receive Packet Steering) en RFS (Receive Flow Steering). RPS verdeelt SoftIRQ's over de cores, RFS koppelt flows aan de core waarop de bijbehorende socket actief is. Ik schakel beide doelgericht in:

# Het aantal flow-entries globaal verhogen (RFS)
echo 32768 > /proc/sys/net/core/rps_sock_flow_entries

# CPU's per RX-wachtrij instellen voor RPS (voorbeeldmasker, aanpassen!)
for f in /sys/class/net/eth0/queues/rx-*/rps_cpus; do echo ffff > "$f"; done

# Pro RX-wachtrij flow-tabel instellen voor RFS
for f in /sys/class/net/eth0/queues/rx-*/rps_flow_cnt; do echo 4096 > "$f"; done

Aan de TX-zijde gebruik ik XPS (Transmit Packet Steering), zodat uitgaande pakketten worden verzonden door de core die ze heeft gegenereerd:

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

RPS/RFS/XPS kosten wat CPU-vermogen, maar zijn handig als ik op hardwaregebied te weinig wachtrijen heb of als ik de socket-lokaliteit strikt wil handhaven.

Single-Flow-prestaties, GRO/TSO en Busy-Polling

Een afzonderlijke stroom blijft om goede redenen aan een core gekoppeld. Als ik de bandbreedte van een afzonderlijke stroom wil vergroten, maak ik gebruik van offloads (GRO/TSO), een hoge core-frequentie en goed afgestemde coalescing. Voor latentiegevoelige paden kan Bezig met polling helpen:

# laag instellen en meten
sysctl -w net.core.busy_read=25
sysctl -w net.core.busy_poll=25

Busy-polling vermindert contextwisselingen, maar neemt wel CPU-tijd in beslag. Ik schakel het alleen in wanneer p99-latenties van belang zijn, en controleer altijd de effecten op de totale belasting en de tail-latentie. GRO laat ik op servers doorgaans ingeschakeld; LRO pas ik toe afhankelijk van de rol; bij middleboxes blijf ik conservatief om de verwerking van headers en de hash-consistentie niet te verstoren.

Aanbevelingen en voorbeelden: wachtrijen, affiniteit, commando’s

Als uitgangspunt kies ik een aantal wachtrijen dat past bij de CPU Pas dit aan, houd vervolgens de belasting per wachtrij in de gaten en pas deze stapsgewijs aan. Bij 10G zijn vaak 8–16 wachtrijen voldoende; bij 25G ga ik vaak uit van een hoger aantal, mits de kernen beschikbaar zijn. Voor affiniteit gebruik ik duidelijke maskers per IRQ, zodat ik de paden later gemakkelijker kan analyseren. De volgende tabel geeft beknopte richtwaarden, die ik vervolgens door middel van metingen verifieer. Pas het meetresultaat bepaalt of ik meer verhoog of verlaag.

Snelheid van de verbinding Typische RX-wachtrijen Voorbeeldopdrachten Opmerkingen
10 Gbit/s 8-16 ethtool -l eth0 | ethtool -L eth0 rx 16 Coalescing op een gematigd niveau houden, L7-latentie controleren
25 Gbit/s 16–32+ grep . /proc/interrupts | IRQ-maskers via echo NUMA Let op: controleer de PCIe-lanes
Multi-25G Per poort afzonderlijk vNIC Multi-Queue inschakelen (bijv. virtio) Wachtrijen op kernen en Werklasten delen

Deze richtwaarden vormen slechts het uitgangspunt, niet het einddoel, aangezien workloads sterk van elkaar verschillen. Ik registreer wijzigingen, voer metingen uit voor en na de aanpassing en laat de omgeving verder ongewijzigd. Zodra het systeem stabiel blijft onder productielast, leg ik de configuratie vast. Later voer ik opnieuw metingen uit na updates van de kernel of stuurprogramma's. Zo blijf ik bij RSS precies op koers en betrouwbare, reproduceerbare resultaten.

Typische struikelblokken en maatregelen om deze te voorkomen

Te weinig Cues Overbelasten afzonderlijke kernen; te veel kernen verhogen de administratieve last en hebben een negatieve invloed op het cache-hitpercentage. Een ongelukkige affiniteit leidt ertoe dat interrupts worden doorgestuurd naar kernen die al zwaar belast zijn of naar verkeerde NUMA-knooppunten. Ook een ongeschikte hash zorgt ervoor dat dominante flows de wachtrijen verstoppen. Ik los dit stapsgewijs op: het aantal wachtrijen aanpassen, de affiniteit corrigeren, hashvelden uitbreiden, coalescing verfijnen. Elke wijziging documenteer ik met Metriek, voordat ik naar de volgende hendel ga.

Praktijkgerichte scenario's

Een opslagserver met 10G profiteert al snel van 8–12 Cues plus gematigde coalescing om bulkoverdrachten soepel te laten verlopen. Een API-server met een groot aantal verbindingen heeft vaak fijnmazigere hash-velden en een lagere latentie bij interrupts nodig. Virtualisatiehosts profiteren aanzienlijk wanneer vNIC-Multi-Queue aan de gastzijde actief is en aansluit bij de hostindeling. Container-workloads draaien soepeler wanneer kritieke pods dicht bij de NIC en NUMA-geheugen draaien. Deze patronen breid ik situationeel uit door PPS, vergelijk retransmissies en de verdeling over de wachtrijen.

Krachtige platforms als voordeel

Hostingconfiguraties met consistent ingestelde RSS, Multi-Queue-NIC’s en een goede affiniteit zorgen voor merkbare reserves bij piekbelasting. Wie serveraanbiedingen beoordeelt, moet specifiek vragen naar Multi-Queue-mogelijkheden, NUMA-pinning en monitoring. Een aanbieder die deze punten zichtbaar implementeert, behaalt vaak merkbaar betere doorvoercurves. Voor krachtige server- en hostingoplossingen noem ik hier webhoster.de als een duidelijke aanbeveling. Deze focus loont zich in Prestaties en stabiliteit, vooral bij veel gelijktijdige flows.

Samenvatting voor de praktijk

Ik activeer Ontvang Side Scaling: stel een redelijk aantal wachtrijen in, koppel IRQ’s aan geschikte kernen en controleer de hash-configuratie. Vervolgens optimaliseer ik coalescing met het oog op latentie, let ik op NUMA-nabijheid en verdeel ik workloads consistent. Bij virtualisatie maak ik gebruik van multi-queue tot in de gasten en houd ik pinning en affiniteit synchroon. Metingen van PPS, wachtrijbelasting, hertransmissies en latentie bepalen de volgende stap. Wie zo te werk gaat, benut 10G en 25G daadwerkelijk en houdt Latency in dit kader en behaalt op betrouwbare wijze netwerkrendement.

Huidige artikelen

Serverrack met gemarkeerde RAM-modules ter illustratie van de ZFS ARC-cache in een datacenter
Servers en virtuele machines

ZFS ARC-cache: het geheugengebruik goed begrijpen

Ontdek hoe de ZFS ARC-cache werkt, waarom een hoog RAM-gebruik normaal is en hoe je het geheugengebruik op de juiste manier kunt instellen om de ZFS-prestaties te verbeteren.