Escalando Node.js Além de 10M de Requisições/Dia: O Que Realmente Importa
Todos os ArtigosDesenvolvimento

Escalando Node.js Além de 10M de Requisições/Dia: O Que Realmente Importa

Prixelo StudioPrixelo Studio
Jan 12, 2026 7 min

Escalando Node.js, a versão sem graça

A maioria dos artigos sobre "escalar Node.js" é escrita por pessoas que escalaram um único serviço até um milhão de requisições por dia e assumem que isso se generaliza. Nós operamos vários sistemas além de 10 milhões de requisições por dia, e as lições não são glamorosas.

Este é o playbook que gostaríamos de ter tido há quatro anos.

Pare de discutir sobre microsserviços

O erro mais caro que já vimos equipes cometerem é dividir em microsserviços antes de precisarem. Um monólito com limites de módulo bem definidos escala muito mais do que a maioria dos engenheiros espera, e é uma ordem de grandeza mais barato de operar.

Nossa regra: permaneça monolítico até que uma destas condições seja verdadeira.

  • Uma rota específica tem características de escala diferentes do restante (por exemplo, um endpoint pesado de inferência de ML).
  • Uma rota específica tem uma cadência de deploy diferente (por exemplo, código sujeito a compliance que precisa ser lançado mensalmente enquanto o restante é lançado diariamente).
  • A equipe já passou de 25 engenheiros e a disputa por merges é real.

Se nenhuma dessas condições for verdadeira, dividir em serviços adiciona custo operacional — service discovery, tracing distribuído, orquestração de deploy, modos de falha de rede — sem trazer nada que você não conseguiria com um monorepo bem organizado.

O verdadeiro gargalo é o banco de dados

Em todo projeto de escala de Node.js que já fizemos, o gargalo eventualmente se move para o banco de dados. CPU e memória nos serviços Node.js geralmente estão bem, porque o Node é bom em concorrência de I/O. Os problemas são:

  • Pools de conexão sem limite sobrecarregando o Postgres
  • Um único índice "quente" virando gargalo de escrita
  • Consultas N+1 em ORMs que parecem inofensivas na revisão de código
  • Transações longas segurando locks durante a lógica de negócio
  • Tráfego de leitura na instância primária em vez de uma read replica

Primeiros passos práticos:

  1. PgBouncer ou equivalente na frente do Postgres, em modo de pooling por transação, com limite de conexões baseado na CPU real do banco de dados, não na intuição do Node.js.
  2. Read replicas com roteamento explícito. Não confie no ORM para decidir. Tenha dois clientes no código: db.read e db.write. Obrigue cada desenvolvedor a escolher.
  3. Medição de tempo de consulta em produção. Pino + um logger de slow-query. Você quer um alerta no Slack quando qualquer consulta ultrapassar 200ms.
  4. EXPLAIN ANALYZE no CI para novas consultas. Adicionamos isso como uma verificação de PR em mudanças que tocam o schema.

Cache, em três camadas

O cache te salva nesta ordem: CDN, em memória, Redis. Pule qualquer camada e você vai se arrepender.

CDN. Tudo que pode ser cacheado na borda (edge) deve ser. Isso inclui respostas de API para leituras não autenticadas. Não tente ser esperto demais — headers de Cache-Control e uma CDN que os respeite resolvem 60% do tráfego de leitura de graça.

Em memória. Um pequeno cache LRU (usamos o lru-cache) na frente do Redis captura a cauda longa de requisições repetidas dentro de um único processo. Parece trivial; economiza de 30–50% do tráfego do Redis nos nossos perfis.

Redis. Para estado compartilhado e valores computados que sobrevivem entre processos. Use-o de forma intencional — cada chave precisa de um dono, um TTL e uma estratégia de invalidação documentada. Chaves de Redis sem rastreamento são a razão pela qual caches de produção acabam servindo dados de 6 meses atrás na Black Friday.

Torne assíncrono tudo o que puder ser assíncrono

Os caminhos de requisição síncronos devem fazer o mínimo: validar, persistir, responder. Qualquer outra coisa — e-mails, disparo de webhooks, processamento de imagens, chamadas a APIs de terceiros, indexação de busca — vai para uma fila.

Usamos BullMQ sobre Redis para quase tudo. Dois motivos:

  1. Isolamento de falhas. Uma API de terceiros instável não consegue derrubar seu caminho de requisição se a chamada acontece em um worker.
  2. Backpressure. Quando o tráfego dispara, a fila absorve o pico e os workers a esvaziam em um ritmo sustentável. Código síncrono sob pico simplesmente morre.

Exemplo concreto: um fluxo de checkout que reconstruímos para um cliente costumava disparar 6 chamadas de terceiros em linha (imposto, fraude, fulfillment, e-mail, SMS, analytics). O checkout mediano levava 1.4 segundos e o p99 chegava a 8 segundos quando um provedor ficava lento. Depois de mover tudo, exceto imposto e fraude, para workers do BullMQ, a mediana caiu para 220ms e o p99 se manteve abaixo de 600ms.

Observabilidade antes de arquitetura engenhosa

O instinto em escala é projetar sistemas engenhosos. A atitude certa é instrumentar o sistema que você já tem até conseguir ver exatamente onde o tempo está sendo gasto.

Nós padronizamos em:

  • Tracing com OpenTelemetry, exportado para Honeycomb ou Tempo. Toda requisição tem um trace ID. Toda chamada externa é um span.
  • Logs estruturados, em formato JSON, com o trace ID. Pino resolve bem.
  • Métricas RED por endpoint — Rate, Errors, Duration. Dashboards no Grafana. Alertas no p99, não no p50.
  • Verificações sintéticas de fora da sua rede, rodando a cada minuto contra os caminhos críticos.

Você não vai prever seus gargalos corretamente. Você vai medi-los. A equipe que entrega observabilidade primeiro ganha os próximos seis meses de trabalho de escala.

As armadilhas que já vimos se repetir

Uma lista curta de coisas que já vimos derrubar produção:

  • Logar demais, derrubar o pipeline de logs e, no fim, ficar sem logs.
  • Esquecer de configurar o --max-old-space-size do Node. O padrão é 1.5GB. Seu container tem 8GB. Faça as contas.
  • Escrever no banco de dados dentro de um loop com Promise.all sem limitar a concorrência. Esgotamento do pool de conexões no pior momento possível.
  • Health checks que passam mesmo quando o serviço está realmente quebrado. Teste a cadeia de dependências, não apenas o processo.
  • Confiar que um SDK de terceiros vai configurar os timeouts. Sempre envolva com o seu próprio.

A verdade nada glamorosa

Em 10M de requisições por dia, a diferença entre um sistema que funciona e um que não funciona raramente é genialidade arquitetural. É uma equipe que observa seus dashboards, corrige consultas lentas na semana em que aparecem, conduz revisões de incidentes com honestidade e resiste ao impulso de refatorar quando deveria estar instrumentando.

Acerte essa cultura e o Node.js vai aguentar muito mais do que as palestras em conferências sugerem. Esse é exatamente o tipo de trabalho que nossas equipes de backend e cloud e DevOps realizam para produtos em estágio de crescimento.

Partilha este artigo
Prixelo Studio

Prixelo Studio

Notes from the studio on craft, code, and product.