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.

Latency Is it fast enough? p50 / p99 roundtrip @ 10k connections
Socket.io + CSR
3 / 7 ms
uWS
2 / 10 ms
AnyCable Pro
3 / 11 ms
Reliability under jitter Does it survive bad networks? % delivered under WiFi drops
Socket.io default
85%
uWS
87%
AnyCable
100%
Reconnect avalanche Does it survive a deploy? max reconnect storm absorbed before OOM
Socket.io + CSR
fails > 10K
uWS
9 s @ 20K
AnyCable
0 s, no restart
Footprint What does it cost to run? RAM per idle connection
Socket.io caps ~120k/node
~52 KB
AnyCable Pro replay included
18 KB
uWS bare wire
5 KB
AnyCable delivers 100% under jitter, holds connections across app deploys, leads broadcast throughput at scale (10× lower p99 than uWS under parallel publishers), and saves 2.5× RAM vs Socket.io. Latency stays in line with the alternatives (3 ms p50, 11 ms p99 at 10K subs).

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/publish API.
  • 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.

Embedded mode (tested) vs microservice mode (target) EMBEDDED NODE 1 App + Socket.io ~5,000 WS connections NODE 2 App + Socket.io ~5,000 WS connections Redis PUB/SUB DEPLOY → RESTART NODE every WS drops STANDALONE SERVICE NODE 1 · APP App only Auth, routes, DB HTTP NODE 2 · SOCKET.IO WS only 10,000 WS connections DEPLOY → NODE 1 RESTARTS all WS survive
Embedded mode vs standalone service. Embedded leads to WS freeze every time main app deploys.

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 default3 / 7 ms3 / 7 ms
Socket.io + CSR3 / 7 ms3 / 7 ms
uWS topics2 / 7 ms2 / 10 ms
AnyCable OSS3 / 13 ms3 / 15 ms
AnyCable Pro3 / 10 ms3 / 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 default100%155 ms1.41 s
Socket.io + CSR100%127 ms2.54 s
uWS100%18 ms177 ms
AnyCable OSS100%4 ms15 ms
AnyCable Pro100%4 ms12 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.

This is where AnyCable competes with Liveblocks, Yjs providers, and PartyKit, not just with WS libraries. If your product has live cursors or shared selections, this row matters more than raw broadcast throughput.

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 roomsEmulated61%1.39 s8.90 s
uWS topicsEmulated100%5 ms14 ms
AnyCable OSSNative100%2 ms13 ms
AnyCable ProNative100%2 ms13 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.

Live chat & notifications Messages disappear during network blips. Users see incomplete conversations, no indication anything is missing.
LLM / AI streaming Responses stream word by word. A mid-sentence disconnect leaves output truncated or garbled — chunks lost, no recovery.
Real-time collaboration & presence Lost operations make documents diverge silently. Cursors jump, presence flickers; users lose trust in what they see.
Dashboards & monitoring Permanent gaps in time-series data. The 200 ms blip during a traffic spike is the data point you needed.

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 KBn/ano
AnyCable OSS821,87734 KB9.8%yes
AnyCable Pro822,03718 KB7.9%yes
uWebSockets.js1,018,3665.4 KBn/ano

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 deliveryNoYes (opt-in)NoYes (default)
Replay latency p99lost~122 slost~6 s
Survives server restartNoRedis Streams / MongoNoNATS / Redis
Multi-node setupRedis pub/subRedis pub/sub incompat.DIY (Redis)Any broker
No external broker requiredRedis requiredRedis Streams requiredDIYEmbedded NATS (Pro)
Deploy resilienceAll dropAll dropAll dropConnections survive
Graceful drain on restartNoneNoneNoneConfigurable (slow-drain Pro)
Presence trackingDIYDIYDIYBuilt-in
AuthenticationDIYDIYDIYJWT, signed streams
Backend languageNode.js onlyNode.js onlyNode.js onlyAny (HTTP API)
Binary wire formatThird-party pluginThird-party pluginDIYmsgpack, protobuf (Pro)
MonitoringAdmin UIAdmin UIDIYPrometheus & 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.

FAQ#

