9 MIN DE LECTURA

Arquitectura de Dashboards en Tiempo Real con SignalR y .NET 8: Paso a Paso

Compartir este artículo
Arquitectura de Dashboards en Tiempo Real con SignalR y .NET 8: Paso a Paso

La mayoría de tutoriales de SignalR te enseñan a construir un chat. Está bien para aprender lo básico, pero no te dice nada sobre qué pasa cuando necesitás empujar 100K+ transacciones por día a un dashboard en vivo sin derretir tu servidor.


1. El Problema: Por Qué los Dashboards en Tiempo Real "Naive" Fallan

He construido dashboards en tiempo real en producción para sistemas financieros — monitoreo de transacciones, dashboards de compliance, paneles de reporting en vivo. Los patrones que uso son fundamentalmente diferentes de lo que encontrás en un tutorial típico de Medium.

El enfoque "obvio" para un dashboard en tiempo real se ve así:

  • Llega una transacción → guardar en base de datos
  • Inmediatamente broadcast a todos los clientes conectados vía SignalR
  • Cada cliente re-renderiza el gráfico
A 10 transacciones por segundo, esto significa:
  • 10 broadcasts de SignalR/segundo × N clientes conectados
  • 10 re-renders de Chart.js/segundo por cliente
  • 10 queries a la base de datos/segundo si estás computando métricas en vivo
¿A 100 TPS? Son 100 serializaciones, 100 × N escrituras de red, y 100 renders de gráficos por segundo. El navegador se congela, el servidor se ahoga, y tu dashboard se convierte en una presentación de slides. La solución no es "usar un servidor más potente." Es arquitectura.

2. Vista General de la Arquitectura

El sistema corre como una sola aplicación Blazor Server con cuatro background services que forman un pipeline de procesamiento:

Cada componente tiene una responsabilidad única y se comunica a través de canales bien definidos. Voy a desglosar los tres patrones que hacen que esto funcione.


3. Patrón 1: Broadcasting por Lotes (Batched Broadcasting)

Esta es la decisión arquitectónica más importante. En lugar de hacer broadcast por transacción, acumulamos transacciones en un buffer y las enviamos como lote cada 500ms.

¿Por qué 500ms?

La percepción humana en dashboards hace plateau alrededor de 200–500ms. Por debajo de 200ms, los usuarios no distinguen actualizaciones individuales. Por encima de 1000ms, se siente lento. A 500ms obtenemos:

  • Máximo 2 broadcasts/segundo independientemente del volumen de transacciones
  • Una sola serialización por lote en vez de por transacción
  • El cliente actualiza el gráfico una vez por lote, no por transacción
A 100 TPS, esto reduce el overhead de SignalR en ~98% comparado con broadcasting por transacción.

4. Patrón 2: Métricas Pre-Computadas

El dashboard muestra métricas como volumen total, tasa de éxito, TPS, y transacciones flaggeadas. Computar estas desde datos crudos en cada refresh es una sentencia de muerte a escala.

En su lugar, un background service
pre-computa todo en un intervalo fijo y cachea los resultados:
El broadcaster lee del cache — cero queries a la base de datos por refresh de cliente. Ya sea que tengas 5 clientes conectados o 500, la carga en la base de datos es la misma: una query de agregación cada 500ms.

5. Patrón 3: Channel<T> como Bus de Mensajes Interno

El
genera transacciones. El
las persiste. El
las envía a los clientes. Estos tres servicios necesitan comunicarse sin acoplamiento fuerte.
provee exactamente esto — una cola productor-consumidor async-friendly, con límite de capacidad y backpressure incluido:

¿Por qué Channel<T> y no ConcurrentQueue?

  • Backpressure:
    bloquea al productor cuando el buffer está lleno — sin crecimiento ilimitado de memoria
  • Enumeración async:
    en vez de loops de polling
  • Cancelación: Soporte nativo de
  • Múltiples consumidores: Tanto el
    como el
    pueden leer del mismo channel

6. El Hub de SignalR: Mantenelo Delgado

El hub en sí no hace casi nada — por diseño. Toda la lógica vive en los background services:

La interfaz tipada (
) asegura seguridad en compile-time — sin magic strings como
.

7. Estrategia de Base de Datos: Escrituras por Lotes

Llamadas individuales de
a 100 TPS crean overhead innecesario. En su lugar, el
acumula transacciones y las escribe en bulk:
El flush se dispara cuando: se acumulan 100 transacciones O pasa 1 segundo — lo que ocurra primero. Esto da ~10x mejora en throughput sobre inserts individuales.

8. Cliente: Blazor + Chart.js

El componente Blazor se conecta al hub y actualiza Chart.js a través de JavaScript interop:

Decisiones clave en el cliente

  • Mantener solo 100 transacciones recientes en memoria — las más viejas se descartan
  • Ventana deslizante de Chart.js de 5 minutos (máximo 300 data points)
  • se llama una vez por lote, no por transacción
  • Reconexión automática con backoff exponencial

9. Escalando: Qué Cambia a 500K, 1M+

La arquitectura en este repo maneja ~100K transacciones diarias en una sola instancia. Esto es lo que cambiarías al escalar:

EscalaCambioPor qué
500K/díaParticionamiento por fecha en MySQLPerformance de queries en tablas grandes
500K/díaRedis para cache de métricasCompartir cache entre múltiples instancias
1M/díaRedis Backplane para SignalREscalado horizontal de SignalR
1M/díaRéplica de lectura MySQLSeparar carga de lectura/escritura
10M/díaKafka reemplaza Channel<T>Comunicación cross-service
10M/díaAzure SignalR ServiceWebSockets managed, auto-scaling
10M/díaClickHouse/TimescaleDBDiseñado para agregación time-series
Los patrones se mantienen iguales — batching, pre-computación, productor-consumidor. Solo la infraestructura cambia.

10. Objetivos de Performance

MétricaObjetivo
Latencia de refresh del dashboard< 100ms
Broadcast SignalR (hub → cliente)< 50ms
Conexiones concurrentes500+ (instancia única)
Throughput de transacciones100+ TPS simulados
Memoria en estado estable< 512MB
Los números reales de benchmarks están disponibles en el repo bajo
.

11. Ejecutalo Vos Mismo

El dashboard empieza a generar transacciones simuladas inmediatamente. Abrí
y mirá las métricas fluir. Acceder a la demo en vivo

12. Conclusiones Clave

  1. Nunca hagas broadcast por transacción. Agrupá en lotes de 500ms — los usuarios no notan la diferencia, tu servidor sí.
  2. Pre-computá todo. Las métricas del dashboard deben venir del cache, no de queries en vivo.
  3. Channel<T> está subestimado. Es el bus de mensajes interno perfecto para .NET — async, con límites, y backpressure.
  4. Mantené el hub delgado. La lógica de negocio va en los servicios, no en el hub de SignalR.
  5. Diseñá para la próxima escala. Los patrones de este repo funcionan a 100K/día; los mismos patrones con diferente infraestructura funcionan a 10M/día.

Esta arquitectura está basada en patrones que he implementado en producción para sistemas de monitoreo de transacciones financieras. El código fuente tiene licencia MIT — cloná, aprendé, adaptalo para tu caso de uso.

Código Fuente Completo en GitHub

Contacto — Sin pitch de ventas, solo una conversación técnica honesta.

¿Listo para iniciar tu proyecto?

Hablemos de cómo puedo ayudarte a construir soluciones modernas y escalables para tu negocio.

Contactar