O bug do Node.js que é invisível para o seu monitoramento
Seu health check diz que está tudo bem. Leva um milissegundo. Então o tráfego cresce. De repente, sua latência p99 salta para 400ms. Você olha para seus dashboards. Tudo parece verde.
O uso de CPU é moderado. O event loop lag está estável. A memória está saudável. Seu APM mostra uma requisição lenta, mas não diz o porquê. Não há chamadas lentas ao banco de dados. Não há erros.
O tempo é gasto no pool de threads do libuv.
A observabilidade padrão do Node foca no event loop. O pool é uma fila separada. Ele está fora do seu alcance.
O Node executa JavaScript no event loop. Ele envia tarefas pesadas para o pool de threads do libuv. Isso inclui:
- Trabalho de sistema de arquivos (fs.readFile, fs.writeFile).
- Tarefas de criptografia (bcrypt, scrypt, pbkdf2).
- Compressão (zlib gzip, deflate).
- Buscas de DNS (dns.lookup).
O pool vem configurado por padrão com apenas quatro threads. Isso é verdade independentemente de quantos núcleos de CPU sua máquina possui.
Quatro threads não são suficientes. Aqui estão três maneiras pelas quais o pool falha:
Bcrypt no login. Um único hash bcrypt pode levar 250ms. Se quatro pessoas fizerem login ao mesmo tempo, todos os slots estarão ocupados. Uma quinta pessoa espera em uma fila. Ela "paga" 250ms apenas para começar. Sua latência dobra.
Operações gzip grandes. Comprimir uma resposta grande ocupa um slot no pool. Se quatro requisições fizerem isso ao mesmo tempo, todas as outras tarefas esperam. Buscas de DNS e leituras de arquivos ficam presas na fila.
Buscas de DNS. A maioria dos apps Node usa dns.lookup. Isso utiliza uma chamada de sistema bloqueante. Isso coloca a tarefa no pool. Se sua rede tiver um soluço, essas buscas travam todo o pool.
Uma requisição presa na fila do pool é invisível. Ela não está usando CPU. Não está executando JavaScript. Ela está apenas estacionada.
Como encontrar:
Se sua latência p99 aumenta sob carga, mas o event loop lag permanece estável, verifique o pool.
O teste mais rápido: Aumente o UV_THREADPOOL_SIZE. Defina-o como 64 no seu ambiente e reinicie. Se a latência cair, você encontrou o problema.
Como corrigir adequadamente:
- Use
worker_threadspara criptografia pesada como bcrypt. Isso evita que elas ocupem o pool do libuv. - Use
dns.resolveem vez dedns.lookup. É um resolvedor assíncrono real. - Use streams para o trabalho com zlib para liberar os slots mais rapidamente.
- Evite trabalhos pesados de sistema de arquivos em seus caminhos principais de requisição.
Pare de encarar dashboards verdes enquanto seus usuários esperam.
Fonte: https://dev.to/r9v/the-nodejs-bug-thats-invisible-to-your-monitoring-oo8
