Home > Blog > Desenvolvimento Web
Desenvolvimento Web
JavaScript
Programação

Event Loop Utilization no Node.js

Atualizado em: 27 de setembro de 2026

Rack de servidores processando fluxos de dados no Node.js

Event Loop Utilization, ou ELU, é uma métrica que estima quanto tempo o event loop do Node.js permaneceu ativo em relação ao tempo total observado. Ela ajuda a diferenciar um processo ocupado executando JavaScript ou callbacks de um processo que passou a maior parte do intervalo esperando por eventos.

A métrica faz parte de node:perf_hooks e é especialmente útil para diagnosticar saturação da thread principal. CPU do sistema, latência e event loop delay continuam importantes, mas ELU acrescenta uma visão do próprio runtime.

O que o ELU mede

O resultado possui três valores principais:

  • idle: tempo em que o event loop permaneceu ocioso;
  • active: tempo em que esteve ativo;
  • utilization: proporção entre atividade e o intervalo total.

Uma utilização próxima de 1 indica que o event loop passou quase todo o intervalo ativo. Isso pode ser esperado durante um benchmark intenso, mas é um sinal de alerta quando a latência aumenta e o throughput deixa de crescer.

Primeira medição

import { performance } from 'node:perf_hooks';

let previous = performance.eventLoopUtilization();

setInterval(() => {
  const current = performance.eventLoopUtilization(previous);
  previous = performance.eventLoopUtilization();

  console.log({
    active: current.active,
    idle: current.idle,
    utilization: current.utilization,
  });
}, 10_000).unref();

A chamada com um valor anterior calcula a diferença entre dois momentos. Trabalhar com intervalos é mais útil que observar apenas o acumulado desde o início do processo.

Utilização não é porcentagem de CPU

ELU e CPU respondem a perguntas diferentes. Um callback pode permanecer ativo aguardando uma chamada nativa síncrona, enquanto a distribuição de CPU aparece em outra camada. Também é possível ter CPU alta em Worker Threads e ELU moderado na thread principal.

Monitore em conjunto:

  • CPU do processo e do container;
  • event loop utilization;
  • event loop delay;
  • p95 e p99;
  • requisições por segundo;
  • taxa de erros;
  • fila de trabalho;
  • garbage collection.

ELU e event loop delay

Event Loop Utilization mostra a proporção de atividade. Event loop delay mede quanto um timer ou callback foi atrasado. Um processo pode ter ELU alto sem atraso extremo se o trabalho estiver dividido em callbacks curtos. Também pode apresentar atraso importante durante pequenos períodos de bloqueio que se perdem em uma média longa.

Use monitorEventLoopDelay:

import { monitorEventLoopDelay, performance } from 'node:perf_hooks';

const delay = monitorEventLoopDelay({ resolution: 20 });
delay.enable();

let previous = performance.eventLoopUtilization();

setInterval(() => {
  const elu = performance.eventLoopUtilization(previous);
  previous = performance.eventLoopUtilization();

  console.log({
    elu: elu.utilization,
    delayP99Ms: delay.percentile(99) / 1e6,
    delayMaxMs: delay.max / 1e6,
  });

  delay.reset();
}, 10_000).unref();

Os valores do histograma são fornecidos em nanossegundos, por isso o exemplo divide por um milhão.

Exportando para Prometheus

Uma aplicação pode transformar ELU em gauge:

import client from 'prom-client';
import { performance } from 'node:perf_hooks';

const eluGauge = new client.Gauge({
  name: 'nodejs_event_loop_utilization_ratio',
  help: 'Utilização do event loop no último intervalo',
});

let previous = performance.eventLoopUtilization();

setInterval(() => {
  const delta = performance.eventLoopUtilization(previous);
  previous = performance.eventLoopUtilization();
  eluGauge.set(delta.utilization);
}, 5_000).unref();

Evite labels de alta cardinalidade. Normalmente basta identificar serviço, ambiente, versão e instância por meio da configuração do coletor.

Interpretando valores altos

Não existe um único limite universal. Uma API com callbacks curtos pode trabalhar bem com ELU alto durante picos. O problema surge quando a utilização elevada coincide com:

  • latência crescente;
  • queda de throughput;
  • timeouts;
  • event loop delay alto;
  • fila aumentando;
  • CPU próxima ao limite;
  • autoscaling tardio;
  • erros de dependências.

Crie uma baseline por serviço e compare o comportamento sob carga normal, pico e stress.

Exemplo de bloqueio

import { createServer } from 'node:http';

function trabalhoPesado() {
  let total = 0;
  for (let i = 0; i < 200_000_000; i += 1) {
    total += Math.sqrt(i);
  }
  return total;
}

createServer((req, res) => {
  if (req.url === '/calcular') {
    res.end(String(trabalhoPesado()));
    return;
  }
  res.end('ok');
}).listen(3000);

