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
WebSocket
Keep a bidirectional connection open for low-latency client and server messages.
Pub/Sub
Broadcast events from publishers to many independent subscribers.
Backpressure
Slow producers down when consumers cannot keep up.
Load Balancer
Distribute traffic across healthy servers without making clients know where work runs.
The Problem
Applications like chat, multiplayer games, collaborative editors, and live trading platforms require low-latency bidirectional communication between client and server. HTTP request-response adds too much overhead per interaction. WebSockets upgrade an HTTP connection to a persistent, full-duplex TCP channel — but scaling millions of concurrent WebSocket connections across a fleet of servers introduces challenges around connection management, message routing, state synchronization, and fault tolerance.
Architectural Approaches
Sticky Sessions with In-Process State
Route each client to a specific server instance using sticky sessions (IP hash, cookie, or connection ID). Each server holds connection state in memory and routes messages locally. Simple to build but limited in scale and resilience.
Pros
- +Simplest implementation — no external state store needed
- +Low latency for messages between users on the same server
- +Easy to reason about — each server is self-contained
Cons
- −Server failure drops all connections on that instance
- −Cross-server messaging requires an additional routing layer
- −Uneven load distribution as sticky sessions prevent rebalancing
- −Horizontal scaling is limited by single-server capacity
Pub/Sub Backbone with Stateless Gateways
WebSocket gateway servers are stateless beyond holding connections. All message routing goes through a central pub/sub system (Redis Pub/Sub, NATS, Kafka). When user A sends a message to user B, the gateway publishes to the bus, and the gateway holding B’s connection delivers it.
Pros
- +Gateways are interchangeable — easy horizontal scaling
- +Server failure only requires clients to reconnect to any instance
- +Clean separation between connection management and business logic
- +Supports broadcast, multicast, and unicast patterns
Cons
- −Added latency from pub/sub hop (typically 1-5ms)
- −Pub/sub becomes a critical dependency — must be highly available
- −Fan-out to all gateways can be wasteful for unicast messages
- −Message ordering across partitions requires careful design
Distributed Actor Model
Each connection and each chat room/channel is modeled as an actor (Erlang/Elixir processes, Akka actors, Cloudflare Durable Objects). Actors communicate via message passing and can be distributed across nodes. The runtime handles location transparency and fault recovery.
Pros
- +Natural modeling of concurrent connections and rooms
- +Built-in fault isolation — one actor crash doesn't affect others
- +Location-transparent messaging simplifies cross-node routing
- +Supervision trees enable automatic recovery
Cons
- −Requires a runtime that supports the actor model
- −Debugging distributed actors can be complex
- −Mailbox overflow under load requires backpressure design
- −Less common skillset in most engineering teams
Architecture
Key Components
WebSocket Gateway
Accepts WebSocket upgrades, manages connection lifecycle (open, message, ping/pong, close), authenticates on connect, and routes messages to/from the pub/sub backbone. Each instance handles 50K-500K concurrent connections depending on message throughput.
Connection Registry
Maps user IDs to gateway instances and connection IDs. Stored in Redis with TTL-based expiry. Used for targeted message delivery and presence tracking. Updated on connect/disconnect events.
Pub/Sub Backbone
Routes messages between gateway instances. Redis Pub/Sub for low-latency fan-out, or NATS/Kafka for higher durability. Supports channel-based subscriptions for rooms/topics and user-specific channels for DMs.
Message Service
Handles message persistence, history retrieval, and delivery receipts. Writes to the database and publishes events to the pub/sub bus. Decoupled from the gateway layer for independent scaling.
Auth Middleware
Validates JWT or session tokens during the WebSocket handshake. Rejects unauthorized connections before the upgrade completes. May also handle per-message authorization for sensitive operations.
Health & Heartbeat Manager
Sends periodic WebSocket ping frames to detect dead connections. Cleans up stale entries in the connection registry. Monitors gateway health and triggers graceful draining during deploys.
Data Model
Connection Lifecycle
A WebSocket connection goes through several stages:
- HTTP Upgrade — Client sends an HTTP request with
Upgrade: websocketheader. Server validates auth token (from query param or cookie) and responds with 101 Switching Protocols. - Initialization — Server registers the connection in the registry, subscribes to relevant pub/sub channels, and sends an initial state payload (unread counts, presence data).
- Steady State — Bidirectional message exchange. Server sends ping frames every 30s; client responds with pong. Messages are routed through the pub/sub backbone.
- Graceful Close — Either side sends a close frame with a status code. Server unsubscribes from channels, removes the registry entry, and notifies presence subscribers.
- Abnormal Close — Network failure or crash. Detected via ping timeout (typically 60-90s). Cleanup runs asynchronously, and the client reconnects with exponential backoff.
Reconnection: Clients should implement exponential backoff with jitter (e.g., 1s, 2s, 4s + random 0-1s). On reconnect, send the last received message ID to enable server-side replay of missed messages.
Scaling to Millions of Connections
Per-server limits: A single server with 16GB RAM can hold ~500K idle WebSocket connections (each consuming ~30KB). Active connections with message buffering need more memory. Key OS tunings:
- Set
ulimit -nto 1M+ for file descriptors - Tune
net.core.somaxconnand TCP buffer sizes - Use epoll (Linux) or kqueue (BSD/macOS) for efficient I/O multiplexing
Horizontal scaling strategy:
- Add gateway instances behind a Layer 4 load balancer
- Shard pub/sub channels to avoid hotspots (e.g., partition by channel ID hash)
- Separate read and write paths — message persistence can be async
- Use connection draining during deploys — stop accepting new connections, wait for existing ones to close or migrate
Cost optimization: WebSocket connections are long-lived and consume resources even when idle. Consider:
- Downgrading idle connections to SSE or long-polling
- Implementing connection quotas per user
- Using serverless WebSocket services (AWS API Gateway WebSocket, Cloudflare Durable Objects) for variable workloads
Message Ordering and Delivery
Within a connection: TCP guarantees in-order delivery. Messages sent on a single WebSocket connection arrive in order.
Across connections: When a user reconnects to a different gateway, message ordering depends on the pub/sub system. Solutions:
- Assign sequence numbers per channel. Clients buffer and reorder if needed.
- Use Kafka partitions keyed by channel ID for strict per-channel ordering.
- Accept eventual consistency for non-critical updates (typing indicators, presence).
Delivery guarantees:
- At-most-once: Default WebSocket behavior. Message lost if connection drops mid-delivery.
- At-least-once: Persist messages before acknowledging to sender. Replay on reconnect using last-message-ID. Clients deduplicate using message IDs.
- Exactly-once: Very expensive. Requires idempotent message processing and acknowledgment tracking. Rarely needed — at-least-once with client dedup is sufficient for most use cases.
Real-World Examples
Slack
Uses WebSocket connections for real-time messaging with a Gateway Fleet pattern. Falls back to long-polling when WebSocket is unavailable. Messages are persisted in MySQL and routed via an internal message bus.
Discord
Runs millions of concurrent WebSocket connections on Elixir/Erlang gateway nodes. Each guild (server) is managed by a process that handles message fan-out to connected members. Uses consistent hashing for guild-to-node assignment.
Figma
WebSocket connections for real-time collaborative design. Uses a custom CRDT-based sync protocol over WebSocket for conflict-free concurrent editing. Each document session is managed by a dedicated server process.
Binance
Streams real-time market data to millions of traders via WebSocket. Uses a tiered fan-out architecture: internal pub/sub → edge gateways → client connections. Supports per-symbol and aggregate streams.
Architectural Tradeoffs
Layer 4 (TCP) vs Layer 7 (HTTP) load balancing
Pros
- +L4 is simpler and lower latency for persistent connections
- +L7 enables smarter routing (auth-aware, header-based)
- +L4 avoids HTTP parsing overhead on every frame
Cons
- −L4 cannot inspect WebSocket frames for routing decisions
- −L7 adds latency but enables features like rate limiting at the LB
- −Sticky sessions at L4 can cause hotspots
Redis Pub/Sub vs Kafka for message backbone
Pros
- +Redis Pub/Sub: ultra-low latency (~0.1ms), simple setup
- +Kafka: durable, replayable, ordered per partition
- +Redis: better for ephemeral real-time events
Cons
- −Redis Pub/Sub: fire-and-forget, no durability or replay
- −Kafka: higher latency (5-50ms), more operational complexity
- −Kafka: overkill for ephemeral events like typing indicators
Connection-per-user vs multiplexed connections
Pros
- +One connection per user is simpler to implement and debug
- +Multiplexing reduces connection count for multi-tab users
- +SharedWorker or BroadcastChannel can share one WS across tabs
Cons
- −Multiple connections per user waste server resources
- −Multiplexing adds client-side complexity
- −SharedWorker support varies across browsers
Learn WebSocket Infrastructure
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