Three approaches
| Approach | Mechanism | Cost |
|---|---|---|
| Short polling | Poll on a timer for new data | Wasted requests, high latency |
| Long polling | Hold the request until data arrives | Rebuilds each time, server holds connections |
| WebSocket | Full-duplex long connection after one handshake | Connection needs upkeep, stateful |
How to choose
- Low frequency, tolerant delay: short polling is simplest;
- Near-real-time, one-way push: long polling suffices and is proxy-friendly;
- High-frequency two-way, chat or quotes: WebSocket fits best.
WebSocket notes
- Heartbeat: send pings or a proxy may drop the idle connection;
- Reconnect: on flaky networks, reconnect with exponential backoff to avoid hammering the server;
- Scale out: with multiple instances, a message bus broadcasts to the node holding that connection.
Real-world cases: three "it connected but is unstable"
- Everyone drops after a few minutes: Nginx's default
proxy_read_timeoutis 60 seconds, so idle long connections are cut. Fix: raise the timeout and have the client send heartbeats. - A reconnect storm takes down the server: after a network outage every client reconnects at once. Fix: exponential backoff with jitter, plus a per-IP connection rate limit.
- Messages lost across instances: the user is connected to instance A but the message is produced on instance B, which holds no such connection. Fix: broadcast via Redis Pub/Sub or a message bus, or route consistently by user.
FAQ
Can WebSocket send auth headers? Native browser WebSocket cannot set custom headers; the usual pattern is a one-time short-lived token in the handshake URL, or cookie validation. Are messages during a disconnect replayed? No — a long connection does not guarantee delivery; use sequence numbers plus a history fetch to catch up. Can SSE replace it? If only the server pushes, SSE is simpler: automatic reconnect over plain HTTP. Is WebSocket always cheaper than polling? Only when connections are stable and messages frequent; for low-frequency cases keeping long connections open is more expensive.
Sizing long connections
The bottleneck for WebSocket is rarely bandwidth; it is connection count and memory:
- Memory per connection: tens to a hundred KB each including buffers and runtime overhead, so a process holding tens of thousands needs that budgeted;
- File descriptors: one per connection — raise both process and system
ulimitor hit the ceiling under load; - Heartbeat amplification: at one ping per 30 seconds per connection, 100,000 connections means thousands of messages a second before any real traffic;
- Stateful operations: rolling deploys must drain gracefully so clients reconnect on schedule, and scaling needs connection migration;
- Broadcast cost: one fan-out to every connection multiplies egress instantly; group subscriptions and push only to relevant connections.
The usual conclusion: keep long connections for high-frequency, genuinely bidirectional flows, and use SSE or a push service for low-frequency notifications.
Client-side implementation notes
- Reconnect with state recovery: reconnecting is step one; catch up on messages missed during the outage by sequence or timestamp, or users see discontinuous data.
- Distinguish offline from empty: surface the connection state instead of showing "no data" during an outage, which misleads users.
- Handle backgrounding: mobile systems may reclaim connections in the background, so check and rebuild on return rather than relying on automatic recovery.
- Bound message size: a large message blocks others, so send references and fetch on demand, or chunk the payload.
- Queue and throttle sends: on weak networks clients pile up sends; use a bounded send queue so reconnection does not flush a flood at once.
Most complexity lives in client state management. Separating connection state, data consistency and user experience makes the implementation much clearer.