18 points par GN⁺ 2025-12-23 | 1 commentaires | Partager sur WhatsApp
  • Lors du débogage des problèmes de latence dans les systèmes distribués, la première chose à vérifier est toujours le paramètre TCP_NODELAY
  • L’algorithme de Nagle est une approche proposée en 1984 dans la RFC896, conçue pour réduire la surcharge des en-têtes TCP lors de l’envoi de petits paquets
  • Cependant, lorsqu’il est combiné au mécanisme de delayed ACK, l’envoi des données est retardé jusqu’à la réception de l’ACK, ce qui dégrade les performances des applications sensibles à la latence
  • Dans les environnements modernes de datacenter, le RTT est très court et la plupart des systèmes envoient déjà de gros messages, si bien que les bénéfices de l’algorithme de Nagle ont presque disparu
  • Par conséquent, dans les systèmes distribués modernes, TCP_NODELAY devrait être activé par défaut, et l’algorithme de Nagle n’est plus vraiment nécessaire

Contexte de l’algorithme de Nagle

  • En 1984, la RFC896 de John Nagle a été proposée pour résoudre le problème d’une surcharge de 4000 % avec 40 octets d’en-tête pour 1 octet de données, typique des petits envois comme la saisie au clavier
    • À l’époque, le problème venait du fait que de petits paquets étaient envoyés à chaque caractère tapé, ce qui réduisait l’efficacité réseau
    • La solution consistait à interdire l’envoi d’un nouveau segment tant que les données précédentes n’avaient pas été acquittées
  • Cette approche était efficace dans les conditions réseau de l’époque, mais elle est inadaptée aux systèmes modernes où la latence est cruciale

Interaction entre l’algorithme de Nagle et le delayed ACK

  • Le delayed ACK (RFC813, RFC1122) est un mécanisme où le récepteur n’envoie pas immédiatement un ACK, mais le retarde jusqu’à ce qu’il ait des données de réponse ou qu’un timer expire
  • L’algorithme de Nagle suspend l’envoi en attendant un ACK, tandis que le delayed ACK retarde cet ACK, ce qui crée une situation de blocage où les deux côtés s’attendent mutuellement
  • John Nagle lui-même a qualifié cette combinaison de « combinaison horrible », soulignant que ces deux mécanismes ont été introduits indépendamment mais provoquent de la latence lorsqu’ils sont utilisés ensemble

Les problèmes dans l’environnement moderne

  • Dans un datacenter, le RTT est d’environ 500 μs, et même dans une même région on reste à quelques millisecondes seulement
  • Dans un tel contexte, retarder l’envoi d’un RTT complet entraîne une perte de performance
  • De plus, les systèmes distribués modernes envoient déjà des messages suffisamment volumineux en raison de TLS, de la sérialisation et de la surcharge des protocoles, si bien que le problème des paquets d’un seul octet a quasiment disparu
  • L’optimisation des petits messages est désormais traitée au niveau applicatif

Pourquoi TCP_NODELAY est nécessaire

  • Dans les systèmes distribués sensibles à la latence, il est recommandé d’activer TCP_NODELAY afin de désactiver l’algorithme de Nagle
    • Ce n’est ni « inefficace » ni un « mauvais réglage », mais un choix adapté aux caractéristiques du matériel moderne et du trafic actuel
  • L’auteur soutient que TCP_NODELAY devrait être la valeur par défaut
    • Certains codes qui « envoient à chaque appel à write() » peuvent devenir plus lents, mais ce type de code devrait être corrigé à la racine

Autres options liées

  • L’option TCP_QUICKACK réduit le délai des ACK, mais les problèmes de portabilité et le comportement incohérent en font une solution non fondamentale
  • Le vrai problème est que le noyau conserve les données plus longtemps que le moment voulu par l’application, alors qu’elles devraient être envoyées immédiatement lors de l’appel à write()

Conclusion

  • L’algorithme de Nagle a été une excellente invention pour améliorer l’efficacité réseau par le passé, mais
    dans les réseaux rapides modernes et les environnements de systèmes distribués, il est devenu une fonctionnalité d’un autre âge qui ajoute de la latence
  • Par conséquent, activer systématiquement TCP_NODELAY est présenté comme un principe de base de la conception des systèmes modernes

