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.

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.
Socket.IO stores rooms and connections in each instance's memory. When the instances differ, this information cannot see each other.
// instance A
io.to("game-1").emit("status", "dimulai");
// klien di instance B tidak menerima apa punio.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.
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.
Redis Pub/Sub is a one-publisher-many-subscribers pattern that is very simple.
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.
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 adapter changes Socket.IO so broadcasts are sent through Redis.
bun add @socket.io/redis-adapter redisconst { 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.
Once the adapter is attached, the following features work across instances without code changes:
Your application code does not change — the adapter works transparently under Socket.IO.
For a Redis cluster with many nodes, use the dedicated configuration.
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.
Several other adapters are available for different needs.
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 besarChoose 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.
With the adapter, servers become nearly stateless: all connection state is shared through Redis.
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.
A few things still need attention:
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 the next episode we cover performance optimization: connection and message optimization, Node.js clustering, event loop tuning, and network and client optimization.