Paneles de control en tiempo real: debida diligencia de ingeniería
Por qué el botón 'Actualizar' está obsoleto. Una guía técnica sobre eventos enviados por servidor (SSE), WebSockets y el problema C10K.
Es viernes negro. Se utilizan en la “Sala de Guerra”. El director general pregunta: “¿Cuántos ingresos obtuvimos en los últimos 10 minutos?” Haces clic en actualizar. The database query takes 15 seconds. La página se carga. “50.000€.” Cuando lo dices, el número es incorrecto.
En el comercio moderno, los datos estáticos están muertos. Movimientos de inventario. Los precios cambian. Picos de tráfico. Los paneles en tiempo real no son sólo un juguete visual “agradable de tener”; son herramientas operativas de misión crítica. Pero diseñarlos correctamente (para miles de usuarios simultáneos, sin destruir la base de datos) es un desafío para los sistemas distribuidos.
En Maison Code Paris, construimos interfaces de “Mission Control” que se sienten vivas. Así es como lo hacemos sin derretir los servidores.
Por qué Maison Code habla de esto
En Maison Code Paris, actuamos como la conciencia arquitectónica de nuestros clientes. A menudo heredamos stacks “modernos” construidos sin una comprensión fundamental de la escala.
Discutimos este tema porque representa un punto de inflexión crítico en la madurez de la ingeniería. Implementarlo correctamente diferencia un MVP frágil de una plataforma resistente de nivel empresarial.
La batalla del protocolo: encuestas, SSE y WebSockets
La primera decisión es el transporte eficiente de datos.
1. Encuestas (el enfoque ingenuo)
setInterval(() => fetch('/api/sales'), 5000);
- Ventajas: Sencillo. Funciona en todos los servidores (PHP, Node, Python).
- Desventajas:
- Latencia: retraso promedio de 2,5 s.
- Residuos: el 90 % de las solicitudes devuelven “Sin cambios”. Estás golpeando tu base de datos por nada.
- Batería: Mantiene activa la radio móvil.
2. WebSockets (el tubo bidireccional)
Una conexión TCP persistente.
- Pros: Extremadamente rápido. Bidireccional (El cliente puede enviar “Estoy escribiendo”).
- Desventajas:
- Firewalls: los servidores proxy corporativos a menudo bloquean el tráfico que no es HTTP.
- Contundencia: escalar es difícil. Necesita sesiones fijas o un backend de Redis.
- Exagerado: un panel suele ser de solo lectura.
3. Eventos enviados por el servidor (SSE) (el estándar de oro)
Conexión HTTP estándar, pero el servidor la mantiene abierta y transmite texto. Tipo de contenido: texto/flujo de eventos.
- Ventajas:
- Compatible con HTTP: Funciona a través de Proxies/equilibradores de carga estándar.
- Reconexión automática: el navegador maneja los reintentos automáticamente.
- Ligero: Solo mensajes de texto simples.
- Contras: Unidireccional únicamente (Servidor -> Cliente).
Para los paneles, SSE es el ganador técnico.
Arquitectura: el autobús de eventos
No se puede transmitir simplemente desde la base de datos al cliente.
Si tiene 5000 clientes y ejecuta una SELECCIONAR suma (total) DE pedidos cada vez que cambia una fila, PostgreSQL morirá.
Necesita una Arquitectura de bus de eventos.
- Ingesta: Orden Crear webhook -> API.
- Publicar: API -> Redis Pub/Sub (
canal: ventas). - Fan-Out: el servidor SSE se suscribe a Redis.
- Transmisión: cuando Redis emite un mensaje, el servidor SSE recorre los clientes conectados y escribe en sus transmisiones.
Código: La secuencia Remix / Node.js
En un marco moderno como Remix o Next.js App Router, la transmisión es nativa.
// aplicación/rutas/api.stream.ts en Remix
importar { eventStream } desde "remix-utils/sse/server";
importar { redis } desde "~/lib/redis";
exportar cargador de funciones asíncronas ({solicitud}: LoaderFunctionArgs) {
return eventStream(solicitud.señal, configuración de función(enviar) {
canal const = "actualizaciones del panel";
oyente constante = (mensaje: cadena) => {
enviar({ evento: "actualizar", datos: mensaje });
};
suscriptor constante = redis.duplicate();
suscriptor.suscribir(canal);
suscriptor.on("mensaje", (chan, msj) => {
if (chan === canal) oyente(msg);
});
función de retorno limpieza() {
suscriptor.cancelar suscripción (canal);
suscriptor.quit();
};
});
}
Los desafíos de la conectividad
1. El problema del C10K (escalado)
El Node.js estándar puede manejar ~10.000 conexiones simultáneas. Servidores como Apache/PHP luchan con cientos. Si necesita 100.000 usuarios conectados (por ejemplo, una cuenta regresiva masiva de ventas flash), no puede utilizar un único servidor estándar. Solución:
- Escalado horizontal: 10 servidores detrás de un balanceador de carga.
- Redis Backplane: todos los servidores se suscriben a Redis. Entonces, si el servidor A publica, el servidor B lo recibe y lo envía al usuario B.
- Servicios administrados: para una escala extrema, descargue a Pusher o Supabase Realtime. Gestionan la capa de encaje.
2. La manada atronadora (Reconexión)
Implementa una nueva versión del panel. El servidor se reinicia. 5.000 clientes se desconectan. 5.000 clientes intentan volver a conectarse inmediatamente exactamente a las 10:00:01. Su CPU aumenta al 100%. El servidor falla. Los clientes vuelven a intentarlo. Bucle de muerte. Solución: Temblor. El cliente debe esperar “random(0, 5000)” milisegundos antes de volver a conectarse. La implementación SSE nativa del navegador tiene un retroceso básico, pero la lógica WebSocket personalizada a menudo pasa por alto esto.
3. Límites del descriptor de archivos del sistema operativo
Un servidor Linux tiene un límite predeterminado de 1024 archivos abiertos (conexiones).
Si desea 10.000 usuarios, DEBE editar /etc/security/limits.conf.
* archivo suave 100000
* archivo duro 100000
Si olvida esto, su servidor Node.js fallará con errores EMFILE en el usuario n.° 1025.
Reconciliación del Estado: la brecha
El usuario se encuentra en un túnel de tren. 10:00:00 - Usuario en línea. Total: 100€. 10:00:05 - Túnel. Usuario sin conexión. 10:00:06 - El servidor envía el evento “Nuevo pedido +50 €”. El total es ahora de 150€. 10:00:10 - Finaliza el túnel. El usuario se vuelve a conectar.
El usuario cree que el total es 100€. Se perdieron el evento. El Estado está a la deriva.
Solución: ID del último evento.
- Cada evento tiene un ID (Número de secuencia o Marca de tiempo).
- El navegador almacena la última identificación que vio.
- Al volver a conectarse, el navegador envía “Último ID de evento: 105”.
- El servidor comprueba su búfer. “Ah, el cliente está atrasado. Aquí están los eventos 106, 107, 108”.
O una estrategia más sencilla: StaleCheck.
En cada reconexión, la interfaz activa un fetch('/api/current-total') HTTP estándar para resincronizar la línea base.
UX: Visualizando el pulso
Los datos en tiempo real crean “ruido visual”. Si vende 10 artículos por segundo y actualiza el número 10 veces por segundo, el usuario no podrá leerlo. Es sólo una mancha borrosa.
Actualizaciones de aceleración:
Limite los repintados de la interfaz de usuario a una vez cada 500 ms. Acumular los cambios en un buffer.
Búfer = +10, +5, +20.
Vaciar -> Actualizar UI +35.
Animación:
Utilice framer-motion o react-spring para animar el número.
100 -> 135. El usuario ve cómo los números aumentan (como una bomba de gasolina).
Esto transmite “Velocidad” y “Actividad” mucho mejor que un salto estático.
función NúmeroAnimado({ valor }) {
const { número } = useSpring({
de: { número: 0 },
número: valor,
retraso: 200,
configuración: {masa: 1, tensión: 20, fricción: 10},
});
return <animated.span>{number.to((n) => n.toFixed(0))}</animated.span>;
}
10. Seguridad: control de acceso basado en roles (RBAC) en transmisiones
La autenticación HTTP estándar valida la solicitud. Pero una vez que el socket está abierto, ¿cómo se puede evitar que un “Administrador de tienda” escuche los eventos de ventas del “Administrador global”? Alcance del canal.
- El usuario se conecta con JWT.
- El servidor decodifica JWT ->
rol: manager,store_id: 12. - El cliente solicita suscripción a
sales-global. - Lógica del servidor:
if (rol! == 'admin') throw Forbidden. - Lógica del servidor:
Solo permitir redis.subscribe('sales-store-12').
11. Granularidad de datos: tick vs agregado
¿Deberías emitir un evento por cada pedido de 5€? Si tiene 100 pedidos por segundo, su tablero se verá como una luz estroboscópica. Estrategia de agregación:
- Raw Stream: Uso interno/Depuración.
- Transmisión acelerada (UI de usuario): agrega cada 1 segundo.
{eventos: 50, total_agregado: 5000 }.- Enviar un mensaje.
- Transmisión de instantáneas: cada 1 minuto. Sincronización completa. Equilibre la “sensación en tiempo real” con los “límites cognitivos humanos”.
13. La decisión “Constructor versus Compra” (Grafana)
A veces, no es necesario crear un panel de React personalizado. Si la audiencia es “Ingenieros” u “Operaciones internas”, simplemente use Grafana. Conéctelo a su réplica de lectura de PostgreSQL. Establezca la frecuencia de actualización en 5 segundos. Hecho. Cuesta 0€. Solo creamos paneles de control de React personalizados cuando la audiencia son “Clientes externos” o “Ejecutivos de nivel C que necesitan una UX específica”.
14. Umbrales de alerta (señales visuales)
Un número que cambia de 100 a 120 son datos.
Un número que cambia de 100 a 120 (fondo rojo) es información.
Construimos una “lógica de umbral” en los componentes del frontend.
si (valor <objetivo) color = 'rojo'.
Esto ayuda a los usuarios a procesar cognitivamente la transmisión en tiempo real.
“No necesito leer los números; sólo necesito buscar el rojo”.
15. Conclusión
Crear un panel en tiempo real consiste en gestionar la ilusión de inmediatez. Requiere un baile sincronizado entre la base de datos, el bus de eventos, el servidor web y la interfaz de usuario del cliente. Si se hace mal, es un cuello de botella en el rendimiento. Bien hecho, actúa como el corazón de la organización.
¿Sus datos están obsoletos?
Construimos interfaces comerciales de alta frecuencia para el comercio minorista. Contrate a nuestros arquitectos.