-
Notifications
You must be signed in to change notification settings - Fork 10.1k
-
Hi! 👋
I’m currently running an Express server with Socket.io, and now I want to add Redis to support horizontal scaling and keep multiple instances in sync.
"@socket.io/redis-streams-adapter": "^0.2.2",
"redis": "^4.7.0",
"socket.io": "^4.7.4",
SERVER CONSTRUCTOR
/ "server" is the Http server initiated in server.ts
constructor(server: HttpServer) {
ServerSocket.instance = this;
const socketOptions = {
serveClient: false,
pingInterval: 5000, // Server sends PING every 5 seconds
pingTimeout: 5000, // Client has 5 seconds to respond with PONG
cookie: false,
cors: {
origin: process.env.CORS_ORIGIN || '*'
},
connectionStateRecovery: {
maxDisconnectionDuration: DISCONNECT_TIMEOUT_MS,
skipMiddlewares: true,
},
adapter: createAdapter(redisClient)
};
// Create the Socket.IO server instance with all options
this.io = new Server(server, socketOptions);
this.users = {};
this.rooms = {
private: {},
public: {}
}
this.io.on('connect', this.StartListeners);
...
I’ve looked through the docs and found the basic setup, but I’m a bit confused about the best practices — especially around syncing custom state in servers.
For example, my Socket server maintains a custom this.rooms state. How would you typically keep that consistent across multiple servers? Is there a common pattern or example for this?
I’ve started pushing room metadata into Redis like this, so any server that’s out of sync can retrieve it:
private async saveRedisRoomMetadata(roomId: string, metadata: any) {
try {
await redisClient.set(
`${ROOM_META_PREFIX}${roomId}`,
JSON.stringify(metadata),
{ EX: ROOM_EXPIRY_SECONDS }
);
return true;
} catch (err) {
console.error(`Error saving Redis metadata for room ${roomId}:`, err);
return false;
}
}
...
// Add new room to LOCAL SERVER rooms object
this.rooms.private[newRoomId] = gameRoomInfo;
...
// UPDATE REDIS STATE, so servers can fetch missing infos from redis
const metadataSaved = await this.saveRedisRoomMetadata(newRoomId, gameRoomInfo);
If another server does not have the room data they could pull it
// Helper methods for Redis operations
private async getRedisRoomMetadata(roomId: string) {
try {
const json = await redisClient.get(`${ROOM_META_PREFIX}${roomId}`);
return json ? JSON.parse(json) : null;
} catch (err) {
console.error(`Error getting Redis metadata for room ${roomId}:`, err);
return null;
}
}
This kind of works, but it feels a bit hacky — I’m not sure if I’m approaching it the right way. It’s my first time building something like this, so I’d really appreciate any guidance! Especially if you could help paint the big picture in simple terms 🙏🏻
- I kept working on it trying to figure it out.. and I got one more scenario to share... what above is my first trial but wjat follows here is where I am so far.. in terms of understanding.:
"""
Client 1 joins a room and connects to Server A. On join, Server A updates its internal state, updates the Redis state, and emits a message to everyone in the room that a new user has joined. Perfect — Redis is up to date, Server A’s state is correct, and the UI reflects the change.
But what about Server B and Server C, where other clients might be connected? Sure, the UI may still look fine if it’s relying on the Redis-driven broadcasts, but the internal state on Servers B and C is now out of sync.
How should I handle this? Do I even need to fix it? What’s the recommended pattern here?
For instance, if a user connected to Server B or C needs to access the room state — won’t that be stale or incorrect? How is this usually tackled in horizontally scaled, real-time systems using Redis?
"""
Thanks in advance!
Beta Was this translation helpful? Give feedback.
All reactions
Replies: 1 comment 1 reply
-
Hi!
I think you need to have a single source of truth, which in your case is Redis (or any kind of database shared between your servers).
Redis set/get with pub/sub (in order to notify the other servers that something has changed) is a classic way to handle this use case.
Alternatively, you may also want to look into Redis streams, depending on your use case.
Beta Was this translation helpful? Give feedback.
All reactions
-
Ty @darrachequesne . So the state would live ONLY in redis, and the servers would read and write from there, is that correct?
I was also thinking, as another approach, would not be better, or at least a valid alternative , to have a bunch of containers:
server-a
, server-b
ans server-c
.
Each server only manages and create rooms starting with A, B or C (E.g: A-123rfgv, B-sf3w4r, etc)
Whenever a socket joins a room we let them connect only in the specific server holding that room, in that case we don't need redis or to sync servers.
What do you think about this :)? and any idea on how could be achieved?
Beta Was this translation helpful? Give feedback.