Event Loop: como o Node.js processa tudo sem travar

Event Loop: como o Node.js processa tudo sem travar

Robson Rabelo - 12 de julho de 2026 - 1 visualizações

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):

As fases do event loop do Node.js

  1. Timers: executa callbacks de setTimeout/setInterval vencidos.
  2. Pending callbacks: callbacks de I/O adiados da volta anterior.
  3. 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.
  4. Check: executa setImmediate.
  5. 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:

  1. Fatiar: quebrar o processamento em pedaços e devolver o controle ao loop entre eles (setImmediate). Resolve casos moderados.
  2. Worker Threads: threads de verdade para CPU, dentro do mesmo processo (tema do terceiro artigo desta série).
  3. Cluster: múltiplos processos Node, um por núcleo, dividindo as conexões (próximo artigo).
  4. 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 *Sync fora do bootstrap da aplicação.
  • Payloads grandes tratados por streams (tema do quarto artigo da série) ou por workers, nunca por JSON.parse de 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.


Leia também

Compartilhar:

Deixe um Comentário

Categorias

Posts Recentes