...

TCP Small Queues: latentie in het Linux-netwerk doelgericht verminderen

TCP Small Queues beperkt per TCP-flow het aantal nog te verzenden bytes in het Linux-verzendpad en zorgt er zo voor dat Latency inclusief bufferbloat doelgericht verminderen. Ik laat zien hoe dit mechanisme in de Linux-netwerken Stack laat zien hoe ik zinvolle limieten instel en welke wisselwerkingen er ontstaan met pacing, QDiscs en congestiecontrole.

Centrale punten

  • Per-flow-limiet: TSQ beperkt het aantal uitstaande bytes per TCP-socket.
  • Minder bufferbloat: Kortere wachtrijen verlagen de RTT.
  • Tegendruk: Toepassingen schrijven langzamer wanneer de limiet van kracht wordt.
  • Eerlijkheid: Geen enkele afzonderlijke flow neemt volledige wachtrijen in beslag.
  • Adaptief Regeling: De limiet is afhankelijk van de snelheid en de segmentgrootte.

Hoe TCP Small Queues werkt

TSQ begint op het punt waar TCP-segmenten worden QDisc en doorgeeft aan de driver. Als ik gegevens naar een socket schrijf, controleert de kernel vóór elke enqueue de reeds toegewezen bytes voor deze flow. Als de flow de limiet bereikt, markeert de logica de socket als afgeremd en stopt verdere enqueue. Pas wanneer de netwerkkaart bufferruimte vrijgeeft, mag de socket weer verzenden en kan ik opnieuw gegevens in de stack plaatsen. Deze strakke terugkoppeling houdt de Wachtrijen kort en maakt reactietijden voorspelbaarder.

Waarom lange wachtrijen de responstijd beïnvloeden

Grote driver- en QDisc-wachtrijen aanmaken Bufferbloat, vooral bij TSO/GSO en grote verzendvolumes. Een grote download kan de uitgaande wachtrijen vullen, terwijl interactieve stromen zoals SSH, API-aanroepen of VoIP achteraan in de rij komen te staan. De overvolle wachtrij domineert dan de RTT in plaats van de daadwerkelijke verbindingstijd. Congestion Control reageert traag omdat ACK’s te laat binnenkomen, en neemt daardoor slechtere cwnd-beslissingen. TSQ beperkt het aantal vooraf gebufferde bytes per flow, zodat kleine, tijdkritische pakketten snel op de lijn terechtkomen.

Een kijkje onder de motorkap: wat de kernel telt

Onder de oppervlakte telt de kernel geen „pakketten“, maar bytes, om precies te zijn: de geheugenbytes die de socket al in de wachtrij heeft geplaatst. Bepalend is wat de stack aan skbuff-structuren, inclusief truesize heeft toegewezen en nog niet door de NIC is verwerkt. TSQ koppelt hieraan een Gas geven/Gas loslaten‑Pad: Als een socket de kredietlimiet bereikt, zet de stack een beperkingsvlag en roept pas weer op nadat TX-bewerkingen zijn voltooid (NAPI/IRQ) write_space() zodat de toepassing opnieuw mag verzenden. Deze terugkoppeling is sneller dan signalen uit congestiecontrole die uitsluitend op verlies zijn gebaseerd en werkt vóór de QDisc. Met TSO/GSO blijft het mechanisme effectief, omdat de limiet op de voor het bytebudget dat aan de segmentatie ten grondslag ligt: grote superframes worden alleen in de QDisc toegelaten als er voldoende krediet beschikbaar is, waardoor bursts worden beperkt.

Dynamische limieten en tempo

Ik profiteer van TSQ omdat de limiet niet simpelweg statisch blijft, maar op Prijs en let op de segmentgrootte. Het doel is ongeveer één milliseconde aan gegevens in het verzendpad per flow, ongeacht of er 100 Mbit, 1 Gbit of 10 Gbit wordt verzonden. Bij een snelle verbinding neemt het toegestane byte-krediet toe, bij een langzamere verbinding neemt het af. In combinatie met TCP-pacing blijven bursts klein en komen bevestigingen sneller terug. Zo bereik ik merkbaar lagere Pieken in latentie, zonder de doorvoer onnodig te beperken.

