- Al depurar problemas de latencia en sistemas distribuidos, lo primero que hay que revisar es la configuración de TCP_NODELAY
- El algoritmo de Nagle fue propuesto en 1984 en el RFC896 y fue diseñado para reducir la sobrecarga de los encabezados TCP al enviar paquetes pequeños
- Sin embargo, cuando se combina con el mecanismo de delayed ACK, la transmisión de datos se retrasa hasta recibir el ACK, lo que empeora el rendimiento de las aplicaciones sensibles a la latencia
- En los centros de datos modernos, el RTT es muy corto y la mayoría de los sistemas ya envían mensajes grandes, por lo que las ventajas del algoritmo de Nagle prácticamente han desaparecido
- Por eso, en los sistemas distribuidos modernos, TCP_NODELAY debería estar activado por defecto, y el algoritmo de Nagle ya no es necesario
Antecedentes del algoritmo de Nagle
- El RFC896 de John Nagle de 1984 se propuso para resolver el problema de una sobrecarga del 4000%: 40 bytes de encabezado para 1 byte de datos, como ocurría con transmisiones pequeñas tipo entrada por teclado
- En ese momento, el problema era que se enviaban paquetes pequeños cada vez que el usuario escribía un carácter, lo que reducía la eficiencia de la red
- La solución fue restringir el envío de nuevos segmentos mientras los datos anteriores no hubieran sido confirmados con ACK
- Ese enfoque era efectivo en las condiciones de red de la época, pero no encaja bien con los sistemas modernos donde la latencia es crítica
Interacción entre el algoritmo de Nagle y Delayed ACK
- Delayed ACK (RFC813, RFC1122) es un mecanismo en el que el receptor no envía el ACK de inmediato, sino que lo retrasa hasta que haya datos de respuesta o expire un temporizador
- El algoritmo de Nagle detiene el envío mientras espera el ACK, y delayed ACK retrasa ese ACK, por lo que ambos lados quedan esperando entre sí en un estado de bloqueo
- El propio John Nagle describió esta combinación como una “combinación terrible”, señalando que ambas funciones se introdujeron de forma independiente, pero que juntas provocan latencia
Problemas en el entorno moderno
- Dentro de un centro de datos, el RTT ronda los 500μs, e incluso dentro de la misma región suele estar en el orden de unos pocos milisegundos
- En este entorno, retrasar una transmisión por un RTT completo se traduce en pérdida de rendimiento
- Además, los sistemas distribuidos modernos ya envían mensajes suficientemente grandes debido a TLS, serialización y sobrecarga del protocolo, por lo que el problema de los paquetes de un solo byte casi no existe
- La optimización de mensajes pequeños ahora se resuelve en la capa de aplicación
Por qué hace falta TCP_NODELAY
- En los sistemas distribuidos sensibles a la latencia, se recomienda activar TCP_NODELAY para desactivar el algoritmo de Nagle
- No se trata de algo “ineficiente” ni de una “mala configuración”, sino de una decisión acorde con el hardware moderno y las características actuales del tráfico
- El autor sostiene que TCP_NODELAY debería ser el valor por defecto
- Parte del código que “envía en cada llamada a write()” puede volverse más lento, pero ese tipo de código debería corregirse de raíz
Otras opciones relacionadas
- La opción TCP_QUICKACK reduce el retraso del ACK, pero no es una solución de fondo debido a problemas de portabilidad y a su comportamiento inconsistente
- El problema central es que el kernel retiene los datos más tiempo del que la aplicación pretende, y los datos deberían enviarse de inmediato cuando se llama a
write()
Conclusión
- El algoritmo de Nagle fue una gran invención para mejorar la eficiencia de red en el pasado, pero
en el entorno moderno de redes de alta velocidad y sistemas distribuidos se ha convertido en una función anticuada que más bien introduce latencia
- Por lo tanto, mantener TCP_NODELAY siempre activado se plantea como un principio básico del diseño de sistemas modernos
1 comentarios
Comentarios de Hacker News
En ese entonces varios hosts compartían un mismo canal Ethernet, así que se usaba CSMA/CD para evitar colisiones
Pero hoy en día la mayoría de Ethernet es punto a punto, en un entorno full dúplex donde se puede enviar y recibir al mismo tiempo
Por eso CSMA ya no es necesario, y considera que en la mayoría de los casos tiene sentido configurar TCP_NODELAY para desactivar el algoritmo de Nagle
Cree que haberlo dejado activado por defecto fue uno de los grandes errores en la historia del networking
Cuenta que alrededor de 2014, al reemplazar switches de datacenter, tuvo que conservar algunos equipos antiguos porque no soportaban 10Mbit half dúplex
Ayuda a evitar la generación de paquetes demasiado pequeños
Nagle es una optimización en la capa TCP, cuya función es agrupar paquetes pequeños para mejorar la eficiencia
CSMA es un problema de la capa física/enlace de datos, y es independiente de Nagle
El backend escrito en Go ya tenía TCP_NODELAY activado por defecto, así que no era la causa, pero le pareció interesante la parte sobre cómo se percibe el problema de Nagle
También hubo una discusión anterior; se puede consultar este hilo
En comunicaciones tipo chat como el protocolo DICOM, configurar TCP_NODELAY=1 mejora mucho el rendimiento
Se puede ver en este enlace relacionado
Piensa que en las cargas modernas delayed ACK no aporta grandes beneficios
En el entorno moderno centrado en HTTP, considera mejor desactivar tanto Nagle como delayed ACK
Como el RTT entre datacenters es de cientos de microsegundos, retrasarlo siquiera un RTT puede ser contraproducente
Enlace de Wikipedia
La aplicación debería decidir cuándo enviar y cuándo hacer buffering
En Linux sirve como pista para indicarle al kernel que pronto se enviarán más datos, y es útil cuando se mandan encabezados y datos por separado
Con io_uring resulta todavía más eficiente
flush)Sería bueno poder vaciar el búfer después de un mensaje que requiere respuesta inmediata
Hoy en día los canales TCP mezclan mensajes síncronos y asíncronos, así que es más complejo
Ojalá protocolos como SCTP se hubieran adoptado más
Incluso con wrappers como TLS, encontrar los límites de los mensajes resulta engorroso
sendy quitarlo solo al final produciría indirectamente un efecto de flushIdealmente debería poder activarse un bit de “buffering permitido” para dividir una transmisión grande, y al final indicar “enviar inmediatamente”
TCP_CORK es una alternativa más o menos parecida, pero algo tosca
La E/S de archivos también sufre problemas similares
Es bastante interesante
La aplicación debería poder ajustar directamente el equilibrio entre latencia y rendimiento
Pero implementarlo a nivel de aplicación sería ineficiente porque habría que conocer los unacked data
Incluso un simple temporizador de flush de 20ms habría sido mucho mejor