Aplicações Node.js executam JavaScript em uma thread principal por processo. Isso é excelente para operações de rede e entrada e saída, mas significa que um único processo não aproveita automaticamente todos os núcleos da máquina. O módulo Cluster no Node.js permite iniciar vários processos trabalhadores que compartilham a mesma porta de servidor, distribuindo conexões entre eles.
Cluster não transforma uma função lenta em código paralelo dentro do mesmo processo. Cada worker possui sua própria memória, seu próprio event loop e sua própria instância da aplicação. O ganho aparece quando várias requisições independentes podem ser atendidas por processos diferentes. Por isso, o módulo é útil para servidores HTTP, gateways e serviços que precisam usar mais de um núcleo sem depender imediatamente de um gerenciador externo.
Neste guia, você aprenderá a criar o processo primário, iniciar workers, compartilhar uma porta, trocar mensagens, reiniciar processos com falha, realizar shutdown gradual, evitar sessões em memória e decidir quando usar Cluster, Worker Threads, containers ou um process manager.
O que é o módulo Cluster?
O módulo node:cluster cria processos filhos por meio de child_process.fork() e oferece uma camada para distribuir conexões de servidor. O processo primário coordena os workers, enquanto cada worker executa o código da aplicação.
A documentação oficial do módulo Cluster descreve eventos, políticas de agendamento e métodos disponíveis. Para revisar a arquitetura do runtime, consulte Event Loop no Node.js e Objeto Process no Node.js.
Quando o Cluster ajuda?
Um servidor com oito núcleos pode executar oito workers, permitindo que requisições simultâneas sejam processadas em processos distintos. Isso reduz o impacto de uma requisição que ocupa temporariamente um worker e aumenta a capacidade total para cargas paralelizáveis.
Cluster costuma ajudar em:
- servidores HTTP e HTTPS com muitas conexões;
- gateways e APIs que fazem trabalho moderado por requisição;
- serviços legados que precisam usar vários núcleos;
- aplicações executadas diretamente em uma única máquina;
- cenários em que o processo primário gerencia reinício dos workers.
Ele não substitui testes de carga. O número ideal de workers depende de CPU, memória, limites do container, banco de dados e serviços externos.
Exemplo básico
const cluster = require('node:cluster');
const http = require('node:http');
const os = require('node:os');
if (cluster.isPrimary) {
const workerCount = os.availableParallelism();
console.log(`Primário ${process.pid}`);
for (let index = 0; index < workerCount; index += 1) {
cluster.fork();
}
} else {
const server = http.createServer((req, res) => {
res.writeHead(200, { 'content-type': 'application/json' });
res.end(JSON.stringify({
worker: process.pid,
path: req.url
}));
});
server.listen(3000, () => {
console.log(`Worker ${process.pid} ouvindo`);
});
}Todos os workers chamam listen(3000), mas o módulo coordena o compartilhamento da porta. As requisições podem ser atendidas por PIDs diferentes.
Quantidade de workers
os.availableParallelism() é um ponto inicial melhor que os.cpus().length, especialmente em ambientes que limitam CPU. Mesmo assim, criar um worker por núcleo não é uma regra absoluta.
const maximumWorkers = Number(process.env.WEB_WORKERS || 4);
const workerCount = Math.max(
1,
Math.min(os.availableParallelism(), maximumWorkers)
);Defina um limite configurável. Cada worker carrega dependências, conexões, caches e heap próprios. Dezesseis processos podem consumir muito mais memória que quatro.
Memória não é compartilhada
Variáveis globais, mapas e caches em memória pertencem somente ao worker atual:
const sessions = new Map();Se um login for armazenado no worker A, uma requisição posterior enviada ao worker B não encontrará a sessão. Use Redis, banco de dados ou outro armazenamento compartilhado. O guia de Redis com Node.js apresenta estratégias para cache e estado distribuído.
Conexões com banco de dados
Cada worker cria seu próprio pool. Se quatro workers abrirem vinte conexões, o total potencial será oitenta. Considere o limite global do banco:
const workers = 4;
const globalConnectionBudget = 40;
const maxPerWorker = Math.max(
1,
Math.floor(globalConnectionBudget / workers)
);Monitore filas, tempo de aquisição e erros de limite. O artigo sobre Pool PostgreSQL no Node.js explica dimensionamento e liberação correta.
Eventos importantes
O processo primário pode acompanhar a criação, conexão, desconexão e saída dos workers:
cluster.on('online', worker => {
console.log(`Worker ${worker.process.pid} online`);
});
cluster.on('exit', (worker, code, signal) => {
console.error({
pid: worker.process.pid,
code,
signal
});
});Esses eventos ajudam a registrar estado e alimentar métricas. Não exponha detalhes internos em respostas públicas.
Reiniciando um worker
cluster.on('exit', (worker, code) => {
if (!shuttingDown && code !== 0) {
cluster.fork();
}
});Reiniciar indefinidamente pode criar um ciclo de falha quando existe configuração inválida, credencial ausente ou bug determinístico. Aplique atraso, limite e alerta:
let restartCount = 0;
function scheduleReplacement() {
restartCount += 1;
const delay = Math.min(30000, 1000 * restartCount);
setTimeout(() => {
cluster.fork();
}, delay).unref();
}Reinícios frequentes devem ser tratados como incidente, não como comportamento normal.
Troca de mensagens
O processo primário e os workers podem usar IPC:
if (cluster.isPrimary) {
const worker = cluster.fork();
worker.send({
type: 'configuration-version',
value: 'v3'
});
} else {
process.on('message', message => {
if (message?.type === 'configuration-version') {
console.log(message.value);
}
});
}Mensagens precisam ser pequenas e validadas. IPC não é uma fila persistente; se o processo morrer, mensagens em trânsito podem ser perdidas.
Balanceamento de conexões
O comportamento depende da plataforma e da política de agendamento. Em uma estratégia comum, o processo primário aceita conexões e as distribui. Em outra, workers aceitam conexões diretamente com ajuda do sistema operacional.
Não presuma distribuição perfeitamente uniforme. Conexões persistentes, WebSockets e diferenças de duração podem deixar alguns workers mais ocupados. Colete métricas por PID.
WebSockets e conexões longas
Depois que uma conexão é atribuída, ela normalmente permanece no mesmo worker. Mensagens entre clientes conectados a workers diferentes precisam de um canal compartilhado, como Redis Pub/Sub ou uma fila.
O artigo de WebSocket com Node.js mostra a base de uma conexão em tempo real. Em Cluster, acrescente coordenação externa para broadcasts e presença.
Sticky sessions
Aplicações que guardam sessão local podem tentar direcionar o mesmo usuário ao mesmo worker. Isso é frágil: um reinício perde o estado, a distribuição pode ficar desigual e a escala horizontal se torna difícil. O melhor caminho é remover estado de sessão do processo.
Quando sticky sessions forem inevitáveis, configure-as no balanceador e mantenha um armazenamento compartilhado como fonte de verdade.
Shutdown gradual
Durante deploy ou encerramento, o primário deve parar de aceitar trabalho e permitir que os workers concluam requisições:
let shuttingDown = false;
async function shutdown() {
if (shuttingDown) return;
shuttingDown = true;
for (const worker of Object.values(cluster.workers)) {
worker?.send({ type: 'shutdown' });
worker?.disconnect();
}
}
process.on('SIGTERM', shutdown);
process.on('SIGINT', shutdown);No worker:
process.on('message', message => {
if (message?.type !== 'shutdown') return;
server.close(async error => {
await closeDatabase();
process.exitCode = error ? 1 : 0;
});
});Defina um prazo máximo para conexões que nunca terminam. Veja Graceful Shutdown no Node.js para uma estratégia completa.
Readiness por worker
Um worker não deve receber tráfego antes de concluir configuração e conexões. Ele pode avisar o primário:
await initializeApplication();
process.send?.({ type: 'ready' });Em ambientes com balanceador externo, exponha readiness que considere dependências. O conteúdo de Health Checks no Node.js diferencia liveness e readiness.
Cluster versus Worker Threads
Cluster cria processos independentes e é adequado para várias instâncias de servidor. Worker Threads criam threads no mesmo processo e podem compartilhar memória, sendo mais apropriadas para cálculos intensivos.
- Cluster: isolamento por processo, memória separada, servidores replicados.
- Worker Threads: paralelismo de CPU, comunicação por mensagens e memória compartilhável.
Leia Worker Threads no Node.js para comparar pools e tarefas de CPU.
Cluster versus containers
Em Kubernetes ou outra plataforma, muitas equipes executam um processo Node.js por container e deixam o orquestrador replicar pods. Isso simplifica métricas, limites e reinícios. Usar vários workers dentro de cada container pode continuar válido, mas aumenta a complexidade de dimensionamento.
Evite multiplicação involuntária: cinco pods com oito workers criam quarenta processos, pools e consumidores.
Logs e observabilidade
Inclua PID, worker ID e identificador de instância nos logs:
logger.info('Requisição concluída', {
pid: process.pid,
workerId: cluster.worker?.id,
requestId,
durationMs
});Agregue métricas por serviço, mas mantenha dimensões suficientes para detectar um worker problemático. Evite usar PID como label permanente em métricas de alta cardinalidade.
Falhas fatais
Depois de uma exceção não tratada, registre o mínimo necessário, pare de aceitar tráfego e encerre o worker. O primário ou supervisor pode substituí-lo. Continuar processando após um estado desconhecido pode corromper respostas.
Testes de carga
Compare um, dois, quatro e mais workers sob carga representativa. Observe throughput, p95, p99, CPU, memória, conexões de banco e event loop lag. Aumentar workers pode piorar desempenho quando o gargalo está no banco ou quando todos disputam o mesmo núcleo limitado.
A documentação de profiling do Node.js ajuda a identificar gargalos antes de aumentar processos.
Erros comuns
- Guardar sessão em memória: requisições chegam a workers diferentes.
- Criar workers demais: memória e pools crescem rapidamente.
- Reiniciar sem limite: um erro permanente cria loop de falhas.
- Ignorar shutdown: deploy interrompe requisições.
- Assumir balanceamento uniforme: conexões longas desequilibram processos.
- Confundir Cluster com paralelismo interno: cada worker continua tendo uma thread JavaScript principal.
- Multiplicar workers em containers: a capacidade total fica maior que o planejado.
Boas práticas para produção
- Comece com poucos workers e meça.
- Use armazenamento compartilhado para estado.
- Divida o orçamento de conexões entre workers.
- Implemente restart com atraso e limite.
- Trate SIGTERM e SIGINT.
- Monitore cada worker e o serviço agregado.
- Use readiness antes de receber tráfego.
- Planeje conexões persistentes e broadcasts.
- Compare com replicação por container.
- Teste falhas, reinícios e deploys.
Conclusão
O Cluster no Node.js permite executar várias instâncias da aplicação e compartilhar uma porta, aproveitando múltiplos núcleos com isolamento por processo. Ele pode aumentar capacidade de servidores quando a carga é distribuída e os recursos externos suportam a concorrência adicional.
O resultado depende de arquitetura: estado deve ser externo, pools precisam de orçamento global, reinícios exigem limites e shutdown deve concluir trabalho em andamento. Com métricas e testes de carga, Cluster pode ser uma solução prática; sem esses cuidados, apenas multiplica processos e gargalos.