Interactie per socket en per app

TSQ werkt alleen als ook de applicatie de tegendruk waarneemt. Daarom houd ik rekening met instellingen zoals SO_SNDBUF, TCP_NOTSENT_LOWAT en autocorking. Een te groot verzendbuffervenster kan op korte termijn veel bytes in de stack duwen; TSQ remt weliswaar af, maar de app merkt het pas op als send() geblokkeerd is of de foutcode EAGAIN retourneert. Met TCP_NOTSENT_LOWAT ik haal het „niet-verzonden“ deel in userland terug en vul daarmee TSQ aan aan de kernelzijde. Autocorking (of expliciet TCP_CORK/MSG_MORE) helpt bij het bundelen van kleine schrijfbewerkingen zonder dat dit leidt tot pieken in de latentie. Pacing-limieten per socket (bijv. via SO_MAX_PACING_RATE) sluiten aan bij TSQ: de frequentie zorgt voor een afvlakking in de tijd, de byte-limiet zorgt voor een beperking in de ruimte. Belangrijk: TCP_NODELAY schakelt Nagle uit en kan de interactiviteit vergroten, maar zonder TSQ neemt het risico op bursts toe; met TSQ heb ik beide onder controle.

Praktijkgids: zinvolle TSQ-waarden

Het algemene kader leg ik vast met net.ipv4.tcp_limit_output_bytes (Sysctl). De gebruikelijke standaardwaarden liggen tussen de 128 en 262 KB per flow. Voor veel web- en API-workloads kies ik lagere waarden, zodat interactieve reacties vlot blijven. Voor back-ups of replicatie verhoog ik de limiet gematigd, zolang de RTT stabiel blijft. Wie dieper in de queue-pagina wil duiken, vindt basisinformatie over Pakketwachtrijen op de server, die helpen bij de indeling.

Scenario Link-snelheid Richtwaarde tcp_limit_output_bytes Doel
API/HTTP zeer interactief 100 Mbit – 1 Gbit 64–128 KB laag RTT, korte spikes
Gemengde belasting: web + downloads 1–10 Gbit 128–256 KB Balans uit Doorvoer en latentie
Replicatie/back-ups 1–10 Gbit 256–512 KB constante bulkstroom, aanvaardbaar Latency
WAN met hoge RTT 10–100 Mbit 96–192 KB kortere bursts, eerlijkere Cues

QDisc en congestiebeheer in combinatie

TSQ werkt aan de ingang van de QDisc, terwijl algoritmen zoals fq_codel de congestie op de lijn beheren. Samen zorgen ze ervoor dat wachtrijen worden verkort en dat de verdeling eerlijk blijft. Met TCP BBR daar heb ik ook nog eens voordeel van, omdat realistischere RTT-metingen leiden tot een betere pacing en cwnd-regeling. CUBIC reageert ook soepeler als ik te lange wachttijden uitschakel. Zo groeit de doorvoer op een organische manier, terwijl de Reactietijd onder controle blijft.

Virtualisatie en cloudstacks

In VM’s stapelen meerdere bufferniveaus zich op: gast-QDisc, virtio/vhost-wachtrijen, host-QDisc en de fysieke NIC. Ik houd TSQ in de gast actief en stel daar een conservatieve limiet in, zodat er geen grote bursts in de host terechtkomen. Op de hypervisor zorg ik met fair QDiscs, gematigde TX-ringen en nette IRQ-pinning voor korte latentieketens. SR-IOV kan de latentie verlagen, maar verschuift de verantwoordelijkheid naar de gasten: zonder TSQ in de gast dreigen lange VF-wachtrijen. In containers past TSQ per NetNS zoals gewoonlijk; met cgroup-pacing en CPU-limieten voorkom ik dat een luidruchtige buur indirect de latentie verhoogt. Het is ook belangrijk om te kijken naar coalescing en offloads in het virtio-pad: overmatige bundeling verlengt de ACK-tijden, te weinig vermindert de efficiëntie – ik pas dit aan op basis van de latentiedoelstelling, niet dogmatisch.

