Graceful Worker Shutdown no Node.js encerra um processo sem iniciar novos jobs, permitindo que tarefas ativas terminem ou retornem a um estado recuperável. O fluxo é essencial em deploys, autoscaling, reinícios, manutenção e interrupções do sistema operacional.
Finalizar o processo imediatamente pode deixar transações abertas, arquivos incompletos e mensagens marcadas como em processamento. Esperar indefinidamente também é perigoso. O worker precisa de uma janela controlada e um caminho de saída forçada.
Sinais do processo
process.once('SIGTERM', () => shutdown('SIGTERM'));
process.once('SIGINT', () => shutdown('SIGINT'));SIGTERM é comum em orquestradores. SIGINT ocorre no terminal. Registre handlers uma vez para não iniciar vários shutdowns.
Estado de encerramento
let shuttingDown = false;
async function shutdown(signal) {
if (shuttingDown) return;
shuttingDown = true;
logger.info({ signal }, 'iniciando shutdown do worker');
// etapas de encerramento
}Todas as partes do processo podem consultar esse estado para rejeitar novos trabalhos.
Ordem recomendada
- marque o processo como não pronto;
- pare de buscar novos jobs;
- pare schedulers e timers;
- aguarde tarefas ativas;
- cancele operações que aceitam AbortSignal;
- faça flush de logs e métricas;
- feche fila, banco e Redis;
- encerre com código apropriado.
Parando a entrada
Com BullMQ:
await worker.close();O método para de adquirir novos jobs e aguarda os ativos conforme o comportamento da biblioteca. Consulte BullMQ no Node.js.
Worker próprio
while (!shuttingDown) {
const job = await reserveNextJob({ signal: shutdownController.signal });
if (!job) continue;
await runTracked(job);
}A operação de reserva precisa ser cancelável ou possuir timeout curto para perceber o shutdown rapidamente.
Rastreando jobs ativos
const activeJobs = new Set();
async function runTracked(job) {
const promise = processJob(job)
.finally(() => activeJobs.delete(promise));
activeJobs.add(promise);
return promise;
}Durante shutdown:
await Promise.allSettled([...activeJobs]);Essa espera precisa de deadline.
Deadline global
async function withShutdownDeadline(task, timeoutMs) {
return Promise.race([
task,
new Promise((_, reject) =>
setTimeout(() => reject(new Error('shutdown timeout')), timeoutMs)
)
]);
}Defina o timeout menor que a janela oferecida pelo orquestrador, reservando tempo para fechar conexões.
AbortController
const shutdownController = new AbortController();
async function shutdown() {
shuttingDown = true;
stopReceivingJobs();
shutdownController.abort(new Error('worker shutting down'));
}Propague o sinal para fetch, timers, streams e bibliotecas compatíveis.
await fetch(url, {
signal: AbortSignal.any([
shutdownController.signal,
AbortSignal.timeout(15_000)
])
});Não aborte automaticamente uma operação crítica se isso deixa estado inconsistente. Algumas tarefas devem terminar a etapa atômica antes de respeitar cancelamento.
Transações
Uma transação curta pode concluir. Uma tarefa longa deve dividir trabalho em checkpoints. Se o processo cair, outro worker retoma do último estado confirmado.
Lease e heartbeat
Sistemas próprios podem marcar jobs como processing com um lease.
UPDATE jobs
SET lease_until = now() + interval '60 seconds',
worker_id = $2
WHERE id = $1;O worker renova enquanto trabalha. No shutdown, para de renovar depois de concluir ou liberar o job. Se morrer, outro worker recupera quando o lease expira.
Idempotência
Shutdown pode ocorrer depois do efeito externo e antes de marcar conclusão. O retry precisa ser seguro.
Consulte Job Deduplication no Node.js e Consumer Idempotente no Node.js.
Jobs não canceláveis
CPU síncrona longa bloqueia handlers de sinal até retornar. Mova trabalho pesado para Worker Threads ou subprocessos, com protocolo de término e timeout.
Child processes
child.kill('SIGTERM');
const timer = setTimeout(() => {
child.kill('SIGKILL');
}, 10_000);
child.once('exit', () => clearTimeout(timer));Confirme implicações em diferentes sistemas operacionais e grupos de processos.
Streams e arquivos
Use pipeline() e aguarde finalização. Arquivos temporários devem ser renomeados atomicamente somente depois da escrita completa. Remova parciais durante recuperação.
Timers
Cancele intervalos e timeouts que criariam novos jobs.
clearInterval(reconciliationTimer);
clearTimeout(scheduleTimer);Não use process.exit() enquanto callbacks e flushes ainda são necessários; ele encerra o event loop imediatamente.
Banco e Redis
Feche recursos depois que jobs ativos terminaram.
await queue.close();
await redis.quit();
await pool.end();Se fechar o pool antes, jobs em andamento falham artificialmente.
Logs e telemetria
Faça flush com timeout. Registre sinal, jobs ativos, tempo de shutdown, jobs concluídos, abortados e forçados.
Readiness no Kubernetes
Ao iniciar shutdown, o pod deve ficar não pronto para sair do balanceamento e parar de receber comandos HTTP auxiliares.
let ready = true;
app.get('/ready', (req, res) => {
res.sendStatus(ready ? 200 : 503);
});
No shutdown, defina ready = false antes de fechar serviços. Consulte Kubernetes Probes no Node.js.
terminationGracePeriodSeconds
A janela do Kubernetes precisa cobrir:
- propagação de readiness;
- jobs ativos;
- flush;
- fechamento de conexões;
- margem de segurança.
Jobs maiores que a janela devem ser checkpointáveis ou executados em ambiente adequado para tarefas longas.
preStop
Um hook pode sinalizar drenagem antes do SIGTERM, mas não dependa exclusivamente dele. O processo ainda precisa tratar sinais corretamente.
Jobs agendados
Schedulers devem parar primeiro. Um job criado durante shutdown pode ficar sem worker. Consulte Delayed Jobs no Node.js.
Exit code
Use código zero quando o shutdown planejado concluiu. Use código diferente para falha fatal ou deadline excedida. Supervisores podem usar o código para diagnóstico, mas normalmente reiniciam o processo conforme política.
Erros fatais
Após uncaughtException ou estado corrompido, não continue processando novos jobs. Inicie drenagem limitada e encerre. Não tente transformar todo erro desconhecido em operação normal.
Teste do shutdown
- inicie job controlado;
- envie SIGTERM;
- confirme que novo job não inicia;
- confirme que o ativo conclui ou volta à fila;
- verifique fechamento de conexões;
- confirme exit code;
- repita com job travado até deadline.
Observabilidade
Monitore:
- quantidade de shutdowns;
- duração;
- jobs ativos no início;
- jobs concluídos;
- jobs recuperados depois;
- timeouts;
- encerramentos forçados;
- leases expirados.
Erros comuns
- chamar
process.exit()imediatamente; - continuar buscando jobs;
- fechar banco antes dos processors;
- não ter deadline;
- abortar transação no ponto errado;
- job sem checkpoint;
- não marcar readiness;
- ignorar subprocessos;
- não testar SIGTERM.
Checklist
- handlers once;
- estado de draining;
- entrada interrompida;
- timers cancelados;
- jobs rastreados;
- deadline global;
- AbortSignal propagado;
- idempotência e leases;
- recursos fechados na ordem;
- teste real de sinais.
Conclusão
Graceful Worker Shutdown no Node.js é um protocolo de drenagem. O processo para de aceitar trabalho, conclui o que é seguro e deixa o restante recuperável.
Use deadline, idempotência, checkpoints e readiness. Com fechamento ordenado de fila, banco e telemetria, deploys deixam de causar jobs duplicados ou abandonados.
Consulte a documentação do process no Node.js e a documentação do ciclo de vida de Pods no Kubernetes.


