MAISON CODE .
/ Backend · Realtime · WebSockets · Analytics · UX

Realtime Dashboards: Engineering Due Diligence

Why the 'Refresh' button is obsolete. A technical guide to Server-Sent Events (SSE), WebSockets, and the C10K problem.

AB
Alex B.
Realtime Dashboards: Engineering Due Diligence

It is Black Friday. Use are in the “War Room.” The CEO asks: “How much revenue did we make in the last 10 minutes?” You click refresh. The database query takes 15 seconds. The page loads. “$50,000.” By the time you say it, the number is wrong.

In modern commerce, data that is static is dead. Inventory moves. Prices change. Traffic spikes. Realtime Dashboards are not just a “nice to have” visual toy; they are mission-critical operational tools. But engineering them correctly—for thousands of concurrent users, without killing your database—is a distributed systems challenge.

At Maison Code Paris, we build “Mission Control” interfaces that feel alive. Here is how we do it without melting the servers.

Why Maison Code Discusses This

At Maison Code Paris, we act as the architectural conscience for our clients. We often inherit “modern” stacks that were built without a foundational understanding of scale. We see simple APIs that take 4 seconds to respond because of N+1 query problems, and “Microservices” that cost $5,000/month in idle cloud fees.

We discuss this topic because it represents a critical pivot point in engineering maturity. Implementing this correctly differentiates a fragile MVP from a resilient, enterprise-grade platform that can handle Black Friday traffic without breaking a sweat.

The Protocol Battle: Polling vs SSE vs WebSockets

The first decision efficient data transport.

1. Polling (The Naïve Approach)

setInterval(() => fetch('/api/sales'), 5000);

  • Pros: Simple. Works on every server (PHP, Node, Python).
  • Cons:
    • Latency: Average 2.5s delay.
    • Waste: 90% of requests return “No Change”. You are hammering your DB for nothing.
    • Battery: Keeps the mobile radio active.

2. WebSockets (The Bi-Directional Pipe)

A persistent TCP connection.

  • Pros: Extremely fast. Bi-directional (Client can send “I am typing”).
  • Cons:
    • Firewalls: Corporate proxies often block non-HTTP traffic.
    • Statefulness: Scaling is hard. You need sticky sessions or a redis backend.
    • Overkill: A dashboard is usually Read-Only.

3. Server-Sent Events (SSE) (The Gold Standard)

Standard HTTP connection, but the server keeps it open and streams text. Content-Type: text/event-stream.

  • Pros:
    • HTTP Compatible: Works through standard Load Balancers / Proxies.
    • Auto-Reconnect: The browser handles retries automatically.
    • Lightweight: Only simple text messages.
  • Cons: One-way only (Server -> Client).

For Dashboards, SSE is the technical winner.

Architecture: The Event Bus

You cannot simple stream from the Database to the Client. If you have 5,000 clients, and you run a SELECT sum(total) FROM orders every time a row changes, PostgreSQL will die.

You need an Event Bus Architecture.

  1. Ingest: Order Create Webhook -> API.
  2. Publish: API -> Redis Pub/Sub (channel: sales).
  3. Fan-Out: The SSE Server subscribes to Redis.
  4. Broadcast: When Redis emits a message, the SSE Server loops through connected clients and writes to their streams.

Code: The Remix / Node.js Stream

In a modern framework like Remix or Next.js App Router, streaming is native.

// app/routes/api.stream.ts in Remix
import { eventStream } from "remix-utils/sse/server";
import { redis } from "~/lib/redis";

export async function loader({ request }: LoaderFunctionArgs) {
  return eventStream(request.signal, function setup(send) {
    const channel = "dashboard-updates";

    const listener = (message: string) => {
      send({ event: "update", data: message });
    };

    const subscriber = redis.duplicate();
    subscriber.subscribe(channel);
    subscriber.on("message", (chan, msg) => {
      if (chan === channel) listener(msg);
    });

    return function cleanup() {
      subscriber.unsubscribe(channel);
      subscriber.quit();
    };
  });
}

The Connectivity Challenges

1. The C10K Problem (Scaling)

Standard Node.js can handle ~10k concurrent connections. Servers like Apache/PHP struggle with hundreds. If you need 100k connected users (e.g., a massive flash sale countdown), you cannot use a standard single server. Solution:

  • Horizontal Scaling: 10 Servers behind a Load Balancer.
  • Redis Backplane: Every server subscribes to Redis. So if Server A publishes, Server B receives it and pushes to User B.
  • Managed Services: For extreme scale, offload to Pusher or Supabase Realtime. They manage the socket layer.

