- Se trataba de una situación en la que había que manejar actualizaciones en tiempo real a gran escala en un backend basado en Node.js/TypeScript
- Se usaba PostgreSQL como backend, y cientos de nodos worker debían verificar continuamente si había nuevos trabajos, mientras que los agentes necesitaban recibir actualizaciones sobre el estado de ejecución y chat
- Empezó como una exploración de WebSocket, pero terminó en una solución “a la antigua” sorprendentemente efectiva
→ "HTTP Long Polling con Postgres"
El problema: actualizaciones en tiempo real a gran escala
- Actualizaciones de nodos worker :
- Había cientos de nodos worker ejecutando los SDK de Node.js/Golang/C#
- Como necesitaban enterarse de inmediato cuando había un nuevo trabajo disponible, hacía falta una estrategia de consultas que no derribara la base de datos Postgres
- Sincronización del estado de los agentes :
- Los agentes necesitaban actualizaciones en tiempo real sobre el estado de ejecución y chat, y había que transmitirlas de manera eficiente
Comparación entre long polling y WebSocket
- El short polling es como un tren que sale estrictamente según el horario: parte a intervalos fijos sin importar si hay pasajeros o no
- El long polling espera la respuesta del servidor y, cuando aparece un dato, lo devuelve de inmediato; si pasa cierto tiempo, responde por timeout
- Es decir, es como un tren que “espera y sale cuando aparecen pasajeros”. Solo sale vacío si no aparece nadie dentro de un tiempo determinado (TTL)
- Cuando hay datos (pasajeros), sale enseguida, y cuando no los hay, permite usar los recursos de forma eficiente
- WebSocket mantiene la conexión abierta de forma permanente para intercambiar datos en ambos sentidos
- En entornos corporativos, por infraestructura o problemas de firewall, long polling resultó más simple y más compatible que configurar WebSocket
Detalles de implementación de long polling
- La función
getJobStatusSync cumple un papel clave
- Recibe parámetros como
jobId, owner, ttl, etc., y consulta repetidamente el estado de un trabajo específico durante un período determinado
- La consulta repetida continúa hasta que se cumpla una de las siguientes condiciones
- El estado del trabajo pase a
success o failure
- Expire el
ttl (timeout)
- La base de datos se consulta cada 500 ms y, si el resultado aún no está definido, se espera antes de volver a consultar
- Si se supera el timeout, se lanza un error; si tiene éxito, se devuelve el resultado
Optimización de base de datos
- Se colocan índices adecuados en Postgres para minimizar el costo de las consultas
- Ejemplo:
CREATE INDEX idx_jobs_status ON jobs(id, cluster_id);
Ventajas de long polling
- Facilidad para mantener el monitoreo : se puede seguir aprovechando tal cual el stack existente de logging y monitoreo basado en HTTP
- Simplicidad en autenticación : se puede usar la autenticación HTTP existente sin implementar un mecanismo nuevo
- Compatibilidad con la infraestructura : no se requieren configuraciones especiales en firewalls o load balancers, y se trata como tráfico HTTP normal
- Simplicidad operativa : incluso al reiniciar el servidor no hace falta manejar el estado de conexión por separado, y es fácil de depurar
- Implementación sencilla del cliente : funciona con la estructura estándar de solicitud-respuesta HTTP agregando solo lógica de reintento
Comparación con ElectricSQL
- ElectricSQL es una solución para sincronizar datos de Postgres con el frontend
- Tiene una estructura que garantiza comportamiento en tiempo real usando HTTP en lugar de WebSocket
- De hecho, si para manejar actualizaciones en tiempo real no se necesita un control extremo ni una estructura de bajo nivel, se recomienda ElectricSQL
Por qué elegimos raw long polling
- El mecanismo de entrega de mensajes no es un simple detalle de implementación, sino un elemento central del producto
- No se puede depender de una librería de terceros para una funcionalidad crítica (por muy buena que sea)
- Requisitos
- Control del producto central : había que tener control total sobre el mecanismo de entrega de mensajes. No era un tema de infraestructura, sino del producto mismo
- Eliminar dependencias externas : minimizar dependencias externas para simplificar el self-hosting
- Control de bajo nivel : controlar directamente el mecanismo de polling y la gestión de conexiones
- Máximo nivel de control : poder ajustar con precisión detalles como implementar intervalos de polling dinámicos
- Simplicidad del código : diseñarlo de forma simple para que los usuarios puedan entender y modificar fácilmente la base de código
- En conclusión, al elegir una implementación simple de HTTP Long Polling se obtuvo control directo y simplicidad
Precauciones al implementar long polling
- Configuración de TTL : del lado del servidor se debe imponer obligatoriamente un TTL máximo y evitar que el TTL solicitado por el cliente lo supere
- Considerar timeouts de infraestructura : el TTL debe ser lo bastante corto como para quedar por debajo de la configuración de timeout de load balancers, servidores edge y proxies
- Intervalo de polling a la DB : introducir un delay de unos 500 ms para reducir la carga sobre la base de datos
- Estrategia de backoff (opcional) : aumentar gradualmente el intervalo de polling puede permitir un uso más eficiente de los recursos del sistema
Cuándo conviene considerar WebSocket
- WebSocket en sí no está mal; puede ser útil en otros aspectos
- Cuando hay que monitorear muchas conexiones con estado y enviar/recibir eventos complejos de manera permanente
- Cuando se cuenta con suficiente tiempo y recursos para resolver autenticación, infraestructura y observabilidad
- Existe la complejidad de tener que construir directamente aspectos como operación y logging, manejo de reconexión y mecanismos de autenticación
WebSockets: una mirada a otra opción
- Aunque long polling se ajustó a nuestras necesidades, WebSockets también valen totalmente la pena considerar
- WebSockets no son malos en sí; simplemente requieren mucha atención y gestión
- Principales desafíos de WebSockets y dirección de solución
- Visibilidad : como WebSockets se basan en estado, hace falta agregar logging y monitoreo para conexiones persistentes
- Autenticación : hace falta implementar un nuevo mecanismo de autenticación para conexiones WebSocket
- Infraestructura : para soportar WebSocket, hay que configurar adecuadamente infraestructura como load balancers y firewalls
- Gestión operativa : gestión de conexiones y reconexiones WebSocket, además de timeouts y manejo de errores
- Implementación del cliente : implementar una librería WebSocket del lado del cliente, incluyendo reconexión y manejo de estado
5 comentarios
Estoy usando para serving de modelos de ML la estructura de "short polling" que se menciona aquí, y he estado pensando mucho qué sería más eficiente. Por lo que averigüé por mi cuenta, se dice que, por el alto costo del manejo de reconexiones en WebSocket o SSE, el short polling suele ser en general una opción más segura, así que terminé eligiendo short polling... 😭
Parece que lo evitan porque el long polling se siente medio
hacky. En el navegador, probablemente seguiría apareciendo como si la solicitud nunca hubiera terminado. A veces hay sitios donde la carga nunca termina, y yo me quedo pensando: ¿será que no se cargó todo el contenido? La verdad no me gusta mucho.Y en la aplicación, al final igual habría alguna parte que quedaría colgada esperando la respuesta..., así que se ve un poco raro.
"El agente necesita recibir actualizaciones del estado de ejecución y del chat"
Al ver esto pensé de inmediato en SSE; tal como esperaba, en los comentarios de Hacker News también se lo menciona mucho.
Opiniones de Hacker News
El long polling tiene sus propios problemas
Es un gusto usar Phoenix y LiveView todos los días
Me pregunto si hay alguna ventaja técnica frente a usar Server-Sent Events (SSE)
Este artículo conecta "Websocket" y "Long-polling" como si fueran decisiones independientes
Una forma más sencilla de usar
setTimeouten Node.jsimport { setTimeout } from "node:timers/promises"; await setTimeout(500);Me gusta el long polling, es fácil de entender y desde la perspectiva del cliente funciona como una conexión muy lenta
Ni Server-Sent Events ni WebSockets reemplazan todos los casos de uso de long polling
Conviene usar la función de notificaciones asíncronas de Postgres
TRIGGERyNOTIFYNo estoy seguro de que long polling siga teniendo sentido con timeouts cortos y solicitudes cerradas de forma elegante
Es refrescante recordar una alternativa relativamente simple a WebSockets
Me gustaría probar WebSockets con Elixir, el framework Phoenix y LiveView.