Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Mind the lag: Por que você deveria monitorar seu event loop

Mind the lag: Por que você deveria monitorar seu event loop

É senso comum que, com JavaScript, você nunca deve executar tarefas de uso intensivo de CPU em sua thread principal. Mas daí você pensa: "Ah, é só uma função CPU-bound, não faz mal". De repente, o desempenho da sua aplicação cai, as respostas ficam lentas e você não consegue identificar o por quê! Provavelmente você está com um lag alto no seu event loop.

Nesta palestra aprenderemos o que é o lag de event loop, como podemos monitorá-lo de forma eficiente e como integrá-lo nas métricas de saúde da nossa aplicação para que tenhamos informações suficientes para debugar esse tipo de problema.

--
Palestra apresentada na TDC Florianópolis 2024.

Paulo Souza

June 17, 2024
Tweet

Other Decks in Programming

Transcript

  1. Quem sou eu? 👤 Paulo Souza 📍 Teresina, Piauí 🏢

    Desenvolvedor de Software @ CodeMiner42 🥓 beacons.ai/paulorsf
  2. Perspectiva do usuário • Imagine um usuário de um serviço

    de conversões ◦ O serviço fornece conversões de arquivos, moedas, unidades, etc • O usuário faz apenas conversões entre moedas:
  3. Perspectiva do usuário • Um dia qualquer, o usuário percebe

    que o tempo de resposta está consistentemente maior que o normal: • Naturalmente, o usuário entra em contato com a empresa e relata o problema
  4. Perspectiva do desenvolvedor • Imagine o desenvolvedor do serviço de

    conversões • Usuários estão relatando respostas lentas ◦ Processo de investigação para debugging é iniciado ◦ Relatos começaram pouco após o deploy da nova funcionalidade de conversão de texto JSON para YAML • Eventualmente, você encontra a implementação da funcionalidade: const fileContent = request.files.original.data.toString(); const parsedFileContent = JSON.parse(fileContent); const fileContentInYaml = YAML.stringify(parsedFileContent); response .header("Content-Type", "application/yaml") .send(fileContentInYaml);
  5. Don't block the event loop! • Implementação do novo endpoint

    é computacionalmente intensiva ◦ Operações de serialização e deserialização podem ser muito custosas • O event loop está sendo bloqueado ◦ Bloqueio de execução afeta todos os usuários da aplicação • Por que o código bloqueia o event loop? Como medir isso?
  6. Como JavaScript é executado • JavaScript possui modelo de execução

    assíncrono // example.js function printNumbers() { console.log("1"); setTimeout(() => { console.log("2"); }, 100); fs.readFile("file.md", () => { console.log("3"); }); console.log("4"); } printNumbers();
  7. Como JavaScript é executado • JavaScript possui modelo de execução

    assíncrono // example.js function printNumbers() { console.log("1"); setTimeout(() => { console.log("2"); }, 100); fs.readFile("file.md", () => { console.log("3"); }); console.log("4"); } printNumbers();
  8. Como JavaScript é executado • JavaScript possui modelo de execução

    assíncrono, concorrente // example.js function printNumbers(callTag) { console.log("1", callTag); setTimeout(() => { console.log("2", callTag); }, 100); fs.readFile("file.md", () => { console.log("3", callTag); }); console.log("4", callTag); } printNumbers("> first call"); printNumbers("> second call"); printNumbers("> third call");
  9. Como JavaScript é executado • JavaScript possui modelo de execução

    assíncrono, concorrente // example.js function printNumbers(callTag) { console.log("1", callTag); setTimeout(() => { console.log("2", callTag); }, 100); fs.readFile("file.md", () => { console.log("3", callTag); }); console.log("4", callTag); } printNumbers("> first call"); printNumbers("> second call"); printNumbers("> third call");
  10. Como JavaScript é executado • JavaScript possui modelo de execução

    assíncrono, concorrente e single threaded Função A Função B Função A Função B
  11. Como JavaScript é executado • Execução é orquestrada por um

    event loop ◦ Call stack contém o código sendo executado ◦ Callback queue contém callbacks para serem executadas de forma assíncrona ◦ Modelo simplificado
  12. HTTP File System Timers … APIs Call Stack Callback queue

    someFunction() fs.readFile("foo.txt", printFileData) printFileData
  13. HTTP File System Timers … APIs Call Stack Callback queue

    someFunction() callback1 callback2 callback3
  14. HTTP File System Timers … APIs Call Stack Callback queue

    someFunction() JSON.parse(hugeJsonStr) callback1 callback2 callback3
  15. HTTP File System Timers … APIs Call Stack Callback queue

    someFunction() JSON.parse(hugeJsonStr) callback1 callback2 callback3
  16. Lag no event loop • Tempo entre o enfileiramento de

    uma callback e sua execução se chama lag • Aumento do lag pode ser causado por bloqueio do event loop ou volume elevado de callbacks ◦ Macro vs micro tasks, nextTick, polling, etc ◦ Escalonamento vertical de aplicações Node nem sempre faz sentido ◦ Ambos os casos podem ser solucionados cb1 cb2 cb3 cb4 cb5 cb6 cb7 cb8 cb9 Callback queue …
  17. Lag no event loop • Até funções "inofensivas" podem bloquear

    o event loop e aumentar o lag ◦ JSON.parse / JSON.stringify • Lag alto indica degradação de performance da aplicação ◦ Performance impacta diretamente a experiência do usuário ◦ Responsividade da aplicação é prejudicada
  18. Como medir o lag • Profiling? ◦ Pode ter impacto

    significativo à performance da aplicação ◦ Normalmente não é adequado em ambientes de produção • Podemos medir durante a execução da aplicação ◦ Podemos utilizar callbacks que meçam o atraso entre seu enfileiramento e sua execução ◦ Impacto de performance é pequeno const queuingTime = performance.now(); setTimeout(() => { const executionTime = performance.now(); const lag = queuingTime - executionTime; console.log("[Event loop lag]:", lag.toFixed(2), "ms"); }, 0);
  19. Como medir o lag • Bibliotecas fornecem facilidades para medir

    o lag (e.g.: event-loop-lag) import initializeLagMeasure from 'event-loop-lag'; const LAG_PROBING_INTERVAL_MS = 1000; const measureLag = initializeLagMeasure(LAG_PROBING_INTERVAL_MS); export function startEventLoopLagMonitor() { setInterval(() => { console.log("[Event loop lag]:", measureLag().toFixed(2), "ms"); }, LAG_PROBING_INTERVAL_MS); }
  20. function bootApplication() { // ... *snip* ... startEventLoopLagMonitor(); httpServer.listen(port, ()

    => { console.log(`[ConversionAPI] Server listening on port ${port}`); console.log("[ConversionAPI] Application started"); }); }
  21. Por que monitorar o event loop • Facilidade para identificar

    problemas de responsividade da aplicação ◦ Problemas que impactam a responsividade podem ter diversas origens ◦ Métrica do lag do event loop facilita o isolamento ◦ Tempo de debugging pode ser reduzido
  22. Por que monitorar o event loop Aplicação Proxy reverso API

    externa Banco de dados Cliente 1 Cliente 2 Cliente n …
  23. Por que monitorar o event loop Aplicação Proxy reverso API

    externa Banco de dados Cliente 1 Cliente 2 Cliente n …
  24. Por que monitorar o event loop Aplicação Proxy reverso API

    externa Banco de dados Cliente 1 Cliente 2 Cliente n … ? ? ? ?
  25. Por que monitorar o event loop Aplicação Proxy reverso API

    externa Banco de dados Cliente 1 Cliente 2 Cliente n …
  26. Por que monitorar o event loop • Redução do tempo

    de instabilidade da aplicação ◦ Podemos criar alertas automatizados ◦ Com alertas, seremos informados de problemas antes do feedback de usuários • Dados históricos sobre como a aplicação lida com a carga de trabalho ◦ Podemos identificar padrões de picos de lag e adequar a aplicação antes do problema se tornar crítico
  27. Plataformas de monitoramento • Métricas de saúde da aplicação podem

    ser enviadas para plataformas de APM (Application Performance Monitoring) ◦ As plataformas vão coletar, armazenar e fornecer visualizações das métricas • Visualização da saúde do event loop é crucial para aplicações em Node • O event loop é parte do funcionamento interno dos runtimes de JS ◦ Nem todas as plataformas possuem ferramentas para inspeção direta do runtime ◦ Instrumentação da aplicação vai variar de acordo com a plataforma
  28. import tracer from "dd-trace" function bootApplication() { tracer.init({ env: "production",

    service: "conv-api", runtimeMetrics: true }); // ... *snip* ... httpServer.listen(port, () => { console.log(`[ConversionAPI] Server listening on port ${port}`); console.log("[ConversionAPI] Application started"); }); } Exemplo: Datadog