2. The Thundering Herd (Reconnection)

You deploy a new version of the dashboard. The server restarts. 5,000 clients disconnect. 5,000 clients immediately try to reconnect at exactly 10:00:01. Your CPU spikes to 100%. The server crashes. Clients retry. Loop of death. Solution: Jitter. Client should wait random(0, 5000) milliseconds before reconnecting. The browser’s native SSE implementation has basic backoff, but custom WebSocket logic often overlooks this.

3. OS File Descriptor Limits

A Linux server has a default limit of 1024 open files (connections). If you want 10k users, you MUST edit /etc/security/limits.conf. * soft nofile 100000 * hard nofile 100000 If you forget this, your Node.js server will crash with EMFILE errors at user #1025.

State Reconciliation: The Gap

User is in a train tunnel. 10:00:00 - User Online. Total: $100. 10:00:05 - Tunnel. User Offline. 10:00:06 - Server pushes event “New Order +$50”. Total is now $150. 10:00:10 - Tunnel Ends. User Reconnects.

The user thinks the total is $100. They missed the event. The state is drifting.

Fix: Last-Event-ID.

  1. Every event has an ID (Sequence number or Timestamp).
  2. Browser stores the last ID it saw.
  3. On reconnect, browser sends Last-Event-ID: 105.
  4. Server checks its buffer. “Ah, client is behind. Here are events 106, 107, 108.”

Or, simpler strategy: StaleCheck. On every reconnect, the frontend triggers a standard HTTP fetch('/api/current-total') to resync the baseline.

UX: Visualizing the Pulse

Realtime data creates “Visual Noise”. If you sell 10 items per second, and you update the number 10 times per second, the user cannot read it. It’s just a blur.

Throttle Updates: Limit UI repaints to once per 500ms. Accumulate the changes in a buffer. Buffer = +10, +5, +20. Flush -> Update UI +35.

Animation: Use framer-motion or react-spring to animate the number. 100 -> 135. The user sees the numbers roll up (like a gas pump). This conveys “Velocity” and “Activity” much better than a static jump.

function AnimatedNumber({ value }) {
  const { number } = useSpring({
    from: { number: 0 },
    number: value,
    delay: 200,
    config: { mass: 1, tension: 20, friction: 10 },
  });

  return <animated.span>{number.to((n) => n.toFixed(0))}</animated.span>;
}

10. Security: Role-Based Access Control (RBAC) on Streams

Standard HTTP Auth validates the request. But once the socket is open, how do you prevent a “Store Manager” from listening to “Global Admin” sales events? Channel Scoping.

  1. User connects with JWT.
  2. Server decodes JWT -> role: manager, store_id: 12.
  3. Client requests subscription to sales-global.
  4. Server logic: if (role !== 'admin') throw Forbidden.
  5. Server logic: Only allow redis.subscribe('sales-store-12').

11. Data Granularity: Tick vs Aggregate

Should you emit an event for every $5 order? If you have 100 orders/sec, your dashboard will look like a strobe light. Aggregation Strategy:

  1. Raw Stream: Internal use / Debugging.
  2. Throttled Stream (User UI): Aggregate every 1 second.
    • { events: 50, total_added: 5000 }.
    • Send one message.
  3. Snapshot Stream: Every 1 minute. Full sync. Balance “Realtime Feel” with “Human Cognitive Limits”.

13. The “Builder vs Buy” Decision (Grafana)

Sometimes, you don’t need to build a custom React Dashboard. If the audience is “Engineers” or “Internal Ops”, just use Grafana. Connect it to your PostgreSQL read replica. Set refresh rate to 5s. Done. Costs $0. We only build custom React dashboards when the audience is “External Customers” or “C-Level Execs who need a specific UX”.

14. Alerting Thresholds (Visual Cues)

A number changing from 100 to 120 is data. A number changing from 100 to 120 (Red Background) is information. We build “Threshold Logic” into the frontend components. if (value < target) color = 'red'. This helps users process the realtime stream cognitively. “I don’t need to read the numbers; I just need to scan for Red.”

15. Conclusion

Building a Realtime Dashboard is about managing the illusion of immediacy. It requires a synchronized dance between the Database, the Event Bus, the Web Server, and the Client UI. Done poorly, it is a performance bottleneck. Done well, it acts as the heartbeat of the organization.


Is your data stale?

We build high-frequency trading interfaces for retail.

Hire our Architects.