18 pontos por GN⁺ 2025-12-23 | 1 comentários | Compartilhar no WhatsApp
  • Ao depurar problemas de latência em sistemas distribuídos, a primeira coisa a verificar é a configuração de TCP_NODELAY
  • O algoritmo de Nagle é uma abordagem proposta em 1984 no RFC896, projetada para reduzir a sobrecarga de cabeçalhos TCP ao transmitir pacotes pequenos
  • Porém, quando combinado com o mecanismo de ACK atrasado (delayed ACK), a transmissão de dados fica adiada até o recebimento do ACK, o que piora o desempenho de aplicações sensíveis à latência
  • Em ambientes modernos de datacenter, o RTT é muito curto, e a maioria dos sistemas já transmite mensagens grandes, então os benefícios do algoritmo de Nagle praticamente desapareceram
  • Portanto, em sistemas distribuídos modernos, o TCP_NODELAY deve ser ativado por padrão, e o algoritmo de Nagle não é mais necessário

Contexto do algoritmo de Nagle

  • O RFC896 de John Nagle, de 1984, foi proposto para resolver o problema da sobrecarga de 4000% de 1 byte de dados contra 40 bytes de cabeçalho em transmissões pequenas, como entrada de teclado
    • Na época, o problema era que pequenos pacotes eram enviados cada vez que o usuário digitava um caractere, reduzindo a eficiência da rede
    • A solução era restringir o envio de novos segmentos enquanto os dados anteriores ainda não tivessem sido confirmados com ACK
  • Essa abordagem era eficaz no ambiente de rede da época, mas é inadequada para sistemas modernos em que a latência é importante

Interação entre o algoritmo de Nagle e Delayed ACK

  • Delayed ACK (RFC813, RFC1122) é um método em que o lado receptor não envia o ACK imediatamente, mas o atrasa até que haja dados de resposta ou que um temporizador expire
  • O algoritmo de Nagle interrompe a transmissão enquanto espera pelo ACK, e o delayed ACK atrasa esse ACK, então surge um impasse em que os dois lados ficam esperando um pelo outro
  • O próprio John Nagle descreveu essa combinação como uma “combinação horrível”, observando que os dois recursos foram introduzidos de forma independente, mas causam latência quando usados juntos

Problemas no ambiente moderno

  • Dentro de datacenters, o RTT é de cerca de 500μs, e mesmo dentro da mesma região fica em um nível de poucos milissegundos, ou seja, muito curto
  • Nesse ambiente, atrasar a transmissão por um RTT inteiro leva a perda de desempenho
  • Além disso, sistemas distribuídos modernos já transmitem mensagens suficientemente grandes devido a TLS, serialização e overhead de protocolo, então o problema de pacotes de um único byte quase não existe mais
  • A otimização para mensagens pequenas agora é tratada na camada de aplicação

Por que o TCP_NODELAY é necessário

  • Em sistemas distribuídos sensíveis à latência, recomenda-se ativar o TCP_NODELAY para desativar o algoritmo de Nagle
    • Isso não é algo “ineficiente” nem uma “configuração incorreta”, e sim uma escolha alinhada ao hardware moderno e às características atuais do tráfego
  • O autor defende que TCP_NODELAY deveria ser o padrão
    • Parte do código que “envia a cada chamada de write()” pode ficar mais lento, mas esse tipo de código deveria ser corrigido na origem

Outras opções relacionadas

  • A opção TCP_QUICKACK reduz o atraso de ACK, mas não é uma solução fundamental devido a problemas de portabilidade e comportamento inconsistente
  • O problema central é que o kernel mantém os dados por mais tempo do que o momento pretendido pela aplicação, e eles deveriam ser enviados imediatamente na chamada de write()

Conclusão

  • O algoritmo de Nagle foi uma excelente invenção para melhorar a eficiência de rede no passado, mas
    em redes modernas de alta velocidade e em ambientes de sistemas distribuídos, ele se tornou uma funcionalidade ultrapassada que, na prática, introduz latência
  • Portanto, manter o TCP_NODELAY sempre ativado é apresentado como um princípio básico do projeto de sistemas modernos

