14 puntos por GN⁺ 2025-01-09 | 5 comentarios | Compartir por WhatsApp
  • 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

 
jhj0517 2025-01-10

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... 😭

 
bbulbum 2025-01-10

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.

 
joyfui 2025-01-09

"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.

 
GN⁺ 2025-01-09
Opiniones de Hacker News
  • El long polling tiene sus propios problemas

    • Second Life usa un canal de long polling por HTTPS entre el cliente y el servidor
    • En el lado del cliente se usa libcurl, y pueden ocurrir timeouts
    • Si el servidor intenta enviar un mensaje entre un timeout y la siguiente solicitud, puede ocurrir una condición de carrera y perderse el mensaje
    • Hay un servidor Apache al frente que bloquea solicitudes innecesarias, pero pueden ocurrir timeouts
    • A los middleboxes y servidores proxy puede no gustarles el long polling
    • Hay muchos elementos a los que no les gusta mantener conexiones HTTP abiertas por mucho tiempo
    • Como resultado, termina siendo un canal de mensajes no confiable, por lo que se necesitan números de secuencia para detectar duplicados y aun así pueden perderse mensajes
    • La sección del gráfico marcada como "loop" en el artículo original no menciona el manejo de timeouts
    • Si se usa long polling, hay que enviar datos cada pocos segundos para mantener viva la conexión
  • Es un gusto usar Phoenix y LiveView todos los días

    • Usan WebSockets, así que no hay que preocuparse por eso
  • Me pregunto si hay alguna ventaja técnica frente a usar Server-Sent Events (SSE)

    • Ambos dejan una conexión HTTP abierta y tienen la ventaja de seguir siendo HTTP simple
    • SSE parece más adecuado cuando se pueden transmitir actualizaciones o resultados en streaming
    • Un caso de uso apropiado podría ser monitorear todos los IDs de trabajos en nombre de un cliente específico
  • Este artículo conecta "Websocket" y "Long-polling" como si fueran decisiones independientes

    • Un servidor de long-polling puede manejar clientes websocket con un poco de trabajo adicional
    • Si la arquitectura existente está basada en websocket, para soportar clientes de long-polling se necesitan dos capas de servidor
  • Una forma más sencilla de usar setTimeout en Node.js

    • usar import { 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

    • Hay que manejar los reintentos y rastrear las conexiones canceladas del lado del cliente
    • El bucle que consulta datos repetidamente en el ejemplo de código se ve raro
  • Ni Server-Sent Events ni WebSockets reemplazan todos los casos de uso de long polling

    • Los límites de conexión de SSE aparecen con frecuencia como problema
    • WebSockets no son confiables en la mayoría de los entornos
    • El problema de detectar cambios en el backend y propagarlos al cliente adecuado sigue sin resolverse
  • Conviene usar la función de notificaciones asíncronas de Postgres

    • El servidor puede hacer LISTEN en un canal y, cuando cambian los datos, PG puede ejecutar TRIGGER y NOTIFY
  • No estoy seguro de que long polling siga teniendo sentido con timeouts cortos y solicitudes cerradas de forma elegante

    • Si no se usa HTTP/2 o QUIC, este truco todavía podría tener sentido
  • Es refrescante recordar una alternativa relativamente simple a WebSockets

    • Trabajé en una startup que eligió WebSockets, y era difícil hacer pruebas en el wifi de hoteles y restaurantes
 
luminance 2025-01-10

Me gustaría probar WebSockets con Elixir, el framework Phoenix y LiveView.