O módulo node:cluster permite criar vários processos Node.js que compartilham uma porta de servidor. Cada worker possui seu próprio event loop, heap e isolate do V8, possibilitando usar múltiplos núcleos de CPU para atender conexões.
Cluster foi muito usado antes da popularização de containers e orquestradores. Ele continua útil em servidores simples, mas deve ser comparado com múltiplas réplicas gerenciadas por systemd, PM2, Docker ou Kubernetes.
Como funciona
Um processo primário inicia workers por cluster.fork(). Os workers podem criar servidores na mesma porta. O runtime coordena a distribuição de conexões conforme a plataforma e a política configurada.
import cluster from 'node:cluster';
import os from 'node:os';
import { createServer } from 'node:http';
if (cluster.isPrimary) {
const workers = Math.max(1, os.availableParallelism());
for (let i = 0; i < workers; i += 1) {
cluster.fork();
}
} else {
createServer((req, res) => {
res.end(`worker ${process.pid}`);
}).listen(3000);
}Cada worker executa o arquivo desde o início, por isso coloque a criação de processos somente no ramo primário.
Isolamento de memória
Variáveis não são compartilhadas:
let counter = 0;
server.on('request', () => {
counter += 1;
});Cada worker terá seu próprio contador. Para estado consistente, use banco, Redis ou outro armazenamento externo. Não mantenha sessões apenas em memória sem afinidade e estratégia de replicação.
Quantidade de workers
os.availableParallelism() fornece uma estimativa adequada do paralelismo disponível. Entretanto, em containers, confirme limites de CPU e faça benchmark. Criar mais processos que núcleos pode aumentar troca de contexto e memória.
Reserve capacidade para sidecars, sistema operacional e tarefas CPU-bound. Uma API I/O-bound pode tolerar mais processos, mas não existe regra universal.
Reiniciando workers
cluster.on('exit', (worker, code, signal) => {
logger.error({
pid: worker.process.pid,
code,
signal,
}, 'Worker encerrado');
cluster.fork();
});Não reinicie indefinidamente sem controle. Um bug determinístico pode criar um ciclo de crash. Aplique backoff, limite reinícios e marque a instância como não saudável.
Mensagens entre primário e worker
if (cluster.isPrimary) {
for (const worker of Object.values(cluster.workers)) {
worker?.send({ type: 'config', logLevel: 'info' });
}
} else {
process.on('message', (message) => {
if (message.type === 'config') {
updateConfig(message);
}
});
}IPC é útil para controle e métricas pequenas. Não use como banco ou barramento de grandes payloads.
Distribuição desigual
Conexões persistentes, WebSockets, HTTP keep-alive e diferenças de duração podem deixar um worker mais carregado. Monitore por processo:
- requisições;
- CPU;
- memória;
- event loop utilization;
- event loop delay;
- conexões ativas;
- latência;
- erros.
Uma média agregada pode esconder um único worker saturado.
Sessões e sticky sessions
WebSockets e alguns protocolos podem exigir que uma conexão continue no mesmo worker. Afinidade pode ser implementada no balanceador externo. Mesmo com sticky sessions, mantenha estado importante fora do processo para tolerar reinícios.
Graceful shutdown
Ao receber SIGTERM, o primário deve parar de criar trabalho e coordenar o encerramento:
if (cluster.isPrimary) {
process.on('SIGTERM', () => {
for (const worker of Object.values(cluster.workers)) {
worker?.send({ type: 'shutdown' });
}
setTimeout(() => {
for (const worker of Object.values(cluster.workers)) {
worker?.kill('SIGKILL');
}
process.exit(1);
}, 30_000).unref();
});
} else {
process.on('message', (message) => {
if (message.type === 'shutdown') {
server.close(() => process.exit(0));
}
});
}O worker deve parar de aceitar conexões, aguardar requisições em andamento e encerrar recursos. Defina prazo menor que o timeout da plataforma.
Rolling restart
- inicie um novo worker;
- aguarde o evento de listening e readiness;
- desconecte um worker antigo;
- aguarde conexões terminarem;
- repita até substituir todos.
Isso permite recarregar código ou configuração com menor indisponibilidade. Uma implantação por orquestrador costuma oferecer mecanismo mais robusto.
Readiness e health checks
O processo primário não deve indicar saúde se a quantidade mínima de workers não está pronta. Cada worker precisa concluir conexões, caches essenciais e inicialização antes de receber tráfego.
worker.on('listening', (address) => {
logger.info({ pid: worker.process.pid, address }, 'Worker pronto');
});Banco de dados
Cada worker cria seu próprio pool. Quatro workers com pool de 20 conexões podem abrir até 80 conexões. Dimensione o total considerando réplicas e limites do banco.
Feche pools no shutdown e evite migrations simultâneas em todos os workers.
Caches locais
Cada worker possui cache independente. Isso aumenta memória e pode gerar dados diferentes até expiração. Para valores que exigem invalidação imediata, use cache externo ou mensagens de invalidação.
Tarefas agendadas
Se cada worker registra o mesmo cron, a tarefa será executada várias vezes. Execute jobs no primário, em processo separado ou em um scheduler com lock distribuído.
Logs
Inclua PID e worker ID:
logger.info({
pid: process.pid,
workerId: cluster.worker?.id,
}, 'Requisição recebida');Centralize a coleta e preserve ordem por timestamp; logs de processos diferentes podem intercalar.
Métricas Prometheus
Cada worker mantém registries em memória. Você pode expor uma porta por worker, agregar via IPC ou usar um agregador compatível com multiprocessos. Evite somar gauges que representam estado não aditivo.
Cluster e CPU-bound
Múltiplos processos permitem que requisições CPU-bound sejam distribuídas, mas cada tarefa ainda bloqueia o worker que a executa. Para cálculos longos, Worker Threads ou filas são mais apropriados.
Cluster e containers
Em Kubernetes, frequentemente é mais simples executar um processo por container e aumentar réplicas. Benefícios:
- limites e métricas por processo;
- reinício isolado;
- autoscaling;
- rolling update nativo;
- configuração mais simples.
Cluster pode fazer sentido quando cada pod recebe vários CPUs e você quer reduzir número de pods. Calcule o total de workers por nó.
PM2 e gerenciadores
PM2 oferece modo cluster, reinício, logs e deploy. systemd e supervisores também podem iniciar múltiplas instâncias. Evite duas camadas tentando reiniciar e escalar o mesmo processo sem coordenação.
Tratamento de exceções
Não tente continuar indefinidamente após estado desconhecido causado por exceção não tratada. Registre contexto seguro, pare de aceitar tráfego e deixe o supervisor reiniciar. Use handlers para shutdown, não para esconder bugs.
Zero downtime não é garantido
Conexões longas, WebSockets, banco e sinais podem atrasar a troca. Teste rolling restart com tráfego realista e observe erros durante a janela.
Benchmark
Compare:
- um processo;
- N workers;
- N containers;
- limites diferentes de CPU;
- keep-alive;
- latência p95 e p99;
- memória total;
- tempo de recuperação.
Mais processos não ajudam quando o gargalo está no banco ou serviço externo.
Erros comuns
- guardar sessão somente em memória;
- abrir pools grandes em cada worker;
- executar cron em todos os workers;
- reiniciar em loop sem backoff;
- ignorar graceful shutdown;
- usar média agregada para saúde;
- criar workers demais;
- misturar cluster com autoscaling sem cálculo;
- assumir distribuição perfeitamente uniforme.
Fluxo recomendado
Defina se o gerenciamento deve ficar no aplicativo ou na infraestrutura. Dimensione workers, externalize estado, implemente readiness e shutdown e teste falhas. Para CPU, combine com Worker Threads; para processos externos, veja child_process; para saturação, acompanhe Event Loop Utilization.
Consulte a documentação oficial de cluster e a referência de availableParallelism.



