8 Topics
Chapter•50 Minutes

Scale Socket.IO Using Redis Adapter

Understand why the default adapter is not suitable for cross-instance broadcasting, integrate Redis Pub/Sub, and prepare Socket.IO for horizontal scaling.

Why Do We Need Socket.IO Scaling?

So far, our application has used a single Node.js server instance.

All connected sockets are maintained within that server process.

Consider what happens when you deploy two Node.js instances behind a load balancer.

  • User A connects to Server 1.
  • User B connects to Server 2.
  • User A sends a message.
  • Server 1 broadcasts the message using its local Socket.IO instance.

Without a shared adapter, Server 2 does not automatically receive the broadcast.

This happens because the default Socket.IO adapter stores room and socket communication state locally within each server process.

To solve cross-instance communication, we need an adapter that distributes broadcasts between server instances.

Problem Without Shared Adapter
Key Takeaways
  • The default adapter is local to a server instance.

  • Multiple server instances do not automatically share broadcasts.

  • A distributed adapter enables cross-instance event communication.

Understand the Redis Adapter

The Socket.IO Redis adapter uses Redis Pub/Sub to distribute events between multiple Socket.IO server instances.

Each server connects to Redis using two clients:

  • Publisher client: Publishes events to Redis channels.
  • Subscriber client: Receives events published by other server instances.

When one server broadcasts an event to a room, the Redis adapter distributes the broadcast to other Socket.IO servers.

Those servers then deliver the event to their locally connected clients.

This allows users connected to different application instances to communicate through the same logical Socket.IO rooms.

Redis Adapter Components

Publisher

Sends messages

Publishes Socket.IO adapter messages to Redis.

Subscriber

Receives messages

Subscribes to Redis Pub/Sub channels.

Redis

Message distribution

Distributes adapter messages between application instances.

Socket.IO Adapter

Cross-instance broadcasts

Connects Socket.IO broadcasting behavior to Redis Pub/Sub.

Socket.IO Redis Adapter Architecture
Key Takeaways
  • Redis Pub/Sub distributes events between server instances.

  • Publisher and subscriber clients perform different roles.

  • The Redis adapter extends broadcasting across instances.

Install Redis Adapter Dependencies

Install the Socket.IO Redis adapter and the official Redis Node.js client.

We will use the redis package to connect to Redis.

The adapter package integrates Socket.IO with Redis Pub/Sub.

Install Redis Dependencies
npm install @socket.io/redis-adapter redis

Installs the official Socket.IO Redis adapter and the Redis Node.js client.

Key Takeaways
  • The Redis adapter is a separate package.

  • The Redis package provides the Node.js client.

  • Both dependencies are required for this integration.

Start a Redis Server

Before connecting Node.js to Redis, ensure that a Redis server is running.

You can use a local Redis installation or a managed Redis service.

For local development, Docker provides a convenient way to start Redis.

Run the following command:

docker run -d --name socket-chat-redis -p 6379:6379 redis:7-alpine

Verify that the container is running:

docker ps

Test the Redis connection:

docker exec -it socket-chat-redis redis-cli ping

Expected response:

PONG

The example uses a local development container without authentication. Never expose this Redis instance publicly.

Redis Security

This command is intended only for local development. Production Redis should use authentication, TLS where appropriate, private networking, and firewall restrictions.

Start Redis with Docker
docker run -d --name socket-chat-redis -p 6379:6379 redis:7-alpine

Starts Redis in a Docker container and maps port 6379 to the host machine.

Key Takeaways
  • Redis must be running before the adapter connects.

  • Docker simplifies local Redis setup.

  • Never expose an unsecured Redis instance to untrusted networks.

Configure Redis Publisher and Subscriber

Create src/config/redis.js.

We need two Redis connections because the adapter uses Pub/Sub.

The publisher client sends messages, while the subscriber client listens for messages.

The subscriber is created by duplicating the publisher client configuration.

src/config/redis.js
import { createClient } from 'redis';

const redisUrl = process.env.REDIS_URL || 'redis://localhost:6379';

export const pubClient = createClient({
  url: redisUrl
});

export const subClient = pubClient.duplicate();

pubClient.on('error', (error) => {
  console.error('Redis publisher error:', error);
});

subClient.on('error', (error) => {
  console.error('Redis subscriber error:', error);
});

