Encerramento ordenado (graceful shutdown)
Quando você implanta uma versão nova ou escalona para baixo, a plataforma para
as instâncias antigas. Não as mata de uma vez: primeiro lhes envia o sinal SIGTERM
para pedir que terminem ordenadamente. Um processo bem feito escuta esse
sinal e faz um encerramento ordenado (graceful shutdown):
- Para de aceitar novas requisições (fecha o servidor HTTP).
- Termina as requisições que já estão em andamento.
- Fecha os recursos abertos: conexões com o banco de dados, filas, arquivos.
- Sai do processo (
process.exit(0)).
process.on("SIGTERM", async () => {
console.log("SIGTERM recebido, encerrando...");
servidor.close(); // 1 e 2: não aceita novas, drena as ativas
await db.disconnect(); // 3: fecha o BD
process.exit(0); // 4
});
Sem isso, uma implantação cortaria requisições pela metade e deixaria conexões penduradas. Costuma-se adicionar um timeout: se em, p. ex., 10 segundos não terminou, força-se a saída para não ficar bloqueado.
Process managers (PM2)
Em produção você não executa node app.js na mão. Um process manager como
PM2 se encarrega de:
- Reiniciar o processo se ele cai (resiliência).
- Executar várias instâncias em modo cluster para usar todos os núcleos.
- Fazer recarga sem downtime (reload) nas implantações.
- Centralizar os logs e expor métricas básicas.
Contêineres (Docker)
Um contêiner Docker empacota sua app com seu ambiente (versão do Node, dependências, sistema base) em uma imagem imutável. A mesma imagem roda igual no seu notebook e em produção ("funciona na minha máquina" deixa de ser desculpa). A config e os segredos são injetados de fora como variáveis de ambiente (coerente com 12-factor), nunca dentro da imagem.
CI/CD
Um pipeline de CI/CD (GitHub Actions, GitLab CI...) automatiza o caminho do
commit até produção: em cada push executa os tests e o lint (Continuous
Integration) e, se passam, constrói a imagem e a implanta (Continuous
Delivery/Deployment). O objetivo é que implantar seja um evento entediante e seguro,
não um ritual de risco.
Escalonamento horizontal
Para suportar mais carga você tem dois caminhos: vertical (uma máquina maior, com limite físico) e horizontal (mais instâncias atrás de um balanceador de carga). O escalonamento horizontal é a base da alta disponibilidade, mas exige que o servidor seja stateless: não deve guardar estado em memória (sessões, cache) que só viva em uma instância; esse estado vai para um armazenamento compartilhado (Redis, o BD). Assim qualquer instância pode atender qualquer requisição, e health checks + graceful shutdown permitem adicionar e remover instâncias sem que o usuário perceba.
Exemplos
Esqueleto de graceful shutdown ao receber SIGTERM
// Simulação: um servidor com um método close e um BD com disconnect.
const servidor = { close() { console.log("Servidor fechado: não aceita novas requisições"); } };
const db = { async disconnect() { console.log("BD desconectado"); } };
async function apagar(sinal) {
console.log(sinal + " recebido, iniciando encerramento ordenado...");
servidor.close();
await db.disconnect();
console.log("Encerramento completo");
}
await apagar("SIGTERM");