Does AnyCable replace Socket.io, or work alongside it?
It replaces Socket.io. Your Node.js app broadcasts to AnyCable via HTTP instead of calling 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?
One extra process: a single Go binary (or Docker container). It's stateless for broadcasts and scales horizontally. No database, no custom build. Defaults work for most apps; Redis or NATS can be added for multi-node pub/sub.
How does AnyCable compare on performance?
Four workloads on the same 32 vCPU / 32 GB Railway box, every option as a standalone WS service.

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.
uWS is genuinely faster on the wire, we measured it. At 1M idle connections it uses 5.45 GB (5.4 KB per conn) vs AnyCable Pro's 14.8 GB at 822K (18 KB per conn). The “10× faster than Socket.io” claim is honest. But uWS is a WebSocket library, not a realtime framework: no replay buffer, no broker, no separate-process deploy resilience. Under jitter it delivers 87.0% (vs default Socket.io's 84.6%); both lose the broadcasts that land during each offline window because both are at-most-once. AnyCable matches uWS on whispers p99 (13 vs 14 ms) and beats it at p50 (2 vs 5 ms), while also delivering 100% under jitter and surviving app deploys. uWS solves “Socket.io's wire is too heavy.” It doesn't solve “we lose messages during disruption” or “every deploy hits our users.”
Can I use AnyCable for streaming LLM responses?
Yes. Message ordering and durable streams are exactly what token streaming needs. If a user briefly disconnects mid-response, AnyCable replays the missed chunks in order from the last offset they saw. Out-of-order arrivals (which corrupt LLM output) don't happen because each stream is monotonically ordered. The Next.js + Twilio + OpenAI demo linked above shows this end-to-end.
What backends can use AnyCable?
Any. Anycable-go is a standalone server with an HTTP broadcast API; your app pushes messages over plain HTTP. Node.js, Laravel, FastAPI, Django, Go, Elixir, anything that can issue an HTTP POST works. There is no language SDK requirement.
Can I self-host AnyCable for HIPAA / SOC2 compliance?
Yes. anycable-go runs entirely on your infrastructure: no data flows through third-party servers, no shared cloud. Multiple healthcare and fintech teams self-host AnyCable for this reason: real-time features (patient-doctor chat, live data feeds) without data ever leaving controlled environments. For HIPAA specifically, deploy AnyCable inside your existing PHI boundary; it's just another service on your network.
Is AnyCable open source?
Yes, MIT-licensed since 2017. anycable/anycable on GitHub. A commercial Pro tier adds broker features (long-term history, embedded NATS) and priority support, free for small deployments.
Is there a managed (hosted) AnyCable?
Yes. AnyCable+ is the managed tier: zero ops, free for early users, paid plans for scale. Same protocol and feature surface as self-hosted, so you can switch in either direction without changing app code.
How does the cost compare to Pusher or Ably?
Pusher and Ably charge per concurrent connection, so costs scale linearly with your user base. AnyCable Pro (self-hosted, unlimited connections and instances) is a flat-rate annual license at $1,490/yr with a 2-month free trial. AnyCable+ Managed is free for early users. At 10K+ concurrent connections, the flat-rate or self-hosted options typically save thousands per month versus per-connection pricing.
How do you handle authentication?
Signed JWT tokens or signed stream names, issued by your application. AnyCable verifies the signature on connect and channel subscribe, so your app stays the source of truth for identity without being on the hot path.
What happens if AnyCable itself restarts?
Less often than app deploys (you don't ship anycable-go on every code change), but it does happen: version upgrades, config changes, host reboots. The behavior depends on the broker. With an external broker (NATS JetStream or Redis Streams), replay state survives the restart, so clients reconnect and resume from the last offset they saw. With the default in-memory broker (or AnyCable Pro's embedded broker), replay state is lost on restart and clients fall back to “live from now,” meaning broadcasts during the brief restart window can be missed. For production, run multiple anycable-go instances behind a load balancer and a shared broker; restart one at a time during upgrades and clients seamlessly reconnect to the others.
How do I run anycable-go in production?
One Go binary. Docker images at hub.docker.com/r/anycable/anycable-go (and 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.