1

Arquitetando Comunicação em Tempo Real em Escala: Redis Pub/Sub, Load Balancing e Milhares de Conexões Simultâneas

O problema que aparece quando a segunda instância sobe

Uma única instância de Node.js com a biblioteca ws segura 10 mil conexões WebSocket sem esforço. O event loop do V8, combinado com a libuv gerenciando I/O não-bloqueante, mantém o consumo de memória por conexão abaixo de 50KB. O problema nunca é a primeira instância. É a segunda.

Quando o load balancer distribui conexões entre duas ou mais instâncias, um usuário conectado à instância A não recebe mensagens publicadas na instância B. Cada processo Node.js mantém seu próprio mapa de conexões em memória. Sem um canal externo de coordenação, as instâncias são ilhas.

A solução padrão da indústria é usar Redis Pub/Sub como backbone de distribuição. Cada instância subscreve a canais Redis e retransmite mensagens para seus clientes locais. É simples, mas os detalhes de implementação determinam se o sistema aguenta 500 ou 50 mil conexões.

Arquitetura geral: o que conecta com o quê

O fluxo completo funciona assim:

Cliente WebSocket
       │
       ▼
┌─────────────────┐
│  Load Balancer   │  (Nginx / ALB com sticky sessions)
│  (Layer 7)       │
└────┬───────┬─────┘
     │       │
     ▼       ▼
┌────────┐ ┌────────┐
│ Node A │ │ Node B │   (cada um com servidor WebSocket)
│  ws    │ │  ws    │
└───┬────┘ └───┬────┘
    │          │
    ▼          ▼
┌──────────────────┐
│   Redis Pub/Sub   │   (canal de coordenação entre instâncias)
└──────────────────┘

Quando o Node A recebe uma mensagem de um cliente que pertence a uma sala, ele publica no Redis. O Node B, subscrito ao mesmo canal, recebe e retransmite para os clientes locais daquela sala. O Redis não armazena mensagens: é fire-and-forget. Se uma instância estiver offline no momento da publicação, perde a mensagem. Isso é intencional para tempo real, onde latência importa mais que garantia de entrega. Se você precisa de entrega garantida, o caminho é mensageria com BullMQ e Redis.

Servidor WebSocket com Redis Pub/Sub

O código abaixo usa ws (não Socket.IO) porque o overhead de protocolo é menor e o controle sobre a conexão é total. Socket.IO adiciona fallback para polling, reconexão automática e namespaces, mas para cenários onde o cliente é controlado (SPA própria, app mobile), ws puro resolve com menos abstração.

// src/server.ts
import { WebSocketServer, WebSocket } from "ws";
import { createClient } from "redis";
import { createServer } from "http";

const PORT = Number(process.env.PORT) || 3000;
const REDIS_URL = process.env.REDIS_URL || "redis://localhost:6379";

// Mapa local de salas: cada instância só conhece seus próprios clientes
const rooms = new Map<string, Set<WebSocket>>();

const httpServer = createServer();
const wss = new WebSocketServer({ server: httpServer });

// Dois clientes Redis separados: um para publish, outro para subscribe.
// O cliente em modo subscriber não aceita comandos regulares (GET, SET, etc).
const publisher = createClient({ url: REDIS_URL });
const subscriber = createClient({ url: REDIS_URL });

async function bootstrap() {
  await publisher.connect();
  await subscriber.connect();

  // Subscreve a um padrão de canais para todas as salas
  await subscriber.pSubscribe("room:*", (message, channel) => {
    const roomId = channel.replace("room:", "");
    const clients = rooms.get(roomId);
    if (!clients) return;

    for (const client of clients) {
      if (client.readyState === WebSocket.OPEN) {
        client.send(message);
      }
    }
  });

  wss.on("connection", (ws, req) => {
    // Extrai roomId da query string: ws://host:3000/?room=abc123
    const url = new URL(req.url || "/", `http://${req.headers.host}`);
    const roomId = url.searchParams.get("room");

    if (!roomId) {
      ws.close(4000, "room query param required");
      return;
    }

    // Adiciona ao mapa local
    if (!rooms.has(roomId)) {
      rooms.set(roomId, new Set());
    }
    rooms.get(roomId)!.add(ws);

---

Leia o artigo completo em [https://www.vivodecodigo.com.br/backend/websockets-escala-redis-pubsub-load-balancing-conexoes-simultaneas](https://www.vivodecodigo.com.br/backend/websockets-escala-redis-pubsub-load-balancing-conexoes-simultaneas)
Carregando publicação patrocinada...