export async function connectRedis() {
  await Promise.all([
    pubClient.connect(),
    subClient.connect()
  ]);

  console.log('Redis publisher and subscriber connected');
}

Creates separate publisher and subscriber clients, registers error listeners, and exposes a connection function.

Key Takeaways
  • The Redis adapter requires publisher and subscriber connections.

  • The subscriber can be created using duplicate().

  • Wait for Redis connections before starting the application.

Attach Redis Adapter to Socket.IO

Update src/config/socket.js to initialize the Redis adapter.

The createAdapter() function receives the publisher and subscriber clients.

The resulting adapter is configured through the Socket.IO server's adapter option.

Updated src/config/socket.js
import { Server } from 'socket.io';
import { createAdapter } from '@socket.io/redis-adapter';
import { pubClient, subClient } from './redis.js';

export function initializeSocket(httpServer) {
  const io = new Server(httpServer, {
    adapter: createAdapter(pubClient, subClient)
  });

  return io;
}

Configures Socket.IO to use Redis Pub/Sub for cross-instance broadcasting instead of relying only on its default in-memory adapter.

Key Takeaways
  • The adapter is configured during Socket.IO initialization.

  • Redis publisher and subscriber clients are shared with the adapter.

  • All instances must use compatible adapter configuration.

Start the Application After Redis Connects

The application must not start accepting connections before Redis is ready when the Redis adapter is required for correct cross-instance broadcasting.

Update src/server.js to connect Redis before starting the HTTP server.

This prevents clients from connecting to an instance that has not completed adapter initialization.

Fail-Fast Startup

If Redis is a required dependency for cross-instance messaging, do not silently start in-memory mode when Redis is unavailable. Either fail startup or implement an explicitly designed degraded mode.

Updated src/server.js
import 'dotenv/config';
import express from 'express';
import { createServer } from 'node:http';
import { initializeSocket } from './config/socket.js';
import { connectRedis } from './config/redis.js';
import { registerChatEvents } from './events/chat.events.js';

const app = express();
app.use(express.json());
app.use(express.static('public'));

const httpServer = createServer(app);

async function startServer() {
  await connectRedis();

  const io = initializeSocket(httpServer);

  registerChatEvents(io);

  const PORT = process.env.PORT || 3000;

  httpServer.listen(PORT, () => {
    console.log(`Server running on port ${PORT}`);
  });
}

startServer().catch((error) => {
  console.error('Application startup failed:', error);
  process.exit(1);
});

Connects Redis first, initializes Socket.IO with the Redis adapter, registers events, and finally starts the HTTP server.

Key Takeaways
  • Initialize Redis before Socket.IO.

  • Register event handlers before accepting connections.

  • Use fail-fast startup when distributed messaging is required.

Understand Redis Adapter Limitations

The Redis adapter enables cross-instance broadcasting, but it does not solve every scaling problem.

Important considerations:

1. Sticky sessions

When HTTP long-polling is enabled and multiple Socket.IO servers are used behind a load balancer, sticky sessions are still required so requests belonging to one Engine.IO session reach the correct server.

2. Connection state recovery

The standard Redis adapter does not support Socket.IO connection state recovery. Do not assume that missed packets can always be restored after disconnection.

3. Redis availability

A Redis outage can affect cross-instance communication.

4. Local socket management

Each server still manages its own local sockets. Redis distributes adapter messages between instances.

5. Redis security

Protect Redis with authentication, ACLs, TLS where appropriate, and network restrictions.

For Redis 7.0 and newer, Socket.IO also provides a sharded adapter designed for Redis sharded Pub/Sub.

Load Balancer Configuration

The Redis adapter handles inter-server event distribution. It does not remove the need for sticky sessions when using HTTP long-polling across multiple Socket.IO servers.

Socket.IO Scaling Options

FeatureDefault AdapterRedis AdapterRedis Sharded Adapter
Single instanceYesYesYes
Cross-instance broadcastsNoYesYes
Redis requiredNoYesYes
Redis 7 sharded Pub/SubNoNoYes
Connection state recoverySupportedNot supportedDepends on implementation
Key Takeaways
  • Redis adapter distributes events between instances.

  • Sticky sessions may still be necessary.

  • The standard Redis adapter does not support connection state recovery.

  • Redis infrastructure must be secured and monitored.