TCP Small Queues begrenzt pro TCP‑Flow die ausstehenden Bytes im Linux‑Sendeweg und drückt so Latenz samt Bufferbloat gezielt nach unten. Ich zeige, wie dieser Mechanismus im linux networking Stack wirkt, wie ich sinnvolle Limits setze und welche Wechselwirkungen mit Pacing, QDiscs und Congestion Control entstehen.
Zentrale Punkte
- Per‑Flow‑Limit: TSQ deckelt die ausstehenden Bytes je TCP‑Socket.
- Weniger Bufferbloat: Kürzere Warteschlangen senken die RTT.
- Backpressure: Anwendungen schreiben langsamer, wenn das Limit greift.
- Fairness: Kein einzelner Flow belegt komplette Queues.
- Adaptive Steuerung: Das Limit folgt Rate und Segmentgröße.
Wie TCP Small Queues wirkt
TSQ setzt an der Stelle an, an der TCP Segmente an QDisc und Treiber übergibt. Schreibe ich Daten in einen Socket, prüft der Kernel vor jedem Enqueue die bereits allokierten Bytes für diesen Flow. Erreicht der Flow das Limit, markiert die Logik den Socket als gedrosselt und stoppt weiteres Enqueue. Erst wenn die Netzwerkkarte Puffer freigibt, darf der Socket wieder senden und ich kann erneut Daten in den Stack schieben. Dieser enge Rückdruck hält die Warteschlangen kurz und macht Reaktionszeiten vorhersagbarer.
Warum große Queues die Reaktionszeit treiben
Große Treiber‑ und QDisc‑Queues erzeugen Bufferbloat, vor allem mit TSO/GSO und hohen Sendevolumina. Ein fetter Download kann die Ausgangsqueues füllen, während interaktive Flows wie SSH, API‑Calls oder VoIP hinten anstehen. Die überhöhte Warteschlange dominiert dann die RTT statt der eigentlichen Link‑Zeit. Congestion Control reagiert träge, weil Acks spät eintreffen, und trifft schlechtere cwnd‑Entscheidungen. TSQ limitiert die vorgepufferten Bytes je Flow, damit kleine, zeitkritische Pakete zügig auf die Leitung kommen.
Blick unter die Haube: was der Kernel zählt
Unter der Oberfläche zählt der Kernel keine „Pakete“, sondern Bytes, genauer: die vom Socket bereits in Queue gegebenen Speicher‑Bytes. Maßgeblich ist, was der Stack an skbuff‑Strukturen samt truesize allokiert hat und noch nicht von der NIC abgearbeitet wurde. TSQ koppelt daran einen Throttle/Unthrottle‑Pfad: Erreicht ein Socket den Kredit, setzt der Stack ein Drossel‑Flag und ruft erst nach TX‑Fertigstellungen (NAPI/IRQ) wieder write_space() auf, damit die Anwendung erneut senden darf. Diese Rückkopplung ist schneller als rein verlustbasierte Signale aus Congestion Control und wirkt vor der QDisc. Mit TSO/GSO bleibt der Mechanismus effektiv, weil das Limit am vor der Segmentierung liegenden Byte‑Budget ansetzt: Große Super‑Frames werden nur in die QDisc gelassen, wenn genügend Kredit frei ist, wodurch Bursts eingehegt werden.
Dynamische Limits und Pacing
Ich profitiere von TSQ, weil das Limit nicht stumpf statisch bleibt, sondern auf Rate und Segmentgröße achtet. Ziel sind etwa eine Millisekunde Daten im Sendeweg pro Flow, egal ob 100 Mbit, 1 Gbit oder 10 Gbit anliegen. Bei schneller Leitung steigt der erlaubte Byte‑Kredit, bei langsamer Leitung sinkt er. In Kombination mit TCP‑Pacing bleiben Bursts klein und Acks kommen schneller zurück. So erreiche ich spürbar geringere Latenzspitzen, ohne den Durchsatz unnötig zu drosseln.
Per‑Socket‑ und App‑Interaktion
TSQ entfaltet seine Wirkung nur, wenn auch die Applikation den Backpressure spürt. Ich berücksichtige daher Einstellungen wie SO_SNDBUF, TCP_NOTSENT_LOWAT und Autocorking. Ein zu großes Sendebuffer‑Fenster kann kurzfristig viele Bytes in den Stack drücken; TSQ bremst zwar, aber die App merkt es erst, wenn send() blockiert oder EAGAIN liefert. Mit TCP_NOTSENT_LOWAT zügle ich den „unversandten“ Anteil im Userland und ergänze damit TSQ auf der Kernel‑Seite. Autocorking (oder explizit TCP_CORK/MSG_MORE) hilft, kleine Writes zu bündeln, ohne Latenzspitzen zu erzeugen. Pacing‑Grenzen pro Socket (z. B. via SO_MAX_PACING_RATE) harmonieren mit TSQ: Die Rate glättet zeitlich, das Byte‑Limit begrenzt räumlich. Wichtig: TCP_NODELAY deaktiviert Nagle und kann Interaktivität erhöhen, aber ohne TSQ steigt Burst‑Risiko; mit TSQ habe ich beides im Griff.
Praxisleitfaden: sinnvolle TSQ‑Werte
Den globalen Rahmen setze ich mit net.ipv4.tcp_limit_output_bytes (Sysctl). Übliche Defaults bewegen sich um 128–262 KB pro Flow. Für viele Web‑ und API‑Workloads wähle ich niedrigere Werte, damit interaktive Antworten flott bleiben. Für Backups oder Replikation schiebe ich das Limit moderat nach oben, solange die RTT stabil bleibt. Wer tiefer in die Queue‑Seite einsteigen will, findet Grundlagen zu Packet-Queues im Server, die bei der Einordnung helfen.
| Szenario | Link‑Rate | Richtwert tcp_limit_output_bytes | Ziel |
|---|---|---|---|
| API/HTTP sehr interaktiv | 100 Mbit – 1 Gbit | 64–128 KB | niedrige RTT, kurze Spikes |
| Mischlast Web + Downloads | 1–10 Gbit | 128–256 KB | Balance aus Durchsatz und Latenz |
| Replikation/Backups | 1–10 Gbit | 256–512 KB | konstanter Bulk‑Flow, akzeptable Latenz |
| WAN mit hoher RTT | 10–100 Mbit | 96–192 KB | kleinere Bursts, fairere Queues |
QDisc und Congestion Control im Zusammenspiel
TSQ arbeitet am Eingang der QDisc, während Algorithmen wie fq_codel den Stau auf der Leitung managen. Gemeinsam reduzieren sie Warteschlangen und halten die Verteilung fair. Mit TCP BBR profitiere ich zusätzlich, weil realistischere RTT‑Messungen zu besserem Pacing und cwnd‑Steuerung führen. CUBIC reagiert ebenfalls runder, wenn ich überhöhte Queueing‑Zeiten ausschalte. So wächst der Durchsatz organisch, während die Reaktionszeit im Griff bleibt.
Virtualisierung und Cloud‑Stacks
In VMs addieren sich mehrere Pufferstufen: Gast‑QDisc, virtio/vhost‑Queues, Host‑QDisc und die physische NIC. Ich halte TSQ im Gast aktiv und wähle dort ein konservatives Limit, damit keine dicken Bursts in den Host gelangen. Auf dem Hypervisor sorge ich mit fairen QDiscs, moderaten TX‑Ringen und sauberem IRQ‑Pinning für kurze Latenzketten. SR‑IOV kann Latenz drücken, verschiebt aber Verantwortung auf die Gäste: Ohne TSQ im Gast drohen lange VF‑Queues. In Containern greift TSQ pro NetNS wie gewohnt; per cgroup‑Pacing und CPU‑Limits verhindere ich, dass ein lauter Nachbar indirekt die Latenz hochzieht. Wichtig ist auch der Blick auf Coalescing und Offloads im virtio‑Pfad: Übertriebene Bündelung verlängert Acks, zu geringe mindert Effizienz – ich justiere entlang des Latenz‑Ziels, nicht dogmatisch.
WLAN und Embedded: Sonderfälle richtig behandeln
Auf WLAN‑Links zählt die Aggregation im MAC‑Layer. Lasse ich zu wenig Bytes im Sendeweg, kann der Treiber weniger Frames bündeln, was die Effizienz senkt. In solchen Setups erhöhe ich das Limit vorsichtig und prüfe den Aggregationsgrad. OpenWrt‑ und Embedded‑Plattformen profitieren zudem von schlanken Pfaden in Treibern und geringeren atomaren Operationen. Ich teste jede Anpassung unter realer Funklast, bevor ich ein Profil breit ausrolle.
Monitoring und Metriken, die wirklich zählen
Ich beobachte die RTT‑Verteilung pro Socket und schaue auf Ausreißer, nicht nur auf Mittelwerte. Mit ss, tc und Exportern lese ich Queue‑Längen, retransmits sowie pacing_rate aus. eBPF‑Programme liefern mir Events, wenn Sockets gedrosselt werden und wieder freikommen. Time‑to‑First‑Byte und 95./99. Perzentile zeigen, ob TSQ seine Wirkung entfaltet. Ohne Messwerte bleibt jede Optimierung ein Blindflug.
A/B‑ und Lasttests mit Aussagekraft
Ich messe TSQ‑Effekte reproduzierbar: Zuerst Baseline ohne Änderungen, dann isolierte Parameter‑Sweeps (z. B. 64, 96, 128, 192 KB). Für Mixed‑Workloads fahre ich parallele Streams (Bulk + viele kurze Requests) und vergleiche 95./99. Perzentile von Latenzen, nicht nur den Median. Unterbreche ich Testläufe klar (Warm‑Up, Messfenster, Cool‑Down), bleiben Artefakte erkennbar. Ich achte auf Konstanten: gleiche Payload‑Muster, identische Route/MTU, identische Server‑ und Client‑CPU‑Frequenzen. Auf WAN‑Strecken simuliere ich Delay/Jitter/Loss mit tc netem, um zu prüfen, ob TSQ Limits bei hoher BDP nicht zu früh deckeln. Erst wenn Perzentile enger werden und Retransmits/Loss stabil bleiben, übernehme ich Werte in die Produktion.
Hardware‑Tuning und Treiber‑Details
Ich prüfe TSO/GSO‑Einstellungen, Ring‑Puffer der NIC und IRQ‑Steuerung, damit TSQ sauber greift. Zu große TX‑Ringe verlängern die Warteschlange am Gerät; zu kleine mindern die Auslastung. Grobe Interrupt‑Bündelung verschleppt Acks, feine Bündelung erhöht CPU‑Last. Praxisnah passe ich die Moderation an und verweise für den Einstieg auf Interrupt Coalescing. Ziel bleibt eine verlässliche Latenz bei tragfähigem Durchsatz.
NUMA, RSS und CPU‑Affinität
Kurze Warteschlangen nützen wenig, wenn Pakete dauernd über NUMA‑Grenzen wandern. Ich binde RX/TX‑Queues per RSS/irqbalance an Kerne derselben NUMA‑Domain, auf der auch die App läuft. Mit XPS/RPS steuere ich, welche CPUs TX‑Work übernehmen, und vermeide so Cross‑Socket‑Hopper. Geringere Cache‑Misses und weniger Lock‑Contention helfen TSQ indirekt: Fertigstellungen kommen schneller zurück, der Socket wird früher „entdrosselt“ und Latenzspitzen bleiben aus. Bei sehr vielen Flows pro Host plane ich ausreichend Queues und vermeide, dass mehrere laute Flows auf demselben TX‑Ring kollidieren.
Schritt‑für‑Schritt: TSQ aktiv prüfen
Ich starte mit einem Blick auf Sysctl: sysctl net.ipv4.tcp_limit_output_bytes zeigt das aktuelle Limit. Danach befrage ich ss -tin für einzelne Sockets, achte auf send‑q und rtt, und vergleiche Lastphasen mit und ohne Limit‑Anpassung. Mit iperf3 erzeuge ich Hintergrundlast und messe parallel API‑Antwortzeiten, um Prioritäten sichtbar zu machen. tc -s qdisc liefert mir Paket‑ und Drop‑Zahlen der Ausgangsdisziplin. Bleiben 95./99. Perzentile eng und die CPU‑Last im Rahmen, passt die Wahl des Limits.
Häufige Irrtümer und Anti‑Patterns
- „Mehr Puffer = mehr Leistung“: Stimmt für Durchsatztests ohne Latenz‑Ziel, bricht aber bei interaktiven Diensten. TSQ ersetzt überdimensionierte Queues durch bedarfsgerechten Kredit pro Flow.
- „TSQ kostet Durchsatz“: Richtig eingestellt limitiert TSQ Bursts, nicht die mittlere Rate. Bei Bulk‑Workloads skaliere ich das Limit moderat hoch und messe die Perzentile statt nur den Peak‑MBit/s.
- „Pacing allein reicht“: Zeitliche Glättung ist wichtig, aber ohne Byte‑Deckel rutschen große GSO‑Frames dennoch in die QDisc. TSQ und Pacing ergänzen sich.
- „Ein Wert für alle“: Workloads, Links und NICs unterscheiden sich. Ich arbeite mit Bereichswerten und validiere pro Umgebung.
- „Nur TCP betroffen“: Fokus ist TCP, aber im System gibt es weitere Stellschrauben (z. B. für UDP‑Last). Ich verhindere, dass Parallel‑Protokolle die gleichen Queues unkontrolliert verstopfen.
Fazit: Latenz gezielt im Griff
TSQ verschiebt die Kontrolle von Treiber‑Queues hin zum Socket und reduziert damit Staus direkt an der Quelle. Ich limitiere die vorgepufferten Bytes je Flow und sorge so für schnelle Acks, niedrigere RTT und fair geteilte Queues. In Kombination mit fq_codel und zeitgemäßer Congestion Control bleibt die Reaktionszeit auch unter Last verlässlich. WLAN‑ und Embedded‑Sonderfälle behandle ich mit angepassten Limits und Tests unter Realbedingungen. Wer die Kennzahlen beobachtet und Limits schrittweise justiert, hält die Latenz konsequent niedrig, ohne unnötigen Durchsatz zu verlieren.