1 commentaires

 
GN⁺ 2025-12-23
Commentaires sur Hacker News
  • Explication du contexte dans lequel l’algorithme de Nagle a été conçu, à l’époque du réseautage multipoint
    À l’époque, plusieurs hôtes partageaient un même canal Ethernet, donc on utilisait le CSMA/CD pour éviter les collisions
    Mais aujourd’hui, la plupart des réseaux Ethernet sont en point à point, dans un environnement full duplex où l’envoi et la réception peuvent se faire simultanément
    Le CSMA n’est donc plus nécessaire, et il semble raisonnable dans la plupart des cas d’activer TCP_NODELAY pour désactiver l’algorithme de Nagle
    • Je me demande si cette motivation liée au CSMA a réellement joué un rôle dans la conception de l’algorithme de Nagle, ou si ce n’était qu’un simple rappel du contexte historique
    • En réalité, l’algorithme de Nagle servait simplement à regrouper les paquets (coalescing)
      Le fait qu’il ait été activé par défaut est à mon avis l’une des grandes erreurs de l’histoire du réseau
    • Pour référence, Ethernet utilise CSMA/CD, tandis que le Wi‑Fi utilise CSMA/CA
      Vers 2014, lors d’un remplacement de commutateurs en datacenter, j’ai dû conserver certains anciens équipements parce qu’ils ne prenaient pas en charge le 10 Mbit half duplex
    • Nagle reste tout à fait pertinent quand l’application ne se soucie pas de la taille des paquets ou n’est pas sensible à la latence
      Cela évite de générer trop de minuscules paquets
    • Il semble y avoir une confusion entre les couches réseau
      Nagle est une optimisation au niveau TCP, qui améliore l’efficacité en regroupant les petits paquets
      Le CSMA est un problème de couche physique / liaison de données, distinct de Nagle
  • Je suis tombé sur cet article en déboguant des problèmes de latence réseau pendant le développement d’un jeu
    Le backend écrit en Go active TCP_NODELAY par défaut, donc ce n’était pas la cause, mais le point sur la perception du problème posé par Nagle m’a semblé intéressant
    Il y avait aussi une discussion précédente ; voir ce fil
    • Je recommande aussi le bon article de Julia Evans
      Dans des protocoles de type bavard comme DICOM, configurer TCP_NODELAY=1 améliore nettement le débit
    • Je suis curieux de savoir sur quel jeu tu travailles. J’aime moi aussi développer des jeux avec Ebitengine et Golang, donc ça m’intéresse
  • D’après Nagle lui-même il y a une dizaine d’années, le vrai problème est le delayed ACK
    Voir ce lien
    Je pense que le delayed ACK n’apporte plus grand-chose pour les charges modernes
    Dans l’environnement actuel centré sur HTTP, il vaut mieux à mon avis désactiver à la fois Nagle et delayed ACK
    • L’article original en parle aussi
      Quand le RTT entre datacenters se compte en centaines de microsecondes, retarder ne serait-ce qu’un RTT peut au contraire être pénalisant
  • En polonais, “nagle” veut dire “soudainement”, et j’ai trouvé étonnant que cela colle si bien au nom de l’algorithme
    • On dirait un autre cas de nominative determinism
      Lien Wikipédia
    • Fait intéressant, avec “NODELAY on”, ça part soudainement, et avec “off”, tout part d’un coup ; on dirait que le mot convient aux deux réglages
    • En réalité, c’est un algorithme basé sur la RFC 896, écrite par John Nagle
  • Je trouve étrange que l’algorithme de Nagle soit le réglage par défaut du noyau
    Ce devrait être à l’application de décider quand envoyer et quand mettre en tampon
  • J’ai été surpris que l’article ne mentionne pas MSG_MORE
    Sous Linux, c’est un indice envoyé au noyau pour signaler que d’autres données vont suivre bientôt, utile quand on envoie séparément les en-têtes et les données
    Avec io_uring, c’est encore plus efficace
    • En fait, on peut aussi envoyer plusieurs fragments de données en un seul appel système, sans copie
  • Je pense que le vrai problème de l’algorithme de Nagle est l’absence d’une fonction de flush immédiat dans l’API des sockets
    Il serait utile de pouvoir vider le tampon juste après un message qui nécessite une réponse immédiate
    Les canaux TCP actuels mélangent souvent messages synchrones et asynchrones, ce qui complique encore les choses
    J’aimerais que des protocoles comme SCTP soient plus largement utilisés
    • Je suis d’accord sur l’absence de fonction flush dans l’API de flux. Ça ressemble clairement à un oubli de conception
    • Je comprends la philosophie UNIX qui consiste à traiter les E/S réseau comme des fichiers, mais si une API orientée message avait existé dès le départ, on n’aurait probablement pas eu ce problème
      Même avec un encapsulage comme TLS, retrouver les frontières des messages est pénible
    • On pourrait peut-être obtenir un effet de flush indirect en mettant MSG_MORE sur tous les send, puis en l’enlevant seulement sur le dernier
    • Les API de flux sont peu pratiques à bien des égards
      Dans l’idéal, on devrait pouvoir définir un bit “mise en tampon autorisée” pour découper un gros envoi, puis demander “envoi immédiat” à la fin
      TCP_CORK est une alternative à peu près comparable, mais assez grossière
      Les E/S fichier souffrent aussi d’un problème similaire
    • Je me demande ce qu’est TCP_CORK
  • En 2024, une discussion précédente avait eu lieu à ce lien
  • Le sujet est abordé dans cet épisode du podcast Oxide and Friends
    C’est assez intéressant
    • Oxide est une entreprise qui redessine l’OS serveur et le matériel, donc cette approche consistant à réexaminer les protocoles traditionnels correspond bien à sa philosophie de marque
  • L’algorithme de Nagle donne l’impression d’intégrer une politique dans le noyau, ce qui est un peu gênant
    L’application devrait pouvoir ajuster elle-même l’équilibre entre latence et débit
    • En l’absence de delayed ACK, c’est un algorithme raisonnable, et s’il fait partie de la pile TCP, c’est parce qu’on a voulu résoudre le problème à ce niveau
      Mais l’implémenter au niveau applicatif serait inefficace, car il faudrait connaître les données non acquittées
    • En théorie oui, mais en pratique la plupart des programmes en espace utilisateur ne se préoccupent pas des couches basses du réseau
      Un simple temporisateur de flush de 20 ms aurait déjà été bien meilleur
    • En réalité, TCP_NODELAY se configure socket par socket, donc on peut considérer que c’est déjà un choix de l’espace utilisateur fait par l’application elle-même
    • Les compromis d’un programme peuvent avoir un impact sur les autres, donc je pense que le noyau doit jouer un rôle d’arbitre à l’échelle de l’ensemble du système