Wifi en embedded systemen: speciale gevallen correct aanpakken

Bij WLAN-verbindingen is de Aggregatie in de MAC-laag. Als ik te weinig bytes in het verzendpad laat staan, kan het stuurprogramma minder frames bundelen, wat de efficiëntie vermindert. In dergelijke opstellingen verhoog ik de limiet voorzichtig en controleer ik de aggregatiegraad. OpenWrt- en embedded-platforms profiteren bovendien van gestroomlijnde paden in stuurprogramma’s en minder atomaire bewerkingen. Ik test elke aanpassing onder reële draadloze belasting voordat ik een Profiel breed uitrollen.

Monitoring en statistieken die echt tellen

Ik observeer de RTT-Verdeling per socket en let op uitschieters, niet alleen op gemiddelden. Met ss, tc en exporters lees ik wachtrijlengtes, hertransmissies en pacing_rate uit. eBPF-programma’s leveren mij gebeurtenissen wanneer sockets worden afgeremd en weer vrij komen. Time-to-First-Byte en het 95e/99e percentiel laten zien of TSQ zijn werk doet. Zonder meetwaarden blijft elke Optimalisatie een blinde vlucht.

Zinvolle A/B- en belastingstests

Ik meet TSQ-effecten op een reproduceerbare manier: eerst een baseline zonder wijzigingen, daarna geïsoleerde parameter-sweeps (bijv. 64, 96, 128, 192 KB). Voor gemengde workloads draai ik parallelle streams (bulk + veel korte verzoeken) en vergelijk ik het 95e en 99e percentiel van de latenties, niet alleen de mediaan. Als ik testruns duidelijk onderbreek (opwarmen, meetvenster, afkoelen), blijven artefacten herkenbaar. Ik let op constante factoren: dezelfde payload-patronen, identieke route/MTU, identieke CPU-frequenties van server en client. Op WAN-trajecten simuleer ik vertraging/jitter/verlies met tc netem, om te controleren of de TSQ-limieten bij een hoge BDP niet te vroeg worden afgetopt. Pas wanneer de percentielen dichter bij elkaar komen te liggen en het aantal heruitzendingen en verlies stabiel blijven, neem ik de waarden over naar de productie.

Hardware-optimalisatie en details over stuurprogramma’s

Ik controleer de TSO/GSO-instellingen, de ringbuffer van de NIC en de IRQ-regeling, zodat TSQ goed werkt. Te grote TX-ringen verlengen de wachtrij bij het apparaat; te kleine ringen verminderen de benutting. Grove interruptbundeling vertraagt de ACK’s, fijne bundeling verhoogt de CPU-belasting. Ik pas de uitleg aan de praktijk aan en verwijs ter inleiding naar Interrupt samenvoegen. Het doel blijft een betrouwbare Latency bij een haalbare doorvoercapaciteit.

NUMA, RSS en CPU-affiniteit

Korte wachtrijen hebben weinig nut als pakketten voortdurend over NUMA-grenzen heen gaan. Ik koppel RX/TX-wachtrijen via RSS/irqbalance aan kernen van hetzelfde NUMA-domein waarop de app ook draait. Met XPS/RPS bepaal ik welke CPU’s TX-werk overnemen, waardoor ik cross-socket-hoppers vermijd. Minder cache-misses en minder lock-contention helpen TSQ indirect: Voltooiingen komen sneller terug, de socket wordt eerder „ontthroatled“ en er treden geen latentiepieken op. Bij zeer veel flows per host plan ik voldoende wachtrijen in en voorkom ik dat meerdere luidruchtige flows op dezelfde TX-ring met elkaar in botsing komen.

