NestJS consumindo muita memória na VPS: como investigar antes de aumentar o plano

Servidor VPS com gráfico de consumo de memória representando aplicação NestJS

Fala aí, beleza? Tua API NestJS está lá na VPS, o painel mostra a RAM lá em cima, a aplicação começa a travar ou reinicia do nada… e bate aquela dúvida: corrijo o código ou contrato mais memória?

Aqui o recorte é esse cenário: API NestJS rodando direto com Node, com PM2 ou dentro de container Docker

A ideia é separar três suspeitos antes de qualquer decisão: vazamento no código, container sem limite (ou com limite mal configurado) e falta real de recurso. Cada um tem sintoma, causa e checagem diferentes 🙂

Como medir a memória real do processo NestJS

A resposta curta: meça o processo Node, não só a memória total do sistema

O sintoma clássico é o painel da VPS mostrando RAM alta sem você saber quanto daquilo é da API. O erro está em olhar só o número geral, que mistura banco, cache, sistema operacional e por aí vai

Por dentro do Node, o caminho é o process.memoryUsage(). Segundo a documentação de memória do Node.js, ele retorna um objeto com três campos que importam aqui:

  • rss (Resident Set Size): memória total alocada ao processo
  • heapTotal: memória total alocada para o heap
  • heapUsed: memória em uso de fato dentro do heap

Um exemplo que loga esses valores em MB de tempos em tempos:

const toMB = (bytes: number) => Math.round(bytes / 1024 / 1024);

setInterval(() => {
  const { rss, heapTotal, heapUsed } = process.memoryUsage();
  console.log(`rss=${toMB(rss)}MB heapTotal=${toMB(heapTotal)}MB heapUsed=${toMB(heapUsed)}MB`);
}, 60_000);

Se a API roda no PM2, o pm2 monit mostra em tempo real os processos gerenciados por ele

pm2 monit

Se roda em container, o docker stats mostra o uso de recursos ao vivo, e com --no-stream ele devolve uma leitura única (ótimo pra anotar)

docker stats
docker stats --no-stream

Checagem: anote as leituras em momentos diferentes (logo após subir, em pico de uso, em horário calmo). Isso vira tua linha de base, e sem ela qualquer decisão é chute

Como prevenir: não meça só na crise. A documentação de deploy do NestJS sugere acompanhar desempenho e saúde da aplicação com ferramentas como Prometheus, New Relic ou o monitoramento nativo do provedor (ex.: AWS CloudWatch)

Se o servidor ainda está naquela fase de montar tudo, vale passar antes pelo guia de configurar a VPS do zero pra ter a base organizada

Container sem limite de memória ou derrubado pelo OOM killer

Se o container some ou reinicia sem erro claro da aplicação, ou se ele está comendo a RAM do host inteiro, o problema provavelmente está nos limites do Docker

Se liga: segundo a documentação de limites de recursos do Docker, por padrão um container não tem restrição de recursos e pode usar tanta memória quanto o scheduler do kernel do host permitir

E quando existe limite e ele é ultrapassado, o kernel pode encerrar o processo via OOM killer. Por isso a API "morre do nada": quem matou não foi o Node, foi o kernel

As opções principais:

  • --memory: limite rígido de memória
  • --memory-reservation: limite suave, menor que o --memory, ativado quando o Docker detecta contenção ou pouca memória no host
  • --memory-swap: controla o uso de swap junto com o limite de memória

Os valores aceitam os sufixos b, k, m e g. Um formato de exemplo (troque os valores pelo que tua linha de base indicar):

docker run --memory=<limite> --memory-reservation=<limite-menor> minha-api

Outro detalhe importante: desde o Node.js 20, o tamanho máximo do heap considera os limites de cgroup do container, como explica um artigo da Red Hat Developer. Ou seja, o limite que você define no container conversa direto com o heap do Node

Checagem: rode docker stats de novo e confirme que o uso de memória do container está dentro do limite que você configurou

Erro comum: rodar sem limite e culpar a VPS quando, na real, o container sozinho ocupou toda a memória do host

