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
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
Backpressure
Slow producers down when consumers cannot keep up.
Pub/Sub
Broadcast events from publishers to many independent subscribers.
WebSocket
Keep a bidirectional connection open for low-latency client and server messages.
CRDT
Let distributed replicas accept local edits and merge without conflicts.
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
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
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 resumeretry: <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:
- 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.
- Targeted routing — Maintain a connection registry mapping user→gateway. Publish events only to the specific gateway. More efficient but requires registry consistency.
- 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.
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
API Versioning
Backward-compatible API evolution with URL, header, and content negotiation strategies
Audit Logging
Append-only event stores, tamper-proof records, and compliance-ready activity trails
Caching Strategies
Cache-aside, write-through, invalidation patterns, and distributed caching with Redis