Socket.io vs uWS vs AnyCable
A 2026 benchmark of five WebSocket setups for Node.js, TypeScript, Bun, and Deno apps. Self-hosted alternatives to Pusher Channels and Ably.
- Socket.io + CSR
- 3 / 7 ms
- uWS
- 2 / 10 ms
- AnyCable Pro
- 3 / 11 ms
- Socket.io default
- 85%
- uWS
- 87%
- AnyCable
- 100%
- Socket.io + CSR
- fails > 10K
- uWS
- 9 s @ 20K
- AnyCable
- 0 s, no restart
- Socket.io caps ~120k/node
- ~52 KB
- AnyCable Pro replay included
- 18 KB
- uWS bare wire
- 5 KB
Your TS/JS application doesn't want to sit still: it needs WebSockets for all sorts of real-time messaging, streaming from LLMs, and collaborative features. What to choose? We benchmarked Socket.io, uWebSockets, and AnyCable across three rubrics: latency + throughput, reliability, and scalability. If we want to dance, let's dance smoothly.
Every option runs as a standalone WS service on the same Railway box, apples-to-apples. Reproducible from the open-source bench repo.
What we compare#
Five production-shaped options, same hardware:
- Default Socket.io — baseline popular option.
- Socket.io + CSR — Socket.io with Connection state recovery, the opt-in for delivery and order guarantees.
- uWebSockets.js + topics — the “just use uWS” alternative, using its built-in
subscribe/publishAPI. - AnyCable OSS — a separate Go binary your app broadcasts to over HTTP, broker built in.
- AnyCable Pro — same protocol as OSS; denser per-connection memory, shared replay state, commercial license.
// Server const io = new Server(httpServer); io.on('connection', (socket) => { socket.on('subscribe', (t) => socket.join(t)); }); // Broadcast from anywhere: io.to('chat:42').emit('message', payload); // Trade-off: at-most-once delivery — messages // during a disconnect are gone.
// Server — same code, plus the CSR option const io = new Server(httpServer, { connectionStateRecovery: { maxDisconnectionDuration: 2 * 60 * 1000, }, }); // Same broadcast call: io.to('chat:42').emit('message', payload); // Trade-off: replay buffers held in memory. // Adapter must support CSR (in-memory or pg).
// Server — uWS + built-in topics API const app = uWS.App(); app.ws('/ws', { message: (ws, msg) => { const { topic } = JSON.parse(msg); ws.subscribe(topic); }, }); // Broadcast from anywhere: app.publish('chat:42', JSON.stringify(payload)); // Trade-off: no replay, no built-in observability, // "bug reports only" maintainer posture.
// Your Node app stays put. anycable-go runs as // a separate Go process; you broadcast over HTTP: await fetch('http://anycable:8080/_broadcast', { method: 'POST', body: JSON.stringify({ stream: 'chat:42', data: JSON.stringify(payload), }), }); // Trade-off: extra process to run; broadcast hop // over HTTP caps single-publisher throughput.
// Same broadcast code as OSS — just a different // binary (anycable-go-pro) for the WS server: await fetch('http://anycable:8080/_broadcast', { method: 'POST', body: JSON.stringify({ stream: 'chat:42', data: JSON.stringify(payload), }), }); // Trade-off: commercial license; in return you get // the embedded broker, lower per-conn memory, and // horizontal scale with shared replay state.
Note on architecture#
Production realtime needs the WS server as a standalone service, not embedded in your Node app. Embedded Socket.io/uWS freeze every user for >2 seconds and lose messages on every app deploy. That's what the rest of the page compares: the WS layer as a separate process.
What we tested: 3-node cluster, embedded Socket.io + Redis, rolling deploy (redeploy each node in turn, wait for it back, move on).
Result: every user sees a >2 s freeze on rolling deployment when their node restarts. 99.6% delivered. 2-3 lost per user during the gap.
What happens when an in-process WS layer restarts. Same Socket.io / +CSR / uWS server on the same 32 GB / 8 vCPU box used for the other tests, scaled from 5K to 25K idle clients, then a real railway redeploy on the app. “Recovery” is wall-clock until 95% reconnected; we cap each run at 10 minutes. AnyCable runs the WS layer as a separate Go binary, so the app deploy never touches it. Methodology · thundering-herd reading.
Recovery degrades for Socket.io as connection count climbs: at 15K only three quarters reconnect within 10 min; at 25K just one fifth. uWS's C++ engine absorbs the storm cleanly (2.3 s at 10K, 8.8 s at 20K, both 100%). AnyCable doesn't restart, so there's nothing to recover from.
| Clients | Recovery† | Reconnected | AnyCable OSS & Pro |
|---|---|---|---|
| 5,000 | 1.6 s | 100% | 0 s |
| 10,000 | 65 s | 96% | 0 s |
| 15,000 | > 10 min | 75% | 0 s |
| 20,000 | > 10 min | 33% | 0 s |
| 25,000 | > 10 min | 19% | 0 s |
†Recovery wall-clock and reconnect % measured for Socket.io. uWS recovery measured separately: 10K → 2.3 s / 98% back, 20K → 8.8 s / 100% back. uWS's C++ engine absorbs the reconnect storm where Socket.io's Node loop can't past 10K. AnyCable runs WS as a separate Go binary; app deploys never touch it, so reconnect time is zero by construction.
How fast is it?#
Latency is the floor of what realtime can feel like — the tempo your app dances to. Two things to measure: roundtrip latency (one publisher to all subscribers) and broadcast throughput (how many messages per second the server can fan out before that latency starts inflating). All in the standalone shape: WS server as a separate process, publisher posting over HTTP.
Roundtrip latency
Same broadcast workload at two connection counts. The shape of the curve from 1k to 10k tells you whether the server has headroom or is approaching a wall.
At 10K subscribers, server-side p99 lands under 15 ms for all five protocols. uWS leads the floor (2 ms p50); Socket.io + CSR hold the tightest tail (7 ms p99); AnyCable Pro pays a 4-ms broker-write premium (11 ms p99) for the replay buffer that powers 100% jitter delivery.
| Setup p50 / p99 | 1k subs | 10k subs |
|---|---|---|
| Socket.io default | 3 / 7 ms | 3 / 7 ms |
| Socket.io + CSR | 3 / 7 ms | 3 / 7 ms |
| uWS topics | 2 / 7 ms | 2 / 10 ms |
| AnyCable OSS | 3 / 13 ms | 3 / 15 ms |
| AnyCable Pro | 3 / 10 ms | 3 / 11 ms |
Per-message HTTP POST to /_broadcast, matching production publishers. 1K: 4 shards × 250 cables. 10K: 40 shards × 250. Sharding the test drivers keeps each Node.js event loop unsaturated, so we measure what one real browser sees, not what 2500 cables in one process queue up. Intra-Railway tracing of AnyCable's broker.Store + HTTP roundtrip: under 11 ms p99 at 2500 subs.
Broadcast throughput
1M deliveries to 10K subscribers, 100% required. Broadcasts come from 40 parallel publishers, mirroring a horizontally scaled production service.
All five sustain 1M deliveries at 100%. AnyCable Pro leads p99 (12 ms) ahead of uWS (177 ms) and Socket.io family (1.4–2.5 s). With 40 concurrent publishers, anycable-go fans work across cores; a single Node event loop serializes.
| Setup 10K subs, 1M deliveries, 40 parallel publishers | Delivered | p50 | p99 |
|---|---|---|---|
| Socket.io default | 100% | 155 ms | 1.41 s |
| Socket.io + CSR | 100% | 127 ms | 2.54 s |
| uWS | 100% | 18 ms | 177 ms |
| AnyCable OSS | 100% | 4 ms | 15 ms |
| AnyCable Pro | 100% | 4 ms | 12 ms |
40 bench-runner shards × 250 cables, each shard runs its own publisher pool. Aggregate: 100 broadcasts/sec across publishers, 1M deliveries. Models a horizontally scaled app with many service instances broadcasting simultaneously. See the latency methodology footnote for why we shard the test drivers.
Whispers: client-to-client updates that bypass the backend
Cursors, typing indicators, presence pings. These travel client → WS server → peers without invoking your app. AnyCable ships it as a native primitive; Socket.io emulates with rooms; uWS with topics. The differences show up under load.
Socket.io rooms saturates and loses ~40% of whispers because every one goes back through the WS process. uWS topics and AnyCable both deliver 100% with single-digit p50 and a p99 under 15 ms; AnyCable leads p50 (2 ms vs 5 ms).
| Setup 1k clients, 10 rooms, 100 peers/room, 2 Hz | Native? | Delivered | p50 | p99 |
|---|---|---|---|---|
| Socket.io rooms | Emulated | 61% | 1.39 s | 8.90 s |
| uWS topics | Emulated | 100% | 5 ms | 14 ms |
| AnyCable OSS | Native | 100% | 2 ms | 13 ms |
| AnyCable Pro | Native | 100% | 2 ms | 13 ms |
1K clients in 10 rooms (100 peers/room), 2 Hz whispers for 30 s. Each whisper fans to 99 peers; each client receives ~200 msgs/sec. uWS, AnyCable OSS, and AnyCable Pro were re-measured at 40 shards × 25 cables so the bench-runner's Node event loop wasn't the bottleneck (single-shard measured the test driver's library overhead at high receive rates, not the broker). Socket.io's row stays at the single-shard number; its limit is server-side rooms emit, not driver-side, and the multi-shard re-test confirms saturation in the same 25–50% range. Methodology.
How reliable is delivery?#
Your users aren't always on a high-speed fiber network. Micro disruptions are mostly invisible to HTTP, but they're the deal-breaker for realtime: a missed beat in the music. Two things decide how it feels: how often messages are lost and how long the recovery window drags. Both come down to whether the protocol carries replay state.
Default Socket.io is at-most-once; so is uWS. CSR adds replay (opt-in, marked “experimental,” adapter constraints). AnyCable ships reliable streams by default: per-stream history, epoch + offset, restart-survivable with NATS or Redis. Under WiFi jitter (~2 s TCP drops every ~15 s) at 10K subscribers, both replay protocols deliver 100%. At-most-once protocols lose what landed during each offline window: about 15% of broadcasts for default Socket.io, 13% for uWS.
Replay protocols (CSR, AnyCable) deliver 100% under jitter. At-most-once protocols (default Socket.io, uWS) lose the broadcasts that landed during each offline window.
| Setup | Delivery | Lost of 1.2M | p95 | p99 replay tail |
|---|---|---|---|---|
| Socket.io default | 84.6% | 184,000 | 0.39 s | no replay |
| Socket.io + CSR | 100% | 0 | 1.97 s | 4.58 s |
| uWS topics | 87.0% | 154,000 | 0.72 s | no replay |
| AnyCable OSS | 100% | 0 | 4.10 s | 6.14 s |
| AnyCable Pro | 100% | 0 | 4.10 s | 6.15 s |
10K clients, 120 broadcasts, each client TCP-drops every ~15 s, ~2 s offline per event (set by each client library’s reconnect backoff; default Socket.io is floored to match via MIN_OFFLINE_MS). 1.2M expected deliveries. CSR resumes 99.7% of disconnects cleanly via its pid + offset protocol; AnyCable replays per-stream history on every reconnect. CSR’s tail is slightly shorter at p99 in this in-memory setup; AnyCable’s edge is elsewhere (deploy resilience, horizontal scaling, memory efficiency, native whispers). Methodology.
What this breaks
Lost messages cluster around network events, exactly when the user is watching. Default Socket.io and uWS lose about one in seven broadcasts under jitter: the user sees a half-finished chat thread, a stale presence indicator, a cursor that snaps. Replay protocols close that gap. The CSR-vs-AnyCable trade-off is no longer about delivery; it’s about what survives a redeploy and what scales beyond one node.
How efficient is it to scale?#
Your user base will grow and your realtime layer needs to grow with it. The question isn't how cheap is this today, it's when we 10× users, does the box cliff before we have time to react? The numbers below come from the single-node idle-connections load test on a 32 vCPU / 32 GB Railway box, 1M-connection target.
uWS holds the most connections per byte. AnyCable Pro is the lightest setup that includes built-in replay. Socket.io tops out around 120K.
| Setup | Connections held | RAM/conn | CPU peak % of 1 vCPU | Replay? |
|---|---|---|---|---|
| Socket.io (1M attempted) | 119,826 | ~52 KB | n/a | no |
| AnyCable OSS | 821,877 | 34 KB | 9.8% | yes |
| AnyCable Pro | 822,037 | 18 KB | 7.9% | yes |
| uWebSockets.js | 1,018,366 | 5.4 KB | n/a | no |
Ramp to 1M connections at 200/sec, hold 2 min. CPU peak measured on the WS server during the hold window via Railway metrics (CPU traces for uWS and Socket.io weren't captured during these runs). uWS is bare wire, no broker. AnyCable Pro is the lightest with replay built in; the extra KB/conn covers per-stream history and reconnect-resume.
What you don't have to build
Socket.io gives you WebSocket transport and rooms. uWebSockets.js gives you a faster transport. Both stop there. Everything else — reliability, presence, auth, clustering, monitoring — is weeks of engineering plus months of hardening, on a single Node event loop you also have to keep alive across deploys.
AnyCable ships these as primitives, hardened in production since 2017, in a process you don't have to restart when your app deploys.
Socket.io and uWS give you transport. AnyCable gives you the whole realtime framework: reliability, presence, auth, deploy resilience.
| Feature | Default Socket.io |
Socket.io + CSR |
uWeb- Sockets.js |
AnyCable OSS & Pro |
|---|---|---|---|---|
| Reliable delivery | No | Yes (opt-in) | No | Yes (default) |
| Replay latency p99 | lost | ~122 s | lost | ~6 s |
| Survives server restart | No | Redis Streams / Mongo | No | NATS / Redis |
| Multi-node setup | Redis pub/sub | Redis pub/sub incompat. | DIY (Redis) | Any broker |
| No external broker required | Redis required | Redis Streams required | DIY | Embedded NATS (Pro) |
| Deploy resilience | All drop | All drop | All drop | Connections survive |
| Graceful drain on restart | None | None | None | Configurable (slow-drain Pro) |
| Presence tracking | DIY | DIY | DIY | Built-in |
| Authentication | DIY | DIY | DIY | JWT, signed streams |
| Backend language | Node.js only | Node.js only | Node.js only | Any (HTTP API) |
| Binary wire format | Third-party plugin | Third-party plugin | DIY | msgpack, protobuf (Pro) |
| Monitoring | Admin UI | Admin UI | DIY | Prometheus & StatsD |
uWS fixes Socket.io's wire overhead. Everything beyond raw transport is still DIY. AnyCable OSS and Pro share every feature here; Pro differentiates on memory, embedded broker, and commercial support.
Run AnyCable in your stack#
Three places to start: read the code, run a working demo, or wire it into a serverless setup.
FAQ#
Does AnyCable replace Socket.io, or work alongside it?
io.emit(). Clients connect to AnyCable over its protocol (actioncable-v1-ext-json, via @anycable/core) instead of socket.io-client.What's the operational cost of running AnyCable?
How does AnyCable compare on performance?
1M-connection idle: uWS 1,018,366 / 5.45 GB. AnyCable Pro 822,037 / 14.8 GB (lightest with built-in replay). AnyCable OSS 821,877 / 28.3 GB. Socket.io caps around 100K (Node event loop saturates handshakes).
10K reconnecting clients under WiFi jitter: both replay protocols deliver 100%. CSR replay tail ~4.6 s p99; AnyCable ~6.1 s p99. At-most-once protocols lose proportional to the offline window: default Socket.io 84.6% delivered, uWS 87.0% delivered.
Broadcast throughput at 100 broadcasts/sec to 10K subs (40 parallel publishers): all five sustain 100% delivery. AnyCable Pro leads on both floor (4 ms p50) and tail (12 ms p99). uWS lands at 18 ms p50 / 177 ms p99. Socket.io family bottlenecks at 1.4–2.5 s p99 because the single Node event loop serializes incoming broadcasts from 40 concurrent publishers; AnyCable Go fans this work across cores.
Whispers (client-to-client at 2 Hz, 1K clients in 10 rooms): AnyCable delivers 100% at 2 ms p50 / 13 ms p99, leading p50 vs uWS topics (5 ms / 14 ms). Socket.io rooms saturates at the WS server's emit path and drops ~40% of whispers. Reproduce.
What about uWebSockets.js? It's faster than Socket.io.
Can I use AnyCable for streaming LLM responses?
What backends can use AnyCable?
Can I self-host AnyCable for HIPAA / SOC2 compliance?
Is AnyCable open source?
Is there a managed (hosted) AnyCable?
How does the cost compare to Pusher or Ably?
How do you handle authentication?
What happens if AnyCable itself restarts?
How do I run anycable-go in production?
anycable-go-pro for Pro). Minimum to run: docker run -p 8080:8080 anycable/anycable-go --broadcast_key=YOUR_SECRET. Configurable via flags or ANYCABLE_* env vars. Health endpoint at /health, Prometheus metrics at /metrics. Graceful drain on SIGTERM (configurable via --shutdown_timeout) so rolling deploys don't drop connections. Behind a load balancer with sticky sessions for multi-instance setups. Helm chart and Fly/Railway templates linked from the docs.Run AnyCable in your Node app
Self-host the Go binary alongside your Node service, or skip the deploy and start on the free managed tier.