Cómo escalar telemetría por WebSocket cuando un solo proceso no basta
Un servidor de telemetría en tiempo real aguanta en un pod y se rompe al repartir conexiones entre instancias. Un ingeniero cuenta qué piezas hacen falta para evitarlo.

Un servidor WebSocket que sirve telemetría a un puñado de clientes se monta en un fin de semana. Servir a miles repartidos entre varias instancias es trabajo de sistemas. Es lo que cuenta Yaseen Khatib, ingeniero full-stack, a partir de streamerOS: el boceto de «un proceso que guarda todos los sockets» navegaba en un pod y se estrelló en cuanto se escaló hacia fuera.
El fallo de fondo es que cada instancia mantiene su propia tabla de conexiones. Un evento que nace en la instancia A no llega a los suscriptores que cayeron en la B, porque el broadcast en memoria muere en la frontera del proceso. La salida es un backplane compartido: pub/sub de Redis, en su caso, con el adaptador de Socket.IO, de modo que cada instancia reciba todo lo que se emite en cualquier otra.
import { createAdapter } from "@socket.io/redis-adapter";
io.adapter(createAdapter(pubClient, subClient));
metrics.on("tick", (m) => io.to("telemetry").emit("metric", m));
Salas en lugar de broadcast
El segundo problema es de alcance. Empujar cada métrica a cada cliente multiplica el trabajo por el número de conexiones abiertas; agrupar las suscripciones en salas —por panel, por dispositivo, por inquilino— deja el coste en función de los clientes que de verdad miran ese flujo, no de todos los conectados. En streamerOS, pasar de un canal global a feeds por sala cortó los picos de NIC y redujo interrupciones de CPU y trabajo de render cuando los operadores saltaban entre vistas. Las altas y bajas de sala las gestiona el orquestador, no los componentes de interfaz.
Coalescer antes de emitir
Las fuentes de alta frecuencia emiten mucho más rápido de lo que ningún cliente puede pintar ni ningún socket debería transportar. La receta que propone es muestrear o aplicar debounce en el servidor antes de difundir: el presupuesto a 60 fps ronda los 16 ms por fotograma, y un flujo de 200 Hz deja sin aire al hilo de la interfaz. Conviene además quedarse con el último valor por clave y preagregar contadores en el servidor. La contrapresión se resuelve en el origen, no descubriéndola en el cliente.
Todo esto encaja en el patrón que el autor llama Trinity: la orquestación asume el reparto entre instancias y la interfaz se queda tonta, solo pinta. En ese reparto también usa un adaptador de serialización; en IntegrateX, quitar los metadatos de interfaz de React Flow antes de persistir recortó los payloads de grafo un 94%, y el mismo hábito mantiene ajustados los fotogramas de telemetría. Su resumen: «Escalar tiempo real no va de un socket más rápido, sino de que cada evento recorra exactamente la distancia que necesita, y ni un paso más».
El interés para quien opera sistemas está en que el problema no aparece hasta que hay más de un proceso detrás del balanceador, y para entonces los gráficos ya se han congelado. La pieza deja fuera, eso sí, los números concretos de carga: no hay cifras de conexiones simultáneas, latencias medidas ni coste del backplane, solo la experiencia del autor. Quien tenga que dimensionar esto tendrá que sacar sus propias mediciones. Para los fundamentos de un solo servidor está la primera parte sobre telemetría en tiempo real.