Durante chamadas concorrentes, ELU tende a subir, o delay aumenta e outras rotas ficam lentas. A solução pode envolver reduzir o trabalho, armazenar resultado, paginar, processar em lote ou mover CPU-bound para Worker Threads.

Operações assíncronas também podem saturar

Uma aplicação pode usar APIs assíncronas e ainda manter o event loop ocupado processando milhares de callbacks, promises e serializações. “Assíncrono” não significa “sem custo de CPU”.

const resultados = await Promise.all(
  ids.map(async (id) => {
    const item = await buscarItem(id);
    return transformarObjetoGrande(item);
  }),
);

Se a lista é enorme, resolução de promises e transformação podem dominar a thread. Use concorrência limitada, lotes, streams ou filas.

Medindo por requisição

ELU é uma métrica do processo ou da thread, não de uma única requisição. Tentar atribuir o valor diretamente a um usuário pode causar interpretações erradas. Para requisições individuais, use timers, traces e spans.

Você pode registrar o ELU no início e no fim de uma operação para fins experimentais, mas requisições concorrentes compartilham o mesmo event loop. O delta inclui trabalho de todas elas.

Worker Threads

Cada Worker Thread possui seu próprio event loop e sua própria medição. Monitore o worker dentro do próprio contexto e agregue os resultados com cuidado.

import { parentPort } from 'node:worker_threads';
import { performance } from 'node:perf_hooks';

let previous = performance.eventLoopUtilization();

setInterval(() => {
  const delta = performance.eventLoopUtilization(previous);
  previous = performance.eventLoopUtilization();
  parentPort?.postMessage({ type: 'elu', value: delta.utilization });
}, 5_000).unref();

Uma média simples pode esconder um único worker saturado. Preserve percentis ou valores por worker em diagnóstico interno, evitando cardinalidade excessiva no backend de métricas.

Cluster e múltiplos processos

Em cluster, cada processo possui ELU independente. O balanceador pode distribuir carga de forma desigual por conexões persistentes, afinidade ou requisições muito diferentes. Analise cada PID e também uma visão agregada.

Autoscaling

CPU é uma métrica comum para HPA, mas ELU pode indicar pressão na thread principal antes de a média de CPU do pod ficar clara. Uma estratégia possível é exportar ELU, criar uma métrica externa e combinar com fila, latência e CPU.

Não escale apenas por ELU sem testar. Um bloqueio causado por código síncrono pode ser multiplicado em mais réplicas sem resolver a causa.

Alertas

Evite alertar por um único valor instantâneo. Prefira uma condição sustentada e correlacionada:

ELU alto por 10 minutos
E p99 acima do SLO
E throughput sem crescimento

Uma regra composta reduz ruído durante tarefas curtas, aquecimento ou manutenção.

Intervalo de coleta

Intervalos curtos mostram picos, mas geram mais ruído. Intervalos longos suavizam bloqueios breves. Cinco a quinze segundos costuma ser um ponto de partida para métricas operacionais; investigações podem usar janelas menores.

Use o mesmo intervalo ao comparar versões. Reiniciar o valor anterior incorretamente pode produzir deltas inconsistentes.

ELU no início do processo

Bootstrap, carregamento de módulos, migrations e aquecimento podem elevar ELU. Marque a aplicação como pronta somente após concluir inicialização crítica. Separe dashboards de startup e tráfego estável.

Testando uma otimização

  1. estabeleça carga e baseline;
  2. registre ELU, delay, CPU, throughput e percentis;
  3. capture perfil de CPU;
  4. faça uma alteração pequena;
  5. repita na mesma infraestrutura;
  6. compare medianas de várias execuções;
  7. valide correção funcional.

Uma redução de ELU não é automaticamente positiva se o trabalho foi deslocado para uma dependência e a latência piorou.

Erros comuns

  • tratar ELU como CPU;
  • usar um limite universal sem baseline;
  • observar apenas média longa;
  • ignorar event loop delay;
  • agregar workers de forma que esconda saturação;
  • criar labels por PID ou requisição no Prometheus;
  • escalar sem corrigir código bloqueante;
  • medir somente em ambiente ocioso.

Fluxo recomendado

Meça ELU em intervalos, correlacione com delay, CPU e latência, estabeleça uma baseline e use profiling quando a utilização indicar saturação. Combine com CPU Profiling com –prof, Clinic.js, Autocannon e métricas em Prometheus no Node.js.

Consulte a documentação oficial de eventLoopUtilization e a referência oficial de monitorEventLoopDelay.

10 melhores cursos de programação em 2026

Descubra os melhores cursos de programação. Aprenda a escolher o curso ideal para iniciar ou avançar na carreira de desenvolvedor

POSTS RELACIONADOS

Ver todos

Seta para a direita