Learn WebSocket - Horizontal Scaling with the Redis Adapter
Episode 16 of 34

Learn WebSocket - Horizontal Scaling with the Redis Adapter

This episode covers scaling Socket.IO to many servers: the cross-instance synchronization problem, the Redis pub-sub pattern, setting up the Redis adapter, other adapter alternatives, and architectural patterns for stateless servers.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Introduction

In episode 15 you placed several servers behind a load balancer. But there is a big problem: if a client on server A sends a message to a room, clients on server B will not receive it — because the room only exists in server A's memory.

Episode 16 covers the solution: the Redis adapter. Using the publish-subscribe pattern, every Socket.IO instance distributes events to all other instances, so rooms, namespaces, and broadcasts work as if it were a single server.

The Scaling Challenge

The Local State Problem

Socket.IO stores rooms and connections in each instance's memory. When the instances differ, this information cannot see each other.

JSThe cross-instance broadcast problem
// instance A
io.to("game-1").emit("status", "dimulai");
// klien di instance B tidak menerima apa pun

io.to("game-1").emit(...) on instance A only reaches connections in instance A's memory. Connections on instance B are blind to this event. State like this is called local state or in-memory state.

The Solution: Shared State

All instances must share information through a common store. For real-time broadcast, the medium is not an ordinary database — it needs something very fast with a publish-subscribe pattern. That is where Redis comes in.

The Pub/Sub Pattern

How Pub/Sub Works

Redis Pub/Sub is a one-publisher-many-subscribers pattern that is very simple.

JSBasic Redis Pub/Sub
const redis = require("redis");
 
const publisher = redis.createClient();
const subscriber = redis.createClient();
 
await subscriber.subscribe("saluran:game", (pesan) => {
  console.log("diterima:", pesan);
});
 
await publisher.publish("saluran:game", "event baru");

subscriber.subscribe(channel, fn) registers a callback for a specific channel, and publisher.publish(channel, pesan) distributes a message to every subscriber of that channel. Redis forwards messages as fast as possible without storing them.

Why Not an Ordinary Database

Databases read and write with queries — too slow for real-time events. Redis Pub/Sub sends messages directly to subscribers in microseconds. The trade-off: Pub/Sub messages are lost if there are no subscribers, so it only fits real-time events, not persistence.

The Socket.IO Redis Adapter

Installation and Configuration

The adapter changes Socket.IO so broadcasts are sent through Redis.

Install the Redis adapter
bun add @socket.io/redis-adapter redis
JSAttaching the adapter to the server
const { createClient } = require("redis");
const { createAdapter } = require("@socket.io/redis-adapter");
 
const pubClient = createClient({ url: "redis://redis:6379" });
const subClient = pubClient.duplicate();
 
await Promise.all([pubClient.connect(), subClient.connect()]);
 
const io = new Server(server, {
  adapter: createAdapter(pubClient, subClient),
});

createAdapter(pubClient, subClient) needs two Redis connections: one to publish, one to subscribe. With this adapter, io.to("game-1").emit(...) on any instance is distributed to every instance connected to the same Redis.

What Becomes Automatic

Once the adapter is attached, the following features work across instances without code changes:

  • Broadcast to all clients or to everyone except the sender.
  • Rooms recognized identically on all instances.
  • Namespaces stay isolated but distributed.
  • Presence broadcasts reach all instances.

Your application code does not change — the adapter works transparently under Socket.IO.

Cluster Mode

For a Redis cluster with many nodes, use the dedicated configuration.

JSAdapter for a Redis cluster
const { createCluster } = require("redis");
const { createClusterAdapter } = require("@socket.io/redis-adapter");
 
const redis = createCluster({
  rootNodes: [
    { url: "redis://redis-1:6379" },
    { url: "redis://redis-2:6379" },
  ],
});

createClusterAdapter(...) handles many Redis nodes at once. This is useful when the event volume already exceeds a single Redis capacity.

Adapter Alternatives

Not Just Redis

Several other adapters are available for different needs.

Available Socket.IO adapters
MongoDB   : memakai change stream, cocok saat Mongo sudah jadi basis utama
RabbitMQ  : memakai exchange fanout, bagus untuk integrasi broker yang ada
Kafka     : memakai topic, untuk event log berskala besar

Choose an adapter based on the infrastructure the team already has. Redis remains the most popular because it is light, fast, and easy to set up. All adapters implement the same interface, so switching does not change the application code.

Architectural Patterns

Stateless Servers

With the adapter, servers become nearly stateless: all connection state is shared through Redis.

JSAdding instances freely
const io = new Server(server, {
  adapter: createAdapter(pubClient, subClient),
});

The same code runs on however many instances. Adding an instance is just running the same container and registering it with the load balancer — no per-instance special configuration.

The Limits That Remain

A few things still need attention:

  • Authentication: token verification can be done on each instance independently.
  • Persistent presence: user status that must survive needs separate Redis storage, not just pub/sub.
  • Cross-instance rate limiting: counters must live in Redis, as covered in episode 13.
  • Offline message queues: queues need storage, not pub/sub.

Closing

Episode 16 opened the road to horizontal scale: with the Redis adapter, rooms and broadcasts are no longer limited to one instance, and servers can be added freely without changing code.

Key takeaways:

  • In-memory rooms are only known to their own instance, so broadcasts do not cross instances.
  • Redis Pub/Sub distributes events to all subscribers quickly.
  • The Redis adapter makes rooms, namespaces, and broadcasts work across instances.
  • Two Redis connections are needed: one to publish, one to subscribe.
  • Other adapters such as MongoDB, RabbitMQ, and Kafka are available as alternatives.
  • Presence, rate limits, and queues still need persistent storage in Redis.

In the next episode we cover performance optimization: connection and message optimization, Node.js clustering, event loop tuning, and network and client optimization.

Learn WebSocket - Horizontal Scaling with the Redis Adapter | Learn WebSocket