Performance em Node: event loop, streams e backpressure
⏱ 15 min·⭐ 65 XP
Pré-requisitos (0/1)0%
- ⬜🚨 Erros como valores: Result, neverthrow e por que `throw` quebra(TypeScript Profissional)
Recomendamos completar os pré-requisitos antes de seguir, mas nada te impede de continuar.
Event loop em 60 segundos
🗺️ As fases do laço, e o que costuma travar cada uma
Temporizadoresatraso aqui indica laço ocupado
→
Retornos agendados por tempo
Espera de entrada e saídaé o estado normal
→
Onde o processo passa a maior parte do tempo saudável
Verificação
→
Retornos de execução imediata
Fechamentovazamento aparece como contagem de descritores crescendo
→
Liberação de recursos
⚠ Fora das fases: seu código síncrononenhuma fase avança enquanto isto roda — é a causa da cauda de latência
→
Leitura de arquivo bloqueante, laço pesado, análise de JSON gigante
Node roda um event loop que alterna entre fases:
- timers — ,
- pending callbacks — I/O adiados
- poll — I/O novos (onde Node passa mais tempo)
- check —
- close — cleanup de handles
Entre cada fase, process.nextTick e microtasks (Promises) rodam. Bloquear qualquer uma dessas fases trava tudo.
Sync vs async FS
// ❌ bloqueia event loop — requests param
const data = fs.readFileSync('huge.json');
// ✅ thread pool do libuv cuida
const data = await fs.promises.readFile('huge.json');
// ✅✅ stream: não carrega tudo na memória
const stream = fs.createReadStream('huge.json');
for await (const chunk of stream) { /* processa */ }⚠️
Sync FS em código de request é crime comum. Seu P99 vai explodir porque uma leitura lenta trava TODOS os requests concorrentes.
Streams e backpressure
import { pipeline } from 'node:stream/promises';
import { createReadStream, createWriteStream } from 'node:fs';
import { createGzip } from 'node:zlib';
// pipeline: composição com backpressure + erro + cleanup
await pipeline(
createReadStream('input.log'),
createGzip(),
createWriteStream('input.log.gz'),
);
// Transform stream custom
import { Transform } from 'node:stream';
const upper = new Transform({
transform(chunk, _enc, cb) {
cb(null, chunk.toString().toUpperCase());
},
});Quiz rápido
Por que `fs.readFileSync` dentro do tratamento de uma requisição costuma aparecer como P99 alto?
CPU-bound: workers
// main.ts
import { Worker } from 'node:worker_threads';
const w = new Worker('./hash-worker.js');
w.postMessage({ data });
w.on('message', result => console.log(result));
// hash-worker.js
import { parentPort } from 'node:worker_threads';
import { createHash } from 'node:crypto';
parentPort?.on('message', ({ data }) => {
const hash = createHash('sha256').update(data).digest('hex');
parentPort?.postMessage(hash);
});Ferramentas pra diagnóstico
- + Chrome DevTools — profiler, heap snapshot.
- (clinic doctor/flame/bubbleprof) — diagnóstico automatizado de gargalos.
- — flamegraphs.
- Built-in perf hooks: , .
Perguntas frequentes
❓ O que bloqueia o laço de eventos?
Trabalho de CPU síncrono: laço longo, análise de arquivo grande, criptografia, compressão. Enquanto isso roda, nenhuma requisição é atendida. O sintoma é latência de todas as rotas subindo junto, não só da rota lenta — e é o que distingue esse problema de consulta lenta.
❓ Para que servem fluxos de dados?
Para processar dado maior que a memória em pedaços, com controle de pressão de volta — o consumidor lento faz o produtor esperar em vez de acumular. Ler arquivo grande inteiro para memória é o erro que só aparece quando o arquivo cresce em produção.
❓ Como diagnosticar consumo de memória crescente?
Com perfil de heap em dois momentos e comparação do que cresceu. As causas mais comuns são cache sem limite, ouvinte de evento nunca removido e referência guardada em escopo de módulo. Reiniciar o processo periodicamente esconde o problema e não o resolve.
Fixando
Quiz rápido
Para copiar um arquivo grande transformando o conteúdo, por que usar `pipeline` com streams em vez de ler tudo e escrever?
Quiz rápido
Um cálculo pesado de CPU está travando o servidor. Qual é a resposta correta?
Terminou de ler?
Marcar como concluído registra o XP, mantém sua sequência e coloca 3 cartas deste módulo na fila de revisão espaçada.
Próximos passos sugeridos
Temas deste módulo
Discussão
Carregando comentários…