Capabilities/Real-Time & Communication

Server-Sent Events

One-way real-time streaming from server to client over HTTP

real-timestreamingHTTPEventSourcepush

Learning lens

Start with judgment, not a universal prescription.

This page is a learning model for understanding the capability. It does not claim exact production internals, exact costs, or an infinite-scale architecture.

Evidence

Conceptual pattern

0->1 default

Start with the simplest delivery path that meets latency needs before introducing durable fanout or global state.

Cost drivers

open connectionsfanout workpresence updatescross-region traffic

Watch for breakpoints

  • -connection count grows faster than app servers
  • -message fanout dominates request work
  • -presence state becomes stale or expensive

Learn first

Concepts behind this architecture

The Problem

Many applications need to push updates from the server to the client in real time — live dashboards, notification feeds, stock tickers, build logs, and progress indicators. Traditional HTTP request-response requires the client to poll repeatedly, wasting bandwidth and adding latency. Server-Sent Events (SSE) provides a standardized, lightweight mechanism for the server to push a continuous stream of events to the client over a single long-lived HTTP connection.

Architectural Approaches

Native SSE with EventSource API

Use the browser’s built-in EventSource API to establish a persistent HTTP connection. The server responds with Content-Type: text/event-stream and writes events in the SSE wire format (data:, event:, id:, retry: fields). The browser automatically handles reconnection with the Last-Event-ID header.

Pros

  • +Zero client-side dependencies — built into every modern browser
  • +Automatic reconnection with last-event-id resume
  • +Works through HTTP/2 multiplexing without extra connections
  • +Simple text-based protocol easy to debug with curl

Cons

  • −Unidirectional — server to client only
  • −Limited to ~6 concurrent connections per domain in HTTP/1.1
  • −No binary data support (text only)
  • −Some older proxies/load balancers may buffer or drop the connection

Long Polling Fallback

Client sends a request, server holds it open until data is available or a timeout occurs, then responds. Client immediately re-requests. Simulates push semantics over standard HTTP. Useful as a fallback when SSE or WebSockets are blocked by corporate proxies.

Pros

  • +Works everywhere — no special protocol support needed
  • +Compatible with all proxies and load balancers
  • +Simple to implement on any HTTP server

Cons

  • −Higher latency due to connection setup overhead per message
  • −More server resources — each poll is a new HTTP request
  • −Complex ordering and deduplication logic required
  • −No built-in reconnection or event ID mechanism

WebSocket for Bidirectional Needs

If the application also needs client-to-server streaming (e.g. chat input, collaborative cursors), WebSockets provide full-duplex communication. However, for pure server-to-client push, SSE is simpler and sufficient.

Pros

  • +Full-duplex — bidirectional communication
  • +Binary and text data support
  • +Lower per-message overhead after handshake

Cons

  • −More complex server implementation and scaling
  • −No automatic reconnection — must implement manually
  • −Requires sticky sessions or external pub/sub for horizontal scaling
  • −Overkill for unidirectional push scenarios

Architecture

Loading diagram...
Server-Sent Events — High-level architecture

Key Components

SSE Gateway

Holds open HTTP connections and streams events to clients. Subscribes to a pub/sub bus (e.g. Redis Streams) for events. Each gateway instance manages thousands of concurrent connections using async I/O (Node.js streams, Go goroutines, etc.).

Pub/Sub Bus

Decouples event producers from SSE gateways. Redis Streams, NATS, or Kafka act as the fan-out layer. Each gateway subscribes to relevant channels/topics and forwards matching events to connected clients.

Event Producer

Backend services or workers that generate events (e.g. order status changes, new notifications). They publish structured events to the pub/sub bus with a channel key, event type, and payload.

Connection Registry

Tracks which users are connected to which gateway instance. Used for targeted delivery (send event only to user X) and connection lifecycle management (heartbeats, cleanup).

Load Balancer

Distributes incoming SSE connections across gateway instances. Must support long-lived HTTP connections and avoid premature timeouts. HTTP/2 multiplexing reduces the connection count issue.

Data Model

Loading diagram...
Server-Sent Events — Entity relationship diagram

SSE Wire Protocol

