DEV Community

loading...
Cover image for Escalando e performando aplicações Node.js
Codecasts

Escalando e performando aplicações Node.js

Vinicius Reis
Married and father a beautiful daughter. I like movies, books, manga, comics, games... That is, I like more things than I have time to do them. I record videos for CodeCasts
・6 min read

Hoje Node.js é uma das plataformas de desenvolvimento mais utilizadas no mundo. Seu ecossistema é vasto e poderoso e sua performance não é nada ruim.

Como não existe bala de prata, Node.js possui pontos de atenção, características que quando não observadas podem prejudicar a performance de aplicações criadas com ele.

Single Tread e código bloqueante

Muitos já devem saber, porém, é importante frisar, Node.js é single tread, mesmo que existam operações async (obrigado libuv) , algumas operações são consideradas bloqueantes e travam toda e qualquer execução de código.

https://repl.it/@vinicius73/blocking-code

Neste exemplo, usamos o console.time para contabilizar a execução de alguns trechos de código.

Há basicamente duas operações neste código, uma assíncrona com setTimeout, e outra síncrona, na implementação da função sleep. A parte assíncrona deste código deveria ser executada em 1 segundo, porém no resultado dos timers , o código só executou depois de 3 segundos, depois que a parte síncrona do código executou.

Para entender melhor este comportamento, leia sobre event loop.

Isso aconteceu, pois a tread do Node/JS ficou travada em uma única operação, uma operação bloqueante.

Operações bloqueantes

No exemplo anterior foi possível ter uma ideia entender que while é uma operação bloqueante e qual o impacto desse tipo de operação na aplicação. Além do while outras estruturas e funções também são bloqueantes. for, Array.prototype.forEach, Array.prototype.map, Array.prototype.reduce entre outras.

Podemos assumir que todo loop é uma operação bloqueante.

Uma única operação bloqueante tem um enorme potencial destrutivo em uma aplicação http.

http server

Um servidor http extremamente simples, para testar sua performance a ferramenta siege será utilizada.

siege http://localhost:7337/ -b -i -t 10s
Enter fullscreen mode Exit fullscreen mode

Siege Result Normal

Durante 10 segundos o servidor http foi capaz de receber 51.415 requisições com correferência de 25. Abaixo alguns _logs das requisições

Http response normal

Evidentemente este é um ótimo resultado.

Na próxima imagem a rota agora passa a executar uma operação bloqueante por 500ms.

blocking route

Novamente o teste de performance com o siege.

siege result blokcing

Durante 10 segundos 25 processos concorrentes conseguiram realizar com sucesso apenas 18 requisições. É uma diminuição drástica se comparado ao teste anterior. Abaixo alguns logs dos requests.

http response blocking

Cada requisição levou ao menos 500ms para ser respondida. É possível ver que o servidor http recebeu 43 requisições, porém, o teste terminou antes que do node concluir o processamento das requisições.

O node foi capaz de resolver apenas 18 requisições em 10 segundos: 500ms * 18 = 9000ms = 9s Todas as outras requisições ficaram "presas".

Este é o preço de executar operações síncronas, bloqueantes em servidores http node.

Cluster mode, múltiplos servidores.

Mesmo que esta característica seja um problema sério, há formas eficientes de contorna-la.

Node possui um módulo chamado cluster. Este módulo permite criar forks do seu processo/servidor, atuando como um load balancer.

Você pode aprender mais sobre este módulo aqui

Neste artigo não vamos falar diretamente do cluster mode, mas de uma ferramenta que usa ele para prover uma série de recursos úteis que melhoram não só a performance como a saúde da aplicação.

PM2

PM2, ou Process Manager 2 é uma ferramenta indispensável na hora de colocar uma aplicação node em produção.

É possível destacar dois principais recursos do PM2, dentre vários outros.

Monitoramento do processo

O PM2 observa cada processo iniciado com ele, e caso o processo morra, ele mesmo reinicia o processo, sem a nenhuma intervenção humana.

Este recurso é extremamente útil para garantir que a aplicação não saia do ar caso alguma exceção seja disparada e não tratada.

Múltiplos processos por aplicação

Além de manter a aplicação viva, o PM2 consegue subir mais de um processo por aplicação. Dessa forma contornamos os problemas citados neste artigo.

Outra coisa interessante é poder subir mais de uma aplicação com o PM2. Se a aplicação http precisa de algum outro processo paralelo dando suporte, como um consumidor de filas, é possível ter total controle sobre isso.

PM2 em ação

Na documentação do PM2 é possível encontrar como se instala ele e todas as suas opções de configuração. Abaixo é possível ver o resultado do PM2 sendo utilizado em cluster mode, neste exemplo foram iniciados 5 processos.

pm2 start index.js -i 5
Enter fullscreen mode Exit fullscreen mode

siege cluster 5

Nestas condições o servidor http foi capaz de responder a 95 requisições em 10 segundos, um valor maior que os 18 do teste anterior.

Agora o mesmo teste com 10 processos.

pm2 start index.js -i 10
Enter fullscreen mode Exit fullscreen mode

siege cluster 10

Agora o serviço foi capaz de responder 180 requisições. O próximo exemplo será com 20 processos e em seguida com 40.

pm2 start index.js -i 20
Enter fullscreen mode Exit fullscreen mode

siege cluster 20

pm2 start index.js -i 40
Enter fullscreen mode Exit fullscreen mode

siege cluster 40

Com 20 foi possível dobrar a quantidade de requisições, porém, com 40 processos não. Isso acontece simplesmente porque os processos começam a concorrer cada vez mais pelo processador.

Ambiente de produção

Neste exemplo foi utilizado uma maquina com 8 núcleos de processamento e 13Gb de memória RAM. Estes valores são superiores a muitos servidores comuns. Por isso a simples escala de processos não é o suficiente, é importante ter isso em mente na hora de construir uma aplicação. Em muitos momentos é necessário utilizar de escala horizontal.

A quantidade de processos por núcleo de processamento do servidor é algo que varia de aplicação para aplicação, então o ideal é fazer testes e identificar como extrair o máximo da máquina sem deixa-la em "stress".

Soluções como auto scaling + docker são altamente recomendadas.

Conclusão

Mesmo Node.js sendo single-tread é possivel tirar proveito dos vários núcleos do processador. Há também um módulo para lidar com treads, elevando as possibilidades.

Este não é o único aspecto que deve ser levado em consideração quando se trabalha com aplicações Node.js, então não se limite a este artigo e as informações contidas aqui.

Performance pura e simplesmente não é tudo, código bem escrito e testado muitas vezes são mais importantes. Até a forma como a aplicação é colocada em produção é importante.

Ao focar em entregar algo de qualidade, seguindo boas práticas de escrita e organização muitas coisas relacionadas a performance são resolvidas logo no começo.


Se quiser saber mais sobre meu trabalho visite dev.to/codecasts ou blog.codecasts.com.br. Assine nosso canal no YouTube, lá você vai ver vídeos sobre JavaScript, jQuery, Gulp, ES6, Vue.JS e muito mais. Também não deixe de entrar em contato pelo nosso grupo no Telegram

Discussion (5)

Collapse
grruas profile image
gabriel ruas

Muito bom o texto, mano! 🤙🤙
Achei bem didático, ainda que conciso. Espero que continue fazendo mais textos sobre Node! 😁 Tô iniciando a aprender sobre o tema agora, e textos como este são sempre muito bem vindos

Collapse
victorhugorch profile image
Victor Hugo

Excelente artigo!

Collapse
rafaelgss profile image
Rafael Gonzaga

Muito bom!

Collapse
jgrossp profile image
Jessica Grosskopf • Edited

Parabéns pelo artigo !! E já seguindo!