1 comentários

 
GN⁺ 2025-12-23
Comentários do Hacker News
  • Explica o contexto em que o algoritmo de Nagle foi criado, na época das redes multiponto
    Naquele tempo, vários hosts compartilhavam um único canal Ethernet, então usava-se CSMA/CD para evitar colisões
    Mas hoje a maior parte da Ethernet é ponto a ponto, em um ambiente full duplex onde envio e recebimento podem ocorrer ao mesmo tempo
    Portanto, o CSMA não é mais necessário, e parece razoável na maioria dos casos definir TCP_NODELAY para desativar o algoritmo de Nagle
    • Fico curioso se essa motivação ligada ao CSMA realmente existiu no projeto do algoritmo de Nagle, ou se foi apenas uma menção ao contexto histórico da época
    • Na verdade, o algoritmo de Nagle tinha simplesmente o objetivo de fazer coalescing de pacotes
      Acho que tê-lo como padrão foi um dos grandes erros da história das redes
    • Como referência, Ethernet usa CSMA/CD e Wi‑Fi usa CSMA/CA
      Por volta de 2014, ao trocar switches de datacenter, tive que manter alguns equipamentos antigos porque eles não suportavam 10Mbit half duplex
    • Quando a aplicação não se preocupa com o tamanho dos pacotes ou não é sensível à latência, Nagle faz bastante sentido
      Ele evita a geração de pacotes pequenos demais
    • Parece haver uma confusão de camadas da rede
      Nagle é uma otimização da camada TCP, servindo para agrupar pacotes pequenos e aumentar a eficiência
      CSMA é um problema da camada física/de enlace, separado de Nagle
  • Encontrei este texto enquanto depurava problemas de latência de rede durante o desenvolvimento de um jogo
    O backend escrito em Go já tinha TCP_NODELAY ativado por padrão, então não era a causa, mas achei interessante a parte sobre como os problemas do Nagle são percebidos
    Também houve uma discussão anterior; dá para ver nesta thread
    • Também recomendo um bom texto da Julia Evans
      Em comunicações conversacionais como no protocolo DICOM, configurar TCP_NODELAY=1 melhora bastante o throughput
    • Fiquei curioso sobre qual jogo você está desenvolvendo. Eu também gosto de fazer jogos com Ebitengine e Golang, então me interessei
  • O próprio Nagle disse, há cerca de 10 anos, que o problema real é o delayed ACK
    Veja este link relacionado
    Acho que, nas cargas de trabalho atuais, delayed ACK não traz grandes vantagens
    No ambiente moderno centrado em HTTP, me parece melhor desligar tanto Nagle quanto delayed ACK
    • O texto original também trata disso
      Como o RTT entre datacenters fica na casa de centenas de microssegundos, atrasar até mesmo um RTT pode acabar sendo pior
  • Em polonês, “nagle” significa “de repente”, e fiquei surpreso com o quanto isso combina com o nome do algoritmo
    • Parece mais um caso de determinismo nominativo
      Link da wiki
    • Curiosamente, quando NODELAY está ligado, envia “de repente”, e quando está desligado, envia tudo de uma vez depois, então o significado da palavra parece servir para os dois modos
    • Na prática, é um algoritmo baseado na RFC 896, escrita por John Nagle
  • Acho estranho que o algoritmo de Nagle tenha sido definido como padrão do kernel
    A aplicação é que deveria decidir quando enviar e quando bufferizar
  • Achei surpreendente o texto não mencionar MSG_MORE
    No Linux, isso serve como uma dica ao kernel de que mais dados serão enviados em seguida, sendo útil quando se envia cabeçalho e dados separadamente
    Com io_uring, fica ainda mais eficiente
    • Na verdade, também dá para enviar vários fragmentos de dados sem cópia com uma única system call
  • Acho que o problema do algoritmo de Nagle é que a API de sockets não tem uma função de envio imediato (flush)
    Seria bom ter uma forma de esvaziar o buffer e enviar logo após uma mensagem que exige resposta imediata
    Hoje em dia, os canais TCP misturam mensagens síncronas e assíncronas, o que torna tudo mais complexo
    Gostaria que protocolos como SCTP fossem mais usados
    • Concordo que a API de stream não ter flush parece uma omissão clara de projeto
    • Entendo a filosofia UNIX de tratar I/O de rede como arquivo, mas, se uma API orientada a mensagens existisse desde o começo, esse problema talvez não existisse
      Em encapsulamentos como TLS, também é trabalhoso encontrar os limites das mensagens
    • Talvez dê para obter um efeito indireto de flush colocando MSG_MORE em todo send e removendo só no último
    • A API de stream é desconfortável em vários sentidos
      Idealmente, deveria ser possível marcar um bit de “bufferização permitida” para dividir uma transmissão grande e, no fim, indicar “envio imediato”
      TCP_CORK é a alternativa mais parecida, mas é meio grosseira
      I/O de arquivo sofre de problema parecido
    • Fiquei curioso sobre o que é TCP_CORK
  • Em 2024, a discussão anterior aconteceu neste link
  • O tema é tratado no episódio do podcast Oxide and Friends
    É um conteúdo bem interessante
    • A Oxide é uma empresa que redesenha sistema operacional de servidor e hardware, então a abordagem de reavaliar protocolos tradicionais combina bem com a filosofia da marca
  • O algoritmo de Nagle parece um caso de política colocada dentro do kernel, o que soa estranho
    A aplicação deveria poder ajustar diretamente o equilíbrio entre latência e throughput
    • Sem delayed ack, é um algoritmo razoável, e o motivo de existir como parte da stack TCP é que tentava resolver o problema naquela camada
      Mas, para implementá-lo no nível da aplicação, seria preciso conhecer os dados ainda não confirmados (unacked data), o que seria ineficiente
    • Em teoria faz sentido, mas, na prática, a maior parte do código em user space não presta atenção às camadas inferiores da rede
      Mesmo um simples timer de flush de 20ms já teria sido muito melhor
    • Na verdade, TCP_NODELAY é configurado por socket, então dá para ver isso mais como uma decisão de user space feita diretamente pela aplicação
    • Como o trade-off de um programa pode afetar outros programas, acho necessário que o kernel atue como árbitro do sistema como um todo