Stap voor stap: TSQ actief controleren

Ik begin met een blik op Sysctl: sysctl net.ipv4.tcp_limit_output_bytes geeft de huidige limiet weer. Vervolgens raadpleeg ik ss -tin voor afzonderlijke sockets, let ik op send‑q en rtt, en vergelijk ik belastingfasen met en zonder aanpassing van de limiet. Met iperf3 genereer ik achtergrondbelasting en meet ik tegelijkertijd API-responstijden om prioriteiten zichtbaar te maken. tc -s qdisc geeft me de pakket- en drop-aantallen van de uitgaande discipline. Blijven het 95e en 99e percentiel dicht bij elkaar en de CPU‑Belasting in het frame, pas de limiet aan.

Veelvoorkomende misvattingen en anti-patronen

  • „Meer buffer = meer prestaties“: dit klopt voor doorvoertests zonder latentiedoel, maar gaat niet op voor interactieve diensten. TSQ vervangt overgedimensioneerde wachtrijen door een op de behoefte afgestemd krediet per flow.
  • „TSQ kost doorvoer“: Als het goed is ingesteld, beperkt TSQ de bursts, niet de gemiddelde snelheid. Bij bulk-workloads schaal ik de limiet gematigd omhoog en meet ik de percentielen in plaats van alleen de piek in Mbit/s.
  • „Pacing alleen is voldoende“: Tijdmatige afvlakking is belangrijk, maar zonder byte-limiet glijden grote GSO-frames toch in de QDisc. TSQ en pacing vullen elkaar aan.
  • „Eén waarde voor alles“: workloads, links en NIC’s verschillen van elkaar. Ik werk met bereikwaarden en valideer per omgeving.
  • „Alleen TCP is betrokken“: de focus ligt op TCP, maar het systeem beschikt over nog meer instelmogelijkheden (bijvoorbeeld voor de UDP-belasting). Ik voorkom dat parallelle protocollen dezelfde wachtrijen ongecontroleerd verstoppen.

Conclusie: latentie doelgericht onder controle

TSQ verplaatst het beheer van driver-wachtrijen naar de Socket en vermindert zo congestie direct bij de bron. Ik beperk het aantal vooraf gebufferde bytes per flow en zorg zo voor snelle ACK’s, een lagere RTT en eerlijk verdeelde wachtrijen. In combinatie met fq_codel en moderne congestiecontrole blijft de reactietijd ook onder belasting betrouwbaar. Voor speciale gevallen met wifi en embedded systemen hanteer ik aangepaste limieten en voer ik tests uit onder reële omstandigheden. Wie de statistieken in de gaten houdt en de limieten stapsgewijs aanpast, houdt de Latency consequent laag, zonder onnodig aan doorvoer in te boeten.

Huidige artikelen

Server met geoptimaliseerde TCP Small Queues om de netwerklatentie te verminderen
Servers en virtuele machines

TCP Small Queues: latentie in het Linux-netwerk doelgericht verminderen

TCP Small Queues in de Linux-kernel beperkt het aantal gebufferde TCP-pakketten per flow en is een krachtig hulpmiddel voor het optimaliseren van de latentie. Ontdek hoe TSQ bufferbloat vermindert en de responstijden van servers verbetert.

Datacenter met serverracks en gestileerde datavisualisatie voor het optimaliseren van de MySQL-prestaties
Databases

MySQL-histogrammen – Betere queryplannen zonder index

Ontdek hoe MySQL-histogrammen de optimizer voorzien van nauwkeurige optimizer-statistieken, betere queryplannen mogelijk maken en je SQL-tuning aanzienlijk verbeteren zonder dat je extra indexen hoeft toe te voegen.