🗓️ 06062026 1500
📎 #api #realtime #protocols

REALTIME PATTERNS COMPARISON

Four ways to push data to clients. Choose the simplest option that meets your needs.

At a Glance​

Short Pollinglong_pollingserver_sent_eventswebsocket_protocol
DirectionClient -> ServerClient -> Server (held)Server -> ClientBidirectional
ConnectionNew per requestHeld, reconnect after each responsePersistent streamPersistent socket
LatencyPoll interval (1–30s)Near-real-timeReal-timeReal-time
ProtocolHTTPHTTPHTTP (text/event-stream)ws:// / wss:// (upgraded from HTTP)
Binary supportVia response bodyVia response bodyText only (Base64 for binary)Native binary frames
Auto-reconnectN/A (client polls)Client must implementBuilt-in (EventSource)Must implement
Browser supportUniversalUniversalAll modern (no IE)All modern (no IE)
Proxy friendlyYesMostly (idle timeouts)Yes (standard HTTP)Varies (upgrade may be blocked)
Max connectionsN/A (short-lived)6/domain (HTTP/1.1)6/domain (HTTP/1.1), unlimited (HTTP/2)6/domain (HTTP/1.1)
Server costLow per-request, high aggregate1 held conn/client1 persistent conn/client1 persistent socket/client

Decision Guide​

  • Need bidirectional messaging? Use websocket_protocol.
  • Need one-way, high-frequency updates? Use server_sent_events. Use websocket_protocol if you need binary data.
  • Need to work through restrictive proxies or firewalls? Use long_polling, with short polling as a fallback.
  • Need simple, low-frequency updates? Use server_sent_events for updates every few seconds, or short polling with ETag/304 for updates every 30 seconds or more.

Common Combinations​

SSE + POST. Most web apps have asymmetric traffic: many server-to-client updates and occasional client-to-server actions. Use server_sent_events for updates and regular POST/PUT requests for client actions. This is simpler than websocket_protocol, works through standard proxies, and keeps each concern separate.

WebSocket + HTTP fallback. Socket.IO popularized this: try websocket_protocol, then fall back to long_polling if the upgrade fails. Useful when clients are behind unpredictable corporate proxies.

Short Polling for status checks. Job progress, deploy status, CI builds — poll every 5–10s with ETag/If-None-Match. Server returns 304 Not Modified when nothing changed (minimal bandwidth). Simple, stateless, cache-friendly. Don't overthink it.

Scaling Considerations​

All persistent-connection approaches (long_polling, server_sent_events, and websocket_protocol) share the same fundamental challenge: one connection per client. At scale:

  • Thread-per-request servers (classic Tomcat, PHP) don't work. Need non-blocking I/O (Netty, Node.js, Go, Spring WebFlux).
  • Load balancers need connection-aware routing. websocket_protocol and long_polling need sticky sessions or a pub/sub backbone. server_sent_events can reconnect to another backend, but replay still requires shared event history.
  • File descriptors. Each connection = 1 fd. Default OS limits (~1024) hit fast. Tune ulimit -n for production.
  • Memory. Each connection carries state (buffers, metadata). Profile per-connection overhead and multiply by expected concurrency.

Bridges to Other Domains​

  • load_balancer — sticky sessions for stateful connections, health checks for WebSocket backends.
  • api_gateway — must understand Upgrade headers to proxy WebSocket. Most modern gateways (NGINX, Kong, Envoy) do.
  • mcp_transports — MCP Streamable HTTP uses HTTP POST with optional SSE responses. Real-world example of combining request/response with streaming.
  • circuit_breaker_pattern — upstream circuit breakers for WebSocket reconnection storms.

References​