18 Punkte von GN⁺ 2025-12-23 | 1 Kommentare | Auf WhatsApp teilen
  • Beim Debugging von Latenzproblemen in verteilten Systemen sollte man als Erstes die Einstellung von TCP_NODELAY prüfen
  • Der Nagle-Algorithmus wurde 1984 in RFC896 vorgeschlagen und entwickelt, um beim Senden kleiner Pakete den Overhead von TCP-Headern zu reduzieren
  • In Kombination mit dem Mechanismus Delayed ACK führt er jedoch dazu, dass die Datenübertragung bis zum Empfang eines ACK verzögert wird, was die Leistung latenzsensitiver Anwendungen verschlechtert
  • In modernen Rechenzentrumsumgebungen ist die RTT sehr kurz, und die meisten Systeme senden ohnehin bereits große Nachrichten, sodass der Nutzen des Nagle-Algorithmus fast vollständig verschwunden ist
  • Deshalb sollte in modernen verteilten Systemen TCP_NODELAY standardmäßig aktiviert sein; der Nagle-Algorithmus wird nicht mehr benötigt

Hintergrund des Nagle-Algorithmus

  • John Nagles RFC896 von 1984 wurde vorgeschlagen, um das Problem eines 4000-%-Overheads bei 1 Byte Nutzdaten gegenüber 40 Byte Header zu lösen, wie es bei kleinen Datenübertragungen wie Tastatureingaben auftrat
    • Das damalige Problem war, dass bei jeder einzelnen Tastatureingabe kleine Pakete gesendet wurden und dadurch die Netzwerkeffizienz sank
    • Die Lösung bestand darin, das Senden neuer Segmente zu unterbinden, solange frühere Daten noch nicht per ACK bestätigt wurden
  • Dieser Ansatz war in der damaligen Netzwerkumgebung effektiv, ist aber für moderne Systeme, in denen Latenz entscheidend ist, ungeeignet

Zusammenspiel von Nagle-Algorithmus und Delayed ACK

  • Delayed ACK (RFC813, RFC1122) bedeutet, dass der Empfänger ACKs nicht sofort sendet, sondern sie verzögert, bis Antwortdaten vorliegen oder ein Timer abläuft
  • Der Nagle-Algorithmus wartet auf ein ACK und stoppt die Übertragung, während Delayed ACK das ACK hinauszögert; dadurch entsteht ein Deadlock, bei dem beide Seiten aufeinander warten
  • John Nagle selbst bezeichnete diese Kombination als eine „schreckliche Kombination“ und wies darauf hin, dass beide Funktionen zwar unabhängig voneinander eingeführt wurden, gemeinsam aber Latenz verursachen

Probleme in modernen Umgebungen

  • Innerhalb eines Rechenzentrums liegt die RTT bei etwa 500 μs, selbst innerhalb derselben Region nur bei wenigen Millisekunden
  • In solchen Umgebungen führt bereits eine Verzögerung um eine RTT zu Leistungsverlusten
  • Zudem senden moderne verteilte Systeme durch TLS, Serialisierung und Protokoll-Overhead ohnehin bereits ausreichend große Nachrichten, sodass das Problem einzelner Byte-Pakete praktisch nicht mehr existiert
  • Die Optimierung kleiner Nachrichten wird heute auf Anwendungsebene vorgenommen

Warum TCP_NODELAY nötig ist

  • In latenzsensitiven verteilten Systemen wird empfohlen, TCP_NODELAY zu aktivieren und damit den Nagle-Algorithmus zu deaktivieren
    • Das ist weder „ineffizient“ noch eine „falsche Konfiguration“, sondern eine Entscheidung, die zu moderner Hardware und heutigen Traffic-Mustern passt
  • Der Autor argumentiert, dass TCP_NODELAY der Standard sein sollte
    • Zwar kann Code, der bei jedem write()-Aufruf sendet, dadurch langsamer werden, aber solcher Code sollte grundsätzlich überarbeitet werden

Weitere verwandte Optionen

  • Die Option TCP_QUICKACK reduziert ACK-Verzögerungen, ist aber wegen Portabilitätsproblemen und inkonsistentem Verhalten keine grundlegende Lösung
  • Das Kernproblem ist, dass der Kernel Daten länger zurückhält, als es die Anwendung beabsichtigt, obwohl sie bei einem write()-Aufruf sofort gesendet werden sollten

