Event Loop: como o Node.js processa tudo sem travar
Uma thread só atendendo milhares de conexões: o event loop explicado com código, as fases oficiais, o pecado de bloquear o loop e como medir o lag em produção.
Como uma linguagem de uma única thread atende milhares de conexões simultâneas? Essa pergunta define o Node.js, e a resposta é o componente mais mal compreendido da plataforma: o event loop. Entendê-lo é a diferença entre um servidor que voa e um que trava misteriosamente sob carga.
A ideia central: nunca espere parado
Em um servidor tradicional com threads, cada requisição ocupa uma thread que espera o banco responder, o arquivo abrir, a API externa retornar. Milhares de conexões = milhares de threads dormindo, cada uma custando memória e trocas de contexto.
O Node.js inverte o modelo: uma thread executa seu JavaScript, e toda operação de I/O é delegada ao sistema operacional (via a biblioteca libuv). Enquanto o banco processa a consulta, a thread não espera: ela atende a próxima requisição. Quando o resultado chega, o callback entra na fila e é executado na primeira brecha.
console.log("1: início");
setTimeout(() => console.log("3: timer"), 0);
Promise.resolve().then(() => console.log("2: microtask"));
console.log("1.5: fim do código síncrono");
// Saída: 1 → 1.5 → 2 → 3
O exemplo revela a mecânica: o código síncrono roda até o fim, depois as microtasks (promises), depois as fases do loop (timers, I/O, etc.). Nada interrompe uma função no meio; cada callback roda até terminar. Essa é a garantia, e também o perigo.
As fases do loop (o mapa oficial)
A cada volta, o event loop percorre fases em ordem fixa, documentadas no guia oficial do Node.js (nodejs.org):

- Timers: executa callbacks de
setTimeout/setIntervalvencidos. - Pending callbacks: callbacks de I/O adiados da volta anterior.
- Poll: a fase principal: recebe novas conexões, lê resultados de I/O, executa seus callbacks. É onde o loop "espera" quando não há nada a fazer.
- Check: executa
setImmediate. - Close: callbacks de encerramento (
socket.on('close')).
E entre cada callback, duas filas furam a ordem: process.nextTick() e as microtasks de Promise. Por isso Promise.resolve().then() roda antes de setTimeout(fn, 0): microtask vence timer, sempre.
O pecado capital: bloquear o loop
A thread que roda seu JavaScript é uma só. Se um callback demora 200ms em CPU, todas as outras requisições esperam 200ms. É assim que um endpoint inocente derruba a API inteira:
// Um endpoint que trava o servidor inteiro:
app.get("/relatorio", (req, res) => {
const linhas = gerarRelatorioGigante(); // 2s de CPU pura
res.json(linhas); // durante esses 2s, NENHUMA outra request é atendida
});
Os bloqueadores clássicos: JSON.parse/stringify de payloads gigantes, regex catastróficas, criptografia síncrona (crypto.pbkdf2Sync), versões *Sync do módulo fs, e loops de processamento de dados grandes. Nenhum deles aparece em teste local com um usuário; todos aparecem em produção com mil.
Como detectar: monitore o event loop lag (o atraso entre o momento em que um timer deveria disparar e o momento em que dispara). A API nativa perf_hooks.monitorEventLoopDelay() entrega isso pronto, e é uma métrica que merece dashboard e alerta:
const { monitorEventLoopDelay } = require("perf_hooks");
const h = monitorEventLoopDelay({ resolution: 20 });
h.enable();
setInterval(() => {
console.log(`lag p99: ${(h.percentile(99) / 1e6).toFixed(1)}ms`);
h.reset();
}, 10_000);
Lag p99 acima de ~100ms sustentado = alguém está bloqueando o loop.
As saídas para trabalho pesado
Quando a tarefa é CPU-intensiva de verdade, as opções em ordem de simplicidade:
- Fatiar: quebrar o processamento em pedaços e devolver o controle ao loop entre eles (
setImmediate). Resolve casos moderados. - Worker Threads: threads de verdade para CPU, dentro do mesmo processo (tema do terceiro artigo desta série).
- Cluster: múltiplos processos Node, um por núcleo, dividindo as conexões (próximo artigo).
- Tirar do caminho: fila e um worker separado para o trabalho pesado. O endpoint só enfileira e responde.
A regra de bolso da plataforma: Node.js é excelente para I/O intenso (APIs, gateways, tempo real) e exige cuidado deliberado para CPU intensa. Não é defeito; é o trade-off que compra a escala de conexões com pouquíssima memória.
O checklist de quem roda Node em produção
- Nenhuma função
*Syncfora do bootstrap da aplicação. - Payloads grandes tratados por streams (tema do quarto artigo da série) ou por workers, nunca por
JSON.parsede dezenas de MB no caminho da requisição. - Event loop lag monitorado com alerta.
- Trabalho de CPU mapeado e endereçado (fatiar, worker, fila).
É o tipo de diagnóstico que fazemos em backends Node no nosso serviço de Desenvolvimento Web, Mobile e Desktop: medir o lag, achar os bloqueadores e desenhar a saída certa para cada um.
Sua API Node engasga sob carga? Descreva o sintoma para a IA da Rabelo Digital, aqui no canto da tela, e ela aponta os suspeitos clássicos do event loop. Sem formulário, sem espera.


Deixe um Comentário