Depurando vazamentos de memória no Node.js
Vazamentos de memória em aplicações Node.js podem ser silenciosos por dias até se tornarem um problema real. Neste post descrevo o processo que usei para identificar e corrigir um vazamento em um serviço de mensageria.
O primeiro passo é sempre medir — sem dados, qualquer hipótese é especulação.
Sintomas
A aplicação começou a consumir memória progressivamente ao longo de horas. O heap crescia devagar, os GCs tornavam-se mais frequentes e, eventualmente, o processo morria com `FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed`.
Ferramentas
`process.memoryUsage()` foi o ponto de partida. Adicionei logs periódicos do `heapUsed` para confirmar que havia crescimento real e não apenas variação normal.
Em seguida usei `--inspect` com o Chrome DevTools para capturar heap snapshots em momentos diferentes. A comparação entre snapshots revelou quais objetos acumulavam.
A causa
Um listener de eventos adicionado dentro de um loop de processamento nunca era removido. Cada mensagem processada adicionava um novo listener ao mesmo EventEmitter, e as closures mantinham referências para objetos grandes.
A correção
Mover o registro do listener para fora do loop e garantir que `removeListener` fosse chamado corretamente na finalização do processamento.
O aprendizado: listeners esquecidos são uma das fontes mais comuns de vazamento em aplicações orientadas a eventos. Vale criar convenção de sempre parear `on` com `off` ou usar `once` quando possível.