A pergunta chega no mesmo formato de sempre: quanto de memória e processamento uma aplicação Node.js precisa?
E, como em qualquer dimensionamento, ela não tem resposta útil sem uma informação que só quem conhece a aplicação tem. Duas APIs com o mesmo número de requisições por segundo podem exigir servidores completamente diferentes — porque o que consome recursos não é a quantidade de chamadas, e sim o que acontece dentro de cada uma delas.
Este guia mostra o que medir, como interpretar os números e como convertê-los em configuração. Ele pressupõe conhecida a arquitetura de produção — supervisor, proxy reverso, múltiplas instâncias —, descrita no artigo sobre aplicações Node.js em produção.
A distinção que organiza tudo
Antes de qualquer medição, é preciso classificar a aplicação em uma de duas categorias. Praticamente todas as decisões seguintes dependem disso.
Aplicações limitadas por espera passam a maior parte do tempo aguardando: consultas ao banco, chamadas a APIs externas, leitura de disco. O processador fica ocioso durante essa espera.
Aplicações limitadas por processamento fazem trabalho de fato: transformam dados, geram documentos, processam imagens, executam cálculos, serializam grandes estruturas.
A consequência prática é oposta em cada caso:
| Limitada por espera | Limitada por processamento | |
|---|---|---|
| Gargalo típico | Conexões e memória | Processador |
| Concorrência suportada | Alta | Baixa |
| Efeito de mais núcleos | Moderado | Direto |
| Efeito de mais memória | Relevante | Depende |
| Estratégia de escala | Mais conexões e ajuste | Mais instâncias ou fila |
| Risco principal | Esgotar conexões de banco | Bloquear a fila de execução |
A maior parte das APIs de negócio é limitada por espera — e é por isso que o Node.js costuma ir bem nelas. A armadilha aparece quando uma rota específica faz trabalho pesado dentro de uma aplicação que, no geral, apenas espera.
O que medir
Seis números, todos obtidos com a aplicação rodando sob carga real ou simulada:
- Memória por instância em regime. Não o valor logo após iniciar, mas o consumo estabilizado depois de horas atendendo requisições.
- Memória em pico, durante a operação mais pesada que a aplicação executa.
- Requisições simultâneas no pico, e não o total diário.
- Tempo de resposta por rota, com atenção às mais lentas — elas indicam onde está a espera ou o processamento.
- Atraso na fila de execução, que é o indicador mais específico do Node.js e merece explicação própria.
- Conexões abertas com o banco de dados, somadas entre todas as instâncias.
Os itens 1 e 2 juntos revelam algo importante: se a memória em regime cresce continuamente sem estabilizar, existe um vazamento — e nesse caso dimensionar é adiar o problema, não resolvê-lo. Uma aplicação saudável estabiliza em um patamar e oscila em torno dele.
O indicador que só existe no Node.js
O atraso na fila de execução merece seção própria porque é o indicador mais útil e o menos conhecido.
Como o código da aplicação roda em uma única linha de execução, existe uma fila de tarefas a processar. Quando tudo está saudável, essa fila é percorrida quase instantaneamente. Quando alguma operação demora a liberar a linha, as demais tarefas esperam — e esse tempo de espera é mensurável.
Por que ele importa mais que processador e memória:
- Ele detecta bloqueio. Uma operação síncrona pesada trava a aplicação inteira, e isso não aparece necessariamente como processador saturado.
- Ele antecede a degradação percebida. O atraso cresce antes de os tempos de resposta ficarem visivelmente ruins.
- Ele diferencia causas. Atraso alto com processador baixo indica bloqueio; atraso alto com processador saturado indica falta de capacidade.
Se você monitorar apenas um indicador além de memória, monitore este.
Quantas instâncias
A pergunta prática que segue o diagnóstico.
Como uma instância usa efetivamente um núcleo para executar o código da aplicação, aproveitar um servidor com vários núcleos exige rodar várias instâncias e distribuir a carga entre elas.
O ponto de partida usual é uma instância por núcleo disponível, com dois ajustes:
- Reserve capacidade para o resto. O sistema operacional, o proxy reverso e, se estiver no mesmo servidor, o banco de dados também precisam de processamento.
- Multiplique pela memória. Cada instância carrega a aplicação inteira. Instâncias vezes memória por instância precisa caber com folga na memória do servidor — é a mesma conta que derruba servidores em qualquer stack.
Duas armadilhas específicas:
Instâncias demais em servidor com pouca memória. Quando ela esgota, o sistema operacional encerra processos, e o resultado são falhas intermitentes sem padrão aparente. É a causa mais comum de instabilidade em aplicações recém-publicadas.
Estado local entre requisições. Se a aplicação guarda algo em memória esperando encontrá-lo na requisição seguinte — sessão, cache local, contador —, distribuir a carga entre instâncias quebra esse comportamento de forma intermitente e difícil de diagnosticar. Rodar várias instâncias exige que a aplicação não dependa de estado local.
O limite de memória por instância
Um detalhe técnico com consequência prática direta e frequentemente ignorado.
O ambiente de execução do Node.js aplica um limite próprio à memória usada pela aplicação, independente da memória disponível no servidor. Quando a aplicação atinge esse limite, ela encerra com erro de memória — mesmo com o servidor tendo memória livre.
O sintoma é característico e confunde: a aplicação morre por falta de memória enquanto o servidor mostra memória sobrando.
O que fazer:
- Verificar o limite efetivo no ambiente em uso, que varia conforme a versão e a configuração.
- Ajustá-lo conscientemente quando a aplicação legitimamente precisa de mais — e não como contorno para um vazamento.
- Dimensionar o servidor considerando esse limite, já que ele é o teto real por instância.
- Investigar antes de aumentar. Aplicações que consomem muita memória frequentemente estão carregando dados inteiros quando poderiam processá-los em partes.
O último ponto é o mais econômico: processar arquivos ou respostas grandes em fluxo, em vez de carregá-los inteiros na memória, costuma reduzir o consumo em ordens de grandeza.
Conexões com o banco de dados
O limite que trava aplicações que pareciam saudáveis.
Cada instância mantém suas próprias conexões com o banco. O total é o número de instâncias multiplicado pelas conexões que cada uma abre — e esse total precisa caber no limite configurado no banco de dados.
O sintoma característico: a aplicação funciona bem até certo volume e então passa a falhar, com erros de conexão, mesmo com processador e memória confortáveis.
A solução é um agrupador de conexões, que mantém um conjunto controlado e reutilizado em vez de abrir uma nova a cada necessidade. É configuração de infraestrutura e não de código, e é obrigatória em aplicações com múltiplas instâncias.
Quando o problema não é capacidade
Vale delimitar, porque dimensionar é caro e nem sempre resolve.
Operações síncronas pesadas. Uma única operação que ocupa a linha de execução por tempo relevante trava todas as requisições enquanto dura. Adicionar memória não muda isso — a correção é mover o trabalho para outro lugar.
Trabalho que deveria estar em fila. Geração de relatórios, processamento de imagens, envio em volume, chamadas a serviços lentos. Durante o ciclo da requisição, essas operações ocupam capacidade que deveria atender usuários.
Consultas mal construídas. Uma consulta lenta ao banco não melhora com mais instâncias da aplicação — ela apenas passa a ser executada em paralelo, piorando a carga do banco.
Vazamento de memória. Já mencionado, e vale repetir: dimensionar um vazamento apenas aumenta o intervalo entre reinícios.
A regra prática: antes de aumentar recursos, verifique se o atraso na fila de execução está alto com processador baixo. Se estiver, o problema é bloqueio, e mais servidor não resolve.
Como testar antes de decidir
Dimensionar sem teste é estimar. Um roteiro que produz números confiáveis:
- Reproduza o perfil real de uso, e não apenas a rota mais simples. Se 80% das chamadas vão a uma rota específica, o teste precisa refletir isso.
- Inclua as rotas pesadas na proporção em que elas ocorrem.
- Aumente a carga gradualmente, observando onde os tempos de resposta começam a degradar.
- Observe qual recurso satura primeiro: memória, processador, conexões de banco ou fila de execução. Essa é a informação que orienta a correção.
- Teste com o número de instâncias que você pretende usar, e não com uma só.
- Repita depois de ajustar, porque cada correção muda o ponto de saturação.
O passo 4 é o que transforma o teste em decisão. Saber que a aplicação degrada em determinado volume é pouco útil; saber o que saturou primeiro indica exatamente onde investir.
Quando escalar horizontalmente
Existe um ponto em que ajustar o servidor deixa de bastar.
Sinais de que chegou:
- O servidor está bem dimensionado e ainda satura em picos previsíveis.
- A indisponibilidade tem custo alto, e um único servidor é ponto único de falha.
- O crescimento é rápido o suficiente para que redimensionar vire rotina.
- A operação exige atualizar sem interrupção.
Escalar horizontalmente significa distribuir a carga entre servidores, com um balanceador na frente. Exige que a aplicação não guarde estado local, que as sessões sejam compartilhadas e que arquivos enviados fiquem em armazenamento comum — decisões de arquitetura, não de infraestrutura.
Vale a mesma ressalva feita no comparativo entre VPS e Cloud Server: redundância de servidores é uma decisão de projeto com custo próprio, e não algo que o tipo de hospedagem entrega sozinho.
Conclusão
Dimensionar um servidor para Node.js começa por uma classificação: a aplicação espera ou processa? A resposta muda o gargalo esperado, a concorrência suportada e a estratégia de escala.
A partir daí, seis medições resolvem: memória em regime e em pico, requisições simultâneas, tempo por rota, atraso na fila de execução e conexões de banco. Duas delas são específicas e frequentemente ignoradas — o atraso na fila, que detecta bloqueio antes da degradação aparecer, e o limite próprio de memória, que derruba a aplicação com o servidor mostrando memória livre.
E antes de contratar mais recursos, vale a verificação que economiza dinheiro: se o atraso na fila está alto com processador baixo, o problema é bloqueio de código — e nenhum servidor resolve isso. Conheça o Cloud Server para Node.js da TBF Host e avalie a configuração a partir dos números que você mediu.
Perguntas frequentes
Quanto de memória uma aplicação Node.js precisa?
Depende do perfil da carga, não do tráfego. É preciso medir o consumo em regime, depois de horas atendendo requisições, e em pico, durante a operação mais pesada. Se o consumo em regime cresce continuamente sem estabilizar, existe um vazamento — e nesse caso dimensionar apenas adia o problema.
Quantas instâncias da aplicação devo rodar?
O ponto de partida usual é uma por núcleo disponível, reservando capacidade para o sistema, o proxy reverso e, se estiver no mesmo servidor, o banco de dados. O número de instâncias multiplicado pela memória de cada uma precisa caber com folga na memória do servidor.
Minha aplicação Node.js morre por falta de memória mas o servidor tem memória livre. Por quê?
Porque o ambiente de execução aplica um limite próprio à memória usada pela aplicação, independente da memória disponível no servidor. Ao atingir esse limite, o processo encerra com erro. Vale verificar o limite efetivo no seu ambiente e investigar o consumo antes de simplesmente aumentá-lo.
O que é atraso na fila de execução e por que monitorá-lo?
É o tempo que as tarefas esperam para serem processadas, já que o código da aplicação roda em uma única linha de execução. É o indicador mais útil do Node.js porque detecta bloqueio — atraso alto com processador baixo indica operação síncrona pesada travando a aplicação, o que mais servidor não resolve.
Por que minha aplicação falha depois de certo volume de acessos?
Uma causa frequente é o esgotamento do limite de conexões do banco de dados. Cada instância mantém suas próprias conexões, e o total é o número de instâncias multiplicado pelas conexões de cada uma. Quando isso ultrapassa o limite do banco, a aplicação falha mesmo com processador e memória confortáveis.
Posso rodar várias instâncias de qualquer aplicação Node.js?
Só se ela não depender de estado local entre requisições. Se a aplicação guarda sessão, cache ou contadores em memória esperando encontrá-los na chamada seguinte, distribuir a carga entre instâncias quebra esse comportamento de forma intermitente e difícil de diagnosticar.
Como testar a carga de uma aplicação Node.js?
Reproduzindo o perfil real de uso, com as rotas pesadas na proporção em que ocorrem, aumentando a carga gradualmente e observando qual recurso satura primeiro — memória, processador, conexões de banco ou fila de execução. Essa última informação é o que orienta onde investir.
Quando devo escalar para mais de um servidor?
Quando o servidor está bem dimensionado e ainda satura em picos previsíveis, quando a indisponibilidade tem custo alto e um servidor único é ponto de falha, quando o crescimento torna o redimensionamento uma rotina, ou quando é preciso atualizar sem interrupção. Exige aplicação sem estado local.