Fazit

  • Der Nagle-Algorithmus war eine hervorragende Erfindung, um in früheren Netzwerken die Effizienz zu steigern,
    ist jedoch in modernen Hochgeschwindigkeitsnetzwerken und verteilten Systemen eine überholte Funktion, die vielmehr Latenz verursacht
  • Daher wird die konsequente Aktivierung von TCP_NODELAY als grundlegendes Prinzip moderner Systemarchitektur vorgeschlagen

1 Kommentare

 
GN⁺ 2025-12-23
Hacker-News-Kommentare
  • Es wird der Hintergrund des Nagle-Algorithmus erklärt, der noch aus der Zeit des Multipoint-Networking stammt.
    Damals teilten sich mehrere Hosts einen Ethernet-Kanal, daher wurde CSMA/CD verwendet, um Kollisionen zu vermeiden.
    Heute ist Ethernet jedoch meist Point-to-Point aufgebaut, in einer Vollduplex-Umgebung, in der Senden und Empfangen gleichzeitig möglich sind.
    Deshalb wird CSMA nicht mehr benötigt, und es erscheint in den meisten Fällen sinnvoll, den Nagle-Algorithmus durch Setzen von TCP_NODELAY zu deaktivieren.
    • Ich frage mich, ob die Motivation rund um CSMA tatsächlich bei der Entwicklung des Nagle-Algorithmus eine Rolle spielte oder ob hier nur der historische Kontext erwähnt wurde.
    • Tatsächlich diente der Nagle-Algorithmus einfach dem Packet Coalescing.
      Dass er als Standard gesetzt wurde, halte ich für einen der großen Fehler der Netzwerkgeschichte.
    • Zur Einordnung: Ethernet verwendet CSMA/CD, WiFi dagegen CSMA/CA.
      Als wir um 2014 Datacenter-Switches austauschten, mussten wir einige Altgeräte behalten, weil kein 10-Mbit-Halbduplex mehr unterstützt wurde.
    • Wenn eine Anwendung sich nicht um Paketgrößen kümmert oder nicht latenzkritisch ist, ist Nagle durchaus sinnvoll.
      Er verhindert die Erzeugung zu vieler kleiner Pakete.
    • Hier scheint es eine Verwechslung der Netzwerkschichten zu geben.
      Nagle ist eine Optimierung auf TCP-Ebene und bündelt kleine Pakete für mehr Effizienz.
      CSMA betrifft die physische Ebene bzw. die Sicherungsschicht und ist von Nagle unabhängig.
  • Ich bin auf den Artikel gestoßen, als ich beim Entwickeln eines Spiels ein Netzwerk-Latenzproblem debuggt habe.
    Das in Go geschriebene Backend hatte standardmäßig bereits TCP_NODELAY gesetzt, also war das nicht die Ursache, aber die Diskussion über die Problemwahrnehmung rund um Nagle fand ich interessant.
    Es gab auch schon frühere Diskussionen dazu, siehe diesen Thread.
    • Empfehlenswert ist auch der gute Artikel von Julia Evans.
      Bei chatartiger Kommunikation wie im DICOM-Protokoll verbessert TCP_NODELAY=1 den Durchsatz deutlich.
    • Ich würde gern wissen, an welchem Spiel du arbeitest. Ich entwickle auch gern Spiele mit Ebitengine und Golang, daher interessiert mich das.
  • Nagle selbst sagte vor etwa zehn Jahren, das eigentliche Problem sei Delayed ACK.
    Siehe diesen Link.
    Ich glaube nicht, dass Delayed ACK bei heutigen Workloads noch große Vorteile bringt.
    In der modernen, HTTP-zentrierten Umgebung ist es meiner Ansicht nach besser, sowohl Nagle als auch Delayed ACK zu deaktivieren.
    • Auch im Originaltext wird das behandelt.
      Bei RTTs zwischen Datacentern im Bereich von einigen hundert Mikrosekunden kann schon die Verzögerung um eine einzige RTT eher schaden.
  • Im Polnischen bedeutet „nagle“ „plötzlich“, und ich war überrascht, wie gut das zum Namen des Algorithmus passt.
    • Das wirkt wie ein weiteres Beispiel für Nominative Determinism.
      Wikipedia-Link
    • Interessanterweise scheint die Bedeutung bei beiden Zuständen zu passen: Mit „NODELAY on“ wird plötzlich gesendet, mit „off“ eher gesammelt und dann auf einmal.
    • Tatsächlich basiert der Algorithmus auf RFC 896, das von John Nagle geschrieben wurde.
  • Ich finde es seltsam, dass der Nagle-Algorithmus als Kernel-Standardwert gesetzt wurde.
    Wann gesendet und wann gepuffert wird, sollte die Anwendung entscheiden.
  • Ich war überrascht, dass der Artikel MSG_MORE nicht erwähnt.
    Unter Linux ist das ein Hinweis an den Kernel, dass bald weitere Daten gesendet werden, was nützlich ist, wenn Header und Daten getrennt verschickt werden.
    Zusammen mit io_uring ist es noch effizienter.
    • Tatsächlich kann man auch mehrere Datenfragmente in einem einzigen Systemaufruf ohne Kopieren senden.
  • Ich denke, das Problem des Nagle-Algorithmus ist, dass der Socket-API eine Funktion für sofortiges Senden (flush) fehlt.
    Es wäre gut, wenn man nach einer Nachricht, die eine sofortige Antwort erfordert, den Puffer explizit leeren und senden könnte.
    Heutige TCP-Kanäle sind noch komplexer, weil sich synchrone und asynchrone Nachrichten mischen.
    Ich wünschte, Protokolle wie SCTP hätten sich stärker verbreitet.
    • Ich stimme zu, dass der Stream-API eine Flush-Funktion fehlt. Ich halte das für ein offensichtliches Designversäumnis.
    • Ich verstehe die UNIX-Philosophie, Netzwerk-I/O wie Dateien zu behandeln, aber wenn es von Anfang an eine nachrichtenorientierte API gegeben hätte, gäbe es dieses Problem wohl nicht.
      Auch bei Wrapping wie TLS ist es mühsam, Nachrichtengrenzen zu erkennen.
    • Wenn man bei jedem send MSG_MORE setzt und es nur beim letzten weglässt, könnte man indirekt einen Flush-Effekt erzielen.
    • Stream-APIs sind in vielerlei Hinsicht unbequem.
      Idealerweise sollte man ein Bit für „Pufferung erlaubt“ setzen können, um große Übertragungen aufzuteilen, und am Ende „sofort senden“ angeben können.
      TCP_CORK ist immerhin eine ähnliche Alternative, aber etwas grob.
      Bei Datei-I/O gibt es ein ähnliches Problem.
    • Ich frage mich, was TCP_CORK genau ist.
  • Eine frühere Diskussion von (2024) gab es unter diesem Link.
  • Das Thema wird in einer Episode des Oxide and Friends Podcast behandelt.
    Ziemlich interessant.
    • Oxide ist ein Unternehmen, das Server-OS und Hardware neu entwirft, daher passt der Ansatz, traditionelle Protokolle zu hinterfragen, gut zur Markenphilosophie.
  • Der Nagle-Algorithmus wirkt auf mich so, als wäre Policy in den Kernel verlagert worden, was sich seltsam anfühlt.
    Die Anwendung sollte das Verhältnis zwischen Latenz und Durchsatz selbst steuern können.
    • Ohne Delayed ACK ist es ein vernünftiger Algorithmus, und der Grund, warum er Teil des TCP-Stacks ist, liegt darin, dass das Problem auf dieser Ebene gelöst werden sollte.
      Auf Anwendungsebene wäre die Umsetzung aber ineffizient, weil man dazu unacked data kennen müsste.
    • Theoretisch stimmt das, aber praktisch kümmern sich die meisten User-Space-Programme nicht um die unteren Netzwerkschichten.
      Schon ein einfacher 20-ms-Flush-Timer wäre viel besser gewesen.
    • Tatsächlich wird TCP_NODELAY pro Socket gesetzt, daher würde ich es eher als eine Entscheidung im User Space sehen, die die Anwendung selbst trifft.
    • Da die Trade-offs eines Programms andere Programme beeinflussen können, sollte der Kernel meiner Meinung nach aus Systemgesamtsicht eine Vermittlerrolle übernehmen.