The SSE protocol is remarkably simple — it’s just a UTF-8 text stream over HTTP with a specific format:

Event format:

  • data: <payload>\n\n — the event data (can span multiple lines)
  • event: <type>\n — optional named event type (defaults to “message”)
  • id: <id>\n — optional event ID for resume
  • retry: <ms>\n — optional reconnection interval in milliseconds
  • : comment\n — comment line, useful for keep-alive heartbeats

Reconnection: When the connection drops, the browser automatically reconnects and sends Last-Event-ID header. The server can use this to replay missed events — critical for reliability. Store events with sequential IDs and replay from the requested ID on reconnect.

Keep-alive: Send a comment line (: heartbeat\n\n) every 15–30 seconds to prevent proxies and load balancers from closing idle connections.

Scaling SSE Horizontally

A single SSE gateway can hold tens of thousands of connections, but at scale you need multiple instances.

The fan-out problem: When an event occurs, it must reach the correct gateway holding the target user’s connection. Solutions:

  1. Pub/Sub fan-out — Every gateway subscribes to a Redis Pub/Sub channel or Kafka topic. Events are broadcast to all gateways, and each gateway filters locally. Simple but wasteful at large scale.
  2. Targeted routing — Maintain a connection registry mapping user→gateway. Publish events only to the specific gateway. More efficient but requires registry consistency.
  3. Consistent hashing — Route users to gateways deterministically. No registry needed, but rebalancing on scale events causes reconnections.

Memory management: Each SSE connection consumes a file descriptor and a small memory buffer. Monitor open FD counts and set appropriate ulimit values. Use connection pooling for the pub/sub subscriber.

Loading diagram...
Scaling SSE Horizontally

Event Ordering and Delivery Guarantees

SSE provides at-most-once delivery by default — if the client misses events during a disconnect, they’re gone unless you implement replay.

At-least-once delivery: Assign monotonically increasing IDs to events. Store recent events in a bounded buffer (e.g., Redis sorted set with TTL). On reconnect, replay from the client’s Last-Event-ID. Accept that clients may receive duplicates and handle idempotently.

Ordering: Events within a single SSE connection are ordered (TCP guarantees this). Across multiple channels or after reconnection, ordering depends on your event store. Use timestamps or sequence numbers for cross-channel ordering.

Backpressure: If events produce faster than the client can consume, the TCP send buffer fills up. Monitor write buffer sizes and drop/batch events for slow clients rather than blocking the event loop.

Real-World Examples

GitHub

Uses SSE for real-time updates on Actions workflow runs, deployment status, and Codespaces state changes.

Vercel

Streams build logs and deployment progress to the dashboard via SSE, with automatic reconnection and log replay.

ChatGPT

Streams LLM token-by-token responses via SSE, enabling the progressive text rendering experience.

Shopify

Uses SSE for real-time inventory and order status updates in the merchant dashboard.

Architectural Tradeoffs

SSE vs WebSocket for server push

Pros

  • +Simpler protocol — works over standard HTTP
  • +Built-in reconnection and event ID resume
  • +HTTP/2 multiplexing eliminates connection limits
  • +Easier to cache and proxy

Cons

  • −Unidirectional only
  • −Text-only (no binary)
  • −Less ecosystem tooling compared to WebSocket

Fan-out via pub/sub vs targeted routing

Pros

  • +Pub/sub is simpler — no connection registry needed
  • +Naturally handles broadcast events
  • +Gateway instances are stateless (aside from connections)

Cons

  • −Wasteful at scale — every gateway processes every event
  • −Targeted routing is more efficient but adds registry complexity
  • −Registry must handle gateway failures gracefully

Event replay buffer size

Pros

  • +Larger buffer improves reliability during long disconnects
  • +Small buffer reduces memory footprint per channel
  • +TTL-based expiry prevents unbounded growth

Cons

  • −Large buffers increase memory usage and replay time
  • −Small buffers risk losing events during extended outages
  • −Must balance reliability vs resource cost per use case

Learn Server-Sent Events

Copy a learning context prompt for an AI assistant. It keeps assumptions visible and asks for tradeoffs, cost drivers, failure modes, and what to measure before adding complexity.

Explore More Capabilities