DEV Community

Kawan Idrees
Kawan Idrees

Posted on

Horizontal Scaling with Socket.IO

As your real‑time application grows, ensuring that every user receives up‑to‑date information becomes increasingly challenging. Horizontal scaling—adding more servers to distribute load—is a powerful strategy to meet this challenge. In this post, we'll explore how the Redis adapter for Socket.IO leverages Redis Pub/Sub to synchronize real‑time events across multiple server instances.

What Is Horizontal Scaling?
Horizontal scaling involves running multiple instances of your application rather than relying on a single server. This approach increases availability, balances load, and improves reliability. When you deploy a chat app or any real‑time service, you need a way for all instances to share events like messages or notifications so that every connected client remains in sync.

Image description

The Role of Socket.IO and Redis Pub/Sub
Socket.IO enables bi‑directional communication between clients and servers, making it a favorite for real‑time applications like chat systems. However, if you have multiple server instances, a message emitted on one server must be shared with all others.

Redis Pub/Sub comes to the rescue by acting as a messaging broker. The Redis adapter for Socket.IO uses Redis Pub/Sub under the hood:

Publish: When a user sends a message, the server instance publishes the event to a Redis channel.
Subscribe: All server instances subscribe to that channel. When an event is published, each instance receives it and broadcasts it to its connected clients.
This mechanism ensures that every client—regardless of which server instance it is connected to—gets the same updates in real time.

How to Set It Up
Below is a concise code example that demonstrates how to integrate the Redis adapter with Socket.IO:

const express = require("express");
const http = require("http");
const socketIO = require("socket.io");
const { createClient } = require("redis");
const { createAdapter } = require("@socket.io/redis-adapter");

const app = express();
const server = http.createServer(app);
const io = socketIO(server, {
  cors: { origin: '*', methods: ["GET", "POST"] }
});

// Set up Redis clients for Pub/Sub
(async () => {
  const pubClient = createClient({ url: "redis://localhost:6379" });
  const subClient = pubClient.duplicate();

  await pubClient.connect();
  await subClient.connect();

  // Attach the Redis adapter to Socket.IO
  io.adapter(createAdapter(pubClient, subClient));
  console.log("Socket.IO is now using the Redis adapter for horizontal scaling.");
})();

// Define basic Socket.IO events
io.on("connection", (socket) => {
  console.log("Client connected:", socket.id);

  socket.on("joinRoom", ({ roomId }) => {
    socket.join(`room_${roomId}`);
    console.log(`Socket ${socket.id} joined room_${roomId}`);
  });

  socket.on("chatMessage", (msg) => {
    // Broadcast the message to all clients in the room
    io.to(`room_${msg.roomId}`).emit("chatMessage", msg);
  });

  socket.on("disconnect", () => {
    console.log("Client disconnected:", socket.id);
  });
});

// Start the server on a designated port
server.listen(4000, () => {
  console.log("Server listening on port 4000");
});


Enter fullscreen mode Exit fullscreen mode

How It Works:
Redis Clients:
Two Redis clients are created—one for publishing events and a duplicate for subscribing. They connect to your Redis server.

Socket.IO Adapter:
The Redis adapter is attached to the Socket.IO instance. This enables communication between multiple server instances via Redis Pub/Sub.

Real‑Time Events:
When a client sends a message (e.g., with the "chatMessage" event), the event is broadcasted to the appropriate room. The Redis adapter ensures that if multiple server instances are running, all will receive and propagate the event.

Production Considerations
Multiple Instances:
In production, you'll run several instances (e.g., on different ports or containers). A load balancer or reverse proxy (like Nginx) will distribute incoming connections across these instances.

Unified Domain:
Clients connect to a single domain (e.g., chat.example.com), and the load balancer routes the requests appropriately, while the Redis adapter handles real‑time synchronization behind the scenes.

Scalability & Reliability:
With Redis Pub/Sub, your horizontally scaled architecture remains consistent. Every event is propagated to all server instances, ensuring that no client misses an update.

Conclusion
Integrating the Redis adapter with Socket.IO is a proven strategy for horizontal scaling in real‑time applications. By leveraging Redis Pub/Sub, you can distribute events across multiple server instances, ensuring that every client stays in sync—even as your user base grows. This setup provides a robust foundation for building scalable, high‑performance chat applications.

Top comments (0)