Como prevenir: defina um limite explícito e coerente com a RAM do plano, deixando espaço pros outros serviços. Se tua stack sobe com Compose, o passo a passo de API NestJS com Docker Compose ajuda a organizar isso

heapUsed só sobe: como confirmar um vazamento de memória

Quando o heapUsed cresce de forma contínua, sem nunca ser liberado, mesmo com carga estável, a documentação do Node.js aponta isso como possível vazamento de memória

E aí não adianta só olhar número, tem que ver O QUE está ocupando o heap

Que ferramenta pra isso? O heap snapshot, uma fotografia da memória do processo. Segundo a documentação de heap snapshot do Node.js, ele pode ser carregado no Chrome DevTools pra inspecionar variáveis e o retainer size, e vários snapshots podem ser comparados pra ver o que mudou ao longo do tempo

Uma das formas oficiais de gerar o snapshot é por sinal externo, usando uma flag na inicialização:

node --heapsnapshot-signal=SIGUSR2 app.js

Com essa flag ativa (disponível a partir da v12.0.0), o Node gera o heap snapshot sempre que o processo recebe o sinal configurado (SIGUSR2, nesse exemplo). Como enviar esse sinal ao PID do processo depende das ferramentas do teu sistema operacional

Existem outras formas oficiais também: pelo inspector, por chamada writeHeapSnapshot dentro do processo ou pelo protocolo do inspector

Erro comum (Tome cuidado!): tirar snapshot em produção sem margem de memória. O alerta oficial é que gerar o snapshot trava todo o trabalho da thread principal (pode passar de um minuto, dependendo do heap) e, como ele é montado em memória, pode dobrar o tamanho do heap, encher a RAM e derrubar a aplicação

Sim, a ferramenta de diagnóstico pode causar justamente o crash que você está investigando haha

Checagem: compare pelo menos dois snapshots separados por um tempo de uso. O que cresce de um pro outro e não some é teu principal suspeito

Como prevenir: reproduza o problema em ambiente de teste ou faça isso em horário de baixo tráfego

Limite de heap e reinício automático: paliativos, não correção

Precisa estabilizar a API enquanto investiga? Dá pra segurar as pontas com dois recursos, mas nenhum deles corrige vazamento

O primeiro é a flag --max-old-space-size, que limita o Old Space do heap do V8 (onde ficam os objetos de vida longa). O exemplo da documentação do Node usa 4096 MB, e você ajusta o valor à RAM que tem disponível

node --max-old-space-size=4096 app.js

O segundo é o max_memory_restart do PM2, que recarrega a aplicação ao atingir um limite de memória (se não estiver em modo cluster, vira um restart). Segundo a documentação de limite de memória do PM2, o worker que verifica a memória roda a cada 30 segundos e aceita as unidades K, M e G

pm2 start api.js --max-memory-restart 300M

Erro comum: usar o restart automático como solução definitiva. A API fica "de pé", o vazamento continua lá e você só para de enxergar o problema

Como prevenir: registre quantas vezes o restart dispara e trate isso como alerta, não como rotina. Se ele dispara toda hora, volta pra seção do heap snapshot

Quando o problema é falta de recurso e vale aumentar o plano

Vale aumentar o plano quando a memória está estável (heapUsed sem crescimento contínuo), os limites do container estão corretos e mesmo assim a carga normal não cabe na RAM

Isso acontece muito quando app, banco de dados e outros serviços dividem o mesmo servidor. Não é bug, é a conta que não fecha

Aí sim o dimensionamento entra, e com base na linha de base que você mediu, não no chute

Para esse cenário, a recomendação principal da Hora de Codar são os planos de VPS da Hostinger (este artigo contém links de afiliado da Hostinger). A escada de RAM dos planos KVM é clara:

Plano vCPU RAM Armazenamento NVMe
KVM 1 1 4 GB 50 GB
KVM 2 2 8 GB 100 GB
KVM 4 4 16 GB 200 GB
KVM 8 8 32 GB 400 GB

O porquê técnico da recomendação:

  • Acesso root: você configura o ambiente conforme a API precisa e aplica tudo o que viu aqui, limites de container, flags do Node e monitoramento
  • Processadores AMD EPYC e armazenamento NVMe SSD nos planos de VPS
  • Backups semanais gratuitos incluídos
  • Oferta específica de VPS para Docker, pra quem já roda a API em container

Um ponto que não dá pra esquecer: mais RAM resolve falta de recurso, mas não resolve container sem limite nem vazamento, isso continua sendo trabalho de diagnóstico

Se a tua linha de base mostra que a carga atual estoura o plano, o próximo degrau da tabela dobra a RAM. Escolha olhando o pico medido, com folga pros serviços que dividem a máquina

Conteúdo extra do canal

Pra quem curte acompanhar o Hora de Codar, fica aqui um conteúdo complementar do canal: um vídeo sobre o Hermes Desktop, um app que roda agente de IA local ou numa VPS 😀

Conteúdo complementar do Hora de Codar para conhecer o canal; não é uma demonstração específica desta notícia ou versão.

Próximo passo: crie a linha de base antes de qualquer decisão

Bora pro prático: registre rss e heapUsed por alguns dias, em horários diferentes, e defina um limite de memória explícito no container

Com esses dados na mão, a decisão fica fácil: heapUsed subindo sem parar é caso de heap snapshot e correção de código, memória estável estourando a RAM é caso de plano maior

Aproveita e confere os pré-requisitos que a doc de deploy do NestJS lista: aplicação funcionando, variáveis de ambiente configuradas, serviços como banco de dados prontos e pelo menos uma versão LTS do Node.js no servidor. Muita dor de cabeça em produção começa num desses itens

E se a conclusão foi mesmo que falta RAM, dá pra conferir a VPS para este projeto e escolher o plano a partir do pico que você mediu

Até o próximo post! =)

Perguntas frequentes

Como saber se o problema é vazamento de memória ou falta de recurso na VPS?

Acompanhe o heapUsed do processo Node ao longo do tempo com process.memoryUsage(). Se ele cresce de forma contínua e nunca é liberado, mesmo com carga estável, isso indica possível vazamento. Se a memória fica estável mas a carga normal já não cabe na RAM disponível, é falta real de recurso.

Como medir a memória real de uma API NestJS em produção?

Use process.memoryUsage() dentro do código Node para ver rss, heapTotal e heapUsed. Se a API roda no PM2, o comando pm2 monit mostra os processos em tempo real. Se roda em container, docker stats (ou docker stats –no-stream para uma leitura única) mostra o uso de recursos do container.

Por que o container Docker reinicia ou é encerrado sem erro aparente?

Por padrão, um container Docker não tem restrição de memória e pode usar tanto quanto o kernel do host permitir. Quando existe um limite definido (via –memory) e ele é ultrapassado, o kernel pode encerrar o processo pelo OOM killer, o que explica encerramentos sem erro vindo da própria aplicação.

Quando vale aumentar o plano de VPS por causa de memória?

Só depois de confirmar que o heapUsed está estável (sem crescimento contínuo) e que os limites do container estão configurados corretamente. Se mesmo assim a carga normal não cabe na RAM disponível, aí sim o dimensionamento entra, escolhendo o plano a partir do pico medido na linha de base.


Quer aprender mais sobre inteligência artificial e programação na prática? Conheça o canal Hora de Codar no YouTube e acompanhe os próximos vídeos.

Escrito por | Matheus Battisti

Matheus Battisti
Fundador da Hora de Codar

Programador apaixonado pelo mundo das tecnologias, sempre buscando em aprender e se aprofundar em linguagens, frameworks e o que mais for necessário para executar um bom trabalho. Agora tem uma nova missão que é de passar seu conhecimento adiante para formar novos programadores e especializar mais os que já são.

Subscribe
Notify of
guest

0 Comentários
Oldest
Newest Most Voted

Formações

Formação Claude Code

Formação Claude Code

Domine Claude Code do absoluto zero até o avançado

  • 120 aulas
  • 4 projetos
  • 9h 48min

Blog | Mais populares