Escalando e performando aplicações Node.js
Tags: node, javascript, performance, ptbr
Published: 2019-09-14 21:13:41

Hoje Node.js é uma das [plataformas de desenvolvimento](https://insights.stackoverflow.com/survey/2018#technology-_-frameworks-libraries-and-tools) 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](https://libuv.org/)) , algumas operações são consideradas bloqueantes e travam toda e qualquer execução de código.

![https://repl.it/@vinicius73/blocking-code](https://thepracticaldev.s3.amazonaws.com/i/o7q9za0x0mwygtl4zjxu.png)

[Neste exemplo](https://repl.it/@vinicius73/blocking-code), usamos o [`console.time`](https://developer.mozilla.org/pt-BR/docs/Web/API/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`](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/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](https://medium.com/reactbrasil/como-o-javascript-funciona-o-event-loop-e-o-surgimento-da-programa%C3%A7%C3%A3o-ass%C3%ADncrona-5-maneiras-de-18d0b8d6849a).

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](https://developer.mozilla.org/pt-BR/docs/Web/JavaScript/Reference/Statements/for),  [Array.prototype.forEach](https://developer.mozilla.org/pt-BR/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach), [Array.prototype.map](https://developer.mozilla.org/pt-BR/docs/Web/JavaScript/Reference/Global_Objects/Array/map), [Array.prototype.reduce](https://developer.mozilla.org/pt-BR/docs/Web/JavaScript/Reference/Global_Objects/Array/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](https://thepracticaldev.s3.amazonaws.com/i/skh4o5658clij2tunz59.png)

Um servidor http extremamente simples, para testar sua performance a ferramenta [siege](https://github.com/JoeDog/siege) será utilizada.

```sh
siege http://localhost:7337/ -b -i -t 10s
```

![Siege Result Normal](https://thepracticaldev.s3.amazonaws.com/i/747hqp14qtmyigkew7jd.png)

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](https://thepracticaldev.s3.amazonaws.com/i/2w57e0o8w2tu7790lo8d.png)

Evidentemente este é um ótimo resultado.

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

![blocking route](https://thepracticaldev.s3.amazonaws.com/i/4uk59e7sjitghqjd89wm.png)

Novamente o teste de performance com o siege.

![siege result blokcing](https://thepracticaldev.s3.amazonaws.com/i/bunflaq4lw56bu472kcv.png)

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](https://thepracticaldev.s3.amazonaws.com/i/ybtj3ppt1cew1bf68mj7.png)

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_](https://nodejs.org/api/cluster.html). Este módulo permite criar forks do seu processo/servidor, atuando como um [_load balancer_](https://pt.wikipedia.org/wiki/Balanceamento_de_carga).

> Você pode aprender mais sobre este módulo [aqui](https://www.infoq.com/br/articles/nodejs-utilizando-modulo-de-cluster/)

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_](http://pm2.keymetrics.io/) é 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.

```sh
pm2 start index.js -i 5
```


![siege cluster 5](https://thepracticaldev.s3.amazonaws.com/i/46nj8z8zakij0huhvk7k.png)

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.

```sh
pm2 start index.js -i 10
```

![siege cluster 10](https://thepracticaldev.s3.amazonaws.com/i/9nxi84b6yun5nbqkdk1k.png)

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

```sh
pm2 start index.js -i 20
```

![siege cluster 20](https://thepracticaldev.s3.amazonaws.com/i/r58defwwu04urzek4zxf.png)

```sh
pm2 start index.js -i 40
```

![siege cluster 40](https://thepracticaldev.s3.amazonaws.com/i/hsxzzxszro94iuq7tfmo.png)

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](https://medium.com/@pedrro/escala-ou-n%C3%A3o-escala-escalabilidade-101-parte-1-d43fe1e3143e).

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](http://comunidadecloud.com/post/auto-scaling/)_ + [docker](https://www.redhat.com/pt-br/topics/containers/what-is-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_](https://nodejs.org/api/worker_threads.html), 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**](https://dev.to/codecasts) ou  [**blog.codecasts.com.br**](https://blog.codecasts.com.br/). Assine nosso [canal no YouTube](https://www.youtube.com/channel/UCTluPqMkm90zyw6mCde561A), lá você vai ver vídeos sobre [JavaScript](https://www.youtube.com/watch?v=fBInMy61plk&t=0s&list=PLy5T05I_eQYNQs4Pta85XRSucm3IOHx2M&index=2), [jQuery](https://www.youtube.com/playlist?index=1&list=PLy5T05I_eQYN8T15w4KLLcDcjAmIXDyu-&playnext=1), [Gulp](https://www.youtube.com/watch?v=eukZI7Rcrss&t=0s&index=2&list=PLy5T05I_eQYPl_iF2aJ0T0JBgJMatOLwv), [ES6](https://www.youtube.com/watch?v=yRr8Wo4XfYY&list=PLy5T05I_eQYOoUz2TtAqq35RLCc-xBZCe&index=2&t=0s), [Vue.JS](https://www.youtube.com/watch?v=ck8uNY0B0HI&index=2&t=0s&list=PLy5T05I_eQYOr3uPSI-eTVCLe1GF1qOf5) e muito mais. Também não deixe de entrar em contato pelo nosso grupo no [Telegram](https://t.me/codecasters)