Erros concorrência Node.js: 11 falhas que degradam apps
Concorrência em Node.js é traiçoeira: o event loop engana, o shared state corrompe e as promises escondem falhas. Listo os 11 erros que vejo em produção e como evitá-los.
Concorrência em Node.js é traiçoeira: o event loop engana, o shared state corrompe e as promises escondem falhas. Listo os 11 erros que vejo em produção e como evitá-los.
Concorrência em Node.js parece simples até o dia em que dois usuários disputam o mesmo recurso. O event loop processa tudo em uma thread, mas isso não elimina race conditions, deadlocks ou corrupção de estado. Se você chegou até aqui buscando "erros concorrencia nodejs", provavelmente já sentiu a dor. Abaixo, listo os 11 erros que mais degradam aplicações reais, do mais crítico ao menos óbvio.
1. Bloquear o event loop com operações síncronas
Operações como fs.readFileSync, crypto.randomBytesSync ou loops pesados travam a thread inteira. Enquanto rodam, nenhuma outra requisição é processada. Em produção, um único JSON.parse gigante pode derrubar o throughput. Prefira versões assíncronas ou delegue a worker threads.
2. Assumir que callbacks são executados em ordem
Callbacks de eventos I/O não garantem ordem de chegada. Se você processa filas de mensagens e assume que a primeira chegou primeiro, vai corromper dados. Use contadores ou timestamps para ordenar, nunca a ordem de invocação.
3. Compartilhar estado mutável entre requisições
Variáveis globais ou módulos com estado são armadilhas. Duas requisições simultâneas podem ler e escrever o mesmo objeto, causando respostas inconsistentes. Cada requisição deve ter seu próprio contexto ou usar estruturas imutáveis.
4. Ignorar race conditions em operações de banco
O clássico: dois usuários reservam o mesmo assento. Se a verificação e a atualização não forem atômicas, ambos passam. Use transações, locks pessimistas ou operações atômicas como findOneAndUpdate com condição.
5. Usar Promise.all sem tratamento de rejeição
Se uma promise rejeita, o Promise.all inteiro falha, mas as outras continuam executando. Isso gera efeitos colaterais inesperados e vazamentos. Use Promise.allSettled quando quiser que todas completem, ou trate cada rejeição individualmente.
6. Esquecer que async/await não paraleliza
await em sequência é mais lento que Promise.all para operações independentes. Muita gente escreve await fetchA(); await fetchB(); e perde performance sem necessidade. Para tarefas independentes, dispare as promises primeiro e aguarde depois.
7. Não limitar concorrência em operações pesadas
Lançar 10.000 promises simultâneas para acessar uma API externa derruba o servidor ou o provedor. Use bibliotecas de limite de concorrência ou filas com pool. Um semáforo simples resolve.
8. Deadlocks com locks manuais
Se você usa locks ou mutexes, cuidado com a ordem de aquisição. Dois processos podem segurar locks que o outro precisa, travando para sempre. Estabeleça uma ordem global de locks ou use timeouts.
9. Variáveis de ambiente e configuração mutável
Configurações lidas em runtime e alteradas por uma requisição afetam as demais. Isso causa comportamento não determinístico em produção. Carregue configurações no boot e trate como imutáveis.
10. Não testar cenários de concorrência
Testes unitários não pegam race conditions. Sem testes de integração que simulam múltiplos usuários simultâneos, você só descobre o problema em produção. Use ferramentas de stress test e testes de concorrência.
11. Ignorar backpressure em streams
Streams que produzem mais rápido que consomem acumulam dados na memória. Sem controle de backpressure, o processo estoura. Use pipe ou pipeline que lidam com isso automaticamente, ou implemente pausa/resumo manual.
Fechamento: por onde começar
Se você tem um app legado, comece auditando o event loop e o uso de operações síncronas. Depois, revise o compartilhamento de estado e as operações de banco. Priorize os erros 1, 3 e 4, que são os que mais derrubam aplicações em produção. Para apps novos, desenhe com atomicidade e filas desde o início.
FAQ
O que causa race condition em Node.js?
Race condition ocorre quando duas ou mais operações concorrentes acessam o mesmo dado sem sincronização, e o resultado depende da ordem de execução. Em Node.js, isso é comum com estado compartilhado entre requisições ou operações de banco não atômicas.
Como evitar deadlock em Node.js?
Evite locks manuais sempre que possível. Se precisar usar, estabeleça uma ordem global de aquisição e use timeouts. Prefira operações atômicas do banco a locks em memória.
O que é o event loop e como ele afeta concorrência?
O event loop é o mecanismo que processa operações assíncronas em uma única thread. Ele não executa código em paralelo, mas intercala tarefas. Bloqueá-lo com código síncrono impede o processamento de outras requisições.
Node.js é single-threaded?
Sim, o JavaScript em Node.js roda em uma única thread para código de usuário. Porém, operações I/O delegam para threads do sistema. Concorrência é alcançada por assincronismo, não por paralelismo.
Como testar concorrência em Node.js?
Use ferramentas de stress test como autocannon ou k6 para simular múltiplos usuários. Escreva testes que disparam várias operações simultâneas e verificam a consistência do resultado.
Qual a diferença entre concorrência e paralelismo?
Concorrência é a capacidade de lidar com múltiplas tarefas ao mesmo tempo, intercalando-as. Paralelismo é executar tarefas simultaneamente em múltiplas threads. Node.js é concorrente, mas não paralelo para código JS puro.
Letícia Sampaio Khoury
Editora de Gadgets e Consumo Tech
Testa o gadget no dia a dia real, avalia se vale a grana sem deslumbre de novidade.
Ver todos os artigos →