"Não bloqueie o loop de eventos..." - provavelmente já ouviu esta frase muitas vezes... Não me surpreende porque é uma das premissas mais importantes quando se trabalha com Node. Mas há também uma segunda "coisa" que não deve ser bloqueada - o Worker Pool. Se for negligenciado, pode ter um impacto significativo no desempenho da aplicação e até na sua segurança.
Fios
O principal aspeto a ter em conta: existem dois tipos de linhas em Node.js: Main Thread - que é gerido por Ciclo de eventose Pool de trabalhadores (pool de threads) - que é o pool de threads -
graças à libuv. Cada um deles tem uma função diferente. O objetivo do primeiro é lidar com operações de E/S não bloqueantes, e o segundo é responsável pelo trabalho intensivo da CPU e também pelo bloqueio de E/S.

Mas o que é uma linha e em que é que é diferente de um processo? Existem várias diferenças, mas a mais importante para nós é a forma como a memória lhes é atribuída. Pode pensar num processo como se fosse uma aplicação. Dentro de cada processo, há um pedaço de memória dedicado apenas a esse processo. Assim, um processo não tem acesso à memória do segundo, e essa propriedade garante alta segurança. Para estabelecer comunicação entre eles, temos que fazer algum trabalho. Threads são diferentes. As threads são executadas dentro de um processo e partilham a mesma memória, pelo que não há qualquer problema com as threads que partilham dados.
No entanto, há uma questão que causa um problema. É a chamada condição de corrida. As threads podem ser executadas ao mesmo tempo, por isso como é que sabemos qual termina primeiro? Pode acontecer que, na primeira vez que a executamos, a primeira operação termine primeiro e, na vez seguinte, pode acontecer o contrário e a segunda operação terminar antes da primeira. Imagine trabalhar com operações de escrita/leitura nestas condições! Um pesadelo! Por vezes é muito difícil escrever corretamente código num ambiente multi-threaded.
Além disso, as linguagens multithread têm uma grande sobrecarga de memória porque criam um thread separado para cada pedido; assim, se quiser chamar 1000 pedidos, criam 1000 threads.
Como lidar com este problema? Utilizar um único fio! E é isso que Nó oferece-lhe.

Como JavaScript criador Encorajo-vos a ver o filme
em que Bart Belder explica claramente o conceito de ciclo de eventos. O diagrama acima foi retirado da sua apresentação. E se não conhece estes termos, ambos Nó e a Libuv tem uma excelente documentação 🙂
Sobre o bloqueio
Em Desenvolvimento do JavaScript indústria dizem que porque Nó é single-threaded e não-bloqueante, é possível obter uma maior simultaneidade com os mesmos recursos do que com soluções multi-threaded. É verdade, mas não é tão bonito e fácil como pode parecer.
Desde Node.js é single-threaded (parte JS), as tarefas com uso intensivo de CPU bloquearão todos os pedidos em andamento até que a tarefa específica seja concluída. Então, é verdade que em Node.js pode bloquear todos os pedidos só porque um deles tinha uma instrução de bloqueio no seu interior. O código de bloqueio significa que demora mais do que alguns milissegundos a terminar. Mas não confunda tempo de resposta longo com bloqueio. A resposta da base de dados pode demorar muito tempo, mas não bloqueia o seu processo (aplicação).
Os métodos bloqueantes são executados de forma síncrona e os métodos não bloqueantes são executados de forma assíncrona.
Como é que pode abrandar (ou bloquear) o seu ciclo de eventos?
- regex vulnerável - uma expressão regular vulnerável é aquela em que o seu motor de expressão regular pode demorar um tempo exponencial; pode ler mais sobre elas aqui,
- Operações JSON em objectos de grandes dimensões,
- utilizando APIs síncronas do Nó em vez de versões assíncronas; todos os métodos de E/S da biblioteca normalizada do Node.js fornecem também as suas versões assíncronas,
- outros erros de programação, como os loops infinitos síncronos.
Nesse caso, uma vez que o Worker Pool utiliza um conjunto de threads, é possível bloqueá-las também? Infelizmente, sim 🙁 Nó baseia-se numa filosofia um thread para muitos clientes. Vamos supor que uma determinada tarefa executada por um Worker específico é muito complexa e precisa de mais tempo para ser concluída. Como resultado, o Worker fica bloqueado e não pode ser utilizado para executar qualquer uma das outras tarefas pendentes até que as suas instruções sejam executadas. Como já deve ter adivinhado, isso pode afetar o desempenho. É possível evitar esses problemas minimizando a variação nos tempos das tarefas usando o particionamento de tarefas.
Conclusão
Evite o bloqueio, isso é certo. Se puder, escolha sempre versões assíncronas das APIs da biblioteca padrão. Caso contrário, depois de executar a sua aplicação, o cliente pode ter vários problemas, começando com a degradação do débito e terminando com a desistência total, o que é fatal do ponto de vista do utilizador.
Ler mais:
Porque é que deve (provavelmente) utilizar Typescript
Como não matar um projeto com más práticas de codificação?
Estratégias de obtenção de dados no NextJS