Introdução à Otimização de Node.js no Ubuntu
Mover uma aplicação Node.js do ambiente de desenvolvimento local para um servidor VPS (Virtual Private Server) rodando Ubuntu é um passo crítico que redefine a expectativa de performance. Em ambientes locais, os gargalos de rede são mínimos e o sistema operacional raramente compete por recursos com outros serviços. No entanto, em produção, fatores como gerenciamento de memória, configuração do cluster do Node.js, otimizações do kernel Linux e estratégias de cache tornam-se determinantes para a estabilidade e velocidade da sua aplicação.
O tuning (afinamento) de performance não se trata apenas de instalar pacotes mais rápidos; é uma abordagem sistêmica que envolve ajustar o runtime do Node.js, configurar o gerenciador de processos e otimizar as variáveis de ambiente do Linux. Neste tutorial, abordaremos a configuração completa para extrair o máximo da sua infraestrutura, garantindo baixa latência e alta disponibilidade.
1. Preparação do Ambiente Ubuntu
Antes de tocar na configuração do Node.js, é fundamental garantir que o sistema operacional esteja preparado para cargas de trabalho de rede intensiva. O Ubuntu, por padrão, possui valores conservadores para limites de arquivos abertos e conexões de rede, o que pode limitar a escalabilidade horizontal da sua aplicação.
Inicie atualizando o sistema e instalando as ferramentas básicas necessárias para gerenciamento e monitoramento:
sudo apt update && sudo apt upgrade -y
sudo apt install build-essential curl wget htop pm2 -y
A instalação do build-essential é crucial caso suas dependências Node.js exijam compilação nativa (como bibliotecas C++). O pm2 será nosso gerenciador de processos principal, mas vamos começar configurando o sistema.
Ajustando Limites de Arquivos e Memória
O Node.js é single-threaded por processo, mas aplicações modernas frequentemente abrem milhares de conexões simultâneas (sockets, arquivos de log, bancos de dados). O limite padrão do Linux para processos não privilegiados é frequentemente 1024, o que é insuficiente para produção.
Crie ou edite o arquivo /etc/security/limits.conf para aumentar os limites:
sudo nano /etc/security/limits.conf
Adicione as seguintes linhas ao final do arquivo, substituindo nodeuser pelo usuário que rodará a aplicação (ou use * para todos os usuários):
nodeuser soft nofile 65535
nodeuser hard nofile 65535
nodeuser soft nproc 65535
nodeuser hard nproc 65535
Além disso, ajuste a política de swap e o uso de memória. Para aplicações Node.js, que dependem muito da RAM para manter buffers e variáveis no heap, é recomendável desativar o swappiness ou mantê-lo extremamente baixo, evitando que o kernel mova dados ativos da memória para o disco, o que causaria uma queda drástica de performance.
sudo sysctl -w vm.swappiness=1
echo "vm.swappiness=1" | sudo tee -a /etc/sysctl.conf
2. Instalação e Configuração do Node.js Otimizado
Não utilize o gerenciador de pacotes apt para instalar o Node.js em produção, pois ele frequentemente entrega versões desatualizadas ou compiladas sem otimizações específicas. A abordagem recomendada é utilizar o nvm (Node Version Manager) ou instalar binários pré-compilados diretamente do site oficial.
Para este tutorial, utilizaremos o método de instalação via binário direto para garantir controle total sobre a versão e evitar sobrecarga do nvm em produção:
# Exemplo para Node.js LTS na arquitetura x64
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo bash -
sudo apt-get install -y nodejs
Verifique se a instalação está correta e se o V8 engine (motor JavaScript do Node) está ativo:
node --version
npm --version
Otimizações de Linha de Comando
Antes de iniciar sua aplicação, você deve configurar as flags do processo. Existem variáveis de ambiente que alteram o comportamento do garbage collector (GC) e da alocação de memória do V8.
- --max-old-space-size: Define o limite máximo de memória heap para o processo principal. Em uma VPS com 4GB de RAM, por exemplo, você pode alocar cerca de 2GB-3GB para o Node, deixando o resto para o sistema operacional e outros serviços.
- --max-semi-space-size: Controla o tamanho do espaço semisspace (onde objetos novos são criados). Aumentar isso pode melhorar performance em aplicações que criam muitos objetos temporários.
No entanto, a melhor prática moderna é delegar o gerenciamento de processos ao PM2, que permite configurar esses limites por aplicação.
3. Gerenciamento de Processos com PM2 e Cluster Mode
O maior erro de iniciantes em Node.js no Linux é rodar uma única instância do processo. Como o Node.js utiliza apenas um núcleo de CPU por processo, sua aplicação estará subutilizando a capacidade da sua VPS.
O PM2 possui um recurso chamado Cluster Mode, que permite iniciar múltiplas instâncias do seu aplicativo (uma para cada núcleo de CPU disponível), compartilhando a mesma porta. Isso paralela o carregamento de requisições e aumenta drasticamente a throughput.
Iniciando em Modo Cluster
Utilize o comando -i max para iniciar um processo para cada núcleo detectado:
pm2 start app.js -i max --name "meu-app-node"
O PM2 lidará automaticamente com a distribuição de carga entre os processos filhos e reiniciará qualquer instância que falhe.
Ajuste Fino do Heap Memory
Você pode especificar o limite de memória para cada processo no cluster. Se sua VPS tem 4GB de RAM e 4 núcleos, e você quer deixar 1GB livre para o sistema, sobram 3GB. Dividindo por 4 processos, cada um teria direito a ~750MB.
pm2 start app.js -i max --name "meu-app-node" --max-memory-restart 750M
Se qualquer processo ultrapassar 750M, o PM2 irá reiniciá-lo automaticamente, prevenindo vazamentos de memória (memory leaks) que poderiam travar toda a VPS.
4. Configuração do Nginx como Reverse Proxy
Rodar o Node.js diretamente na porta 80 ou 443 não é recomendável por questões de segurança e desempenho. O ideal é colocar um servidor web estável, como o Nginx, na frente do seu aplicativo Node.js. O Nginx é extremamente eficiente em lidar com conexões keep-alive, compressão Gzip e cache de arquivos estáticos, liberando o Node.js para focar exclusivamente na lógica de negócios.
Instalando e Configurando o Nginx
sudo apt install nginx -y
sudo systemctl enable nginx
Crie um arquivo de configuração no diretório /etc/nginx/sites-available/. Vamos chamar de node-app:
sudo nano /etc/nginx/sites-available/node-app
Insira a seguinte configuração, ajustando o domínio e a porta (geralmente 3000 ou 8080):
server {
listen 80;
server_name seu-dominio.com www.seu-dominio.com;
# Redirecionar HTTP para HTTPS se usar SSL
# return 301 https://$server_name$request_uri;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Ative a configuração e teste se há erros de sintaxe:
sudo ln -s /etc/nginx/sites-available/node-app /etc/nginx/sites-enabled/
sudo nginx -t
Se o teste for bem-sucedido, recarregue o Nginx:
sudo systemctl reload nginx
5. Otimizações Avançadas de Kernel Linux (TCP Tuning)
Para aplicações que recebem um alto volume de requisições curtas (HTTP), a configuração padrão do TCP no Linux pode introduzir latência devido ao tempo de fechamento de conexões (time-wait). Ajustar o kernel pode reduzir significativamente o overhead de CPU dedicado à gestão de rede.
Crie um arquivo de configuração sysctl personalizado:
sudo nano /etc/sysctl.d/99-tcp-optimize.conf
Adicione as seguintes diretrizes para otimizar a pilha TCP:
# Aumenta o tamanho do buffer TCP para melhorar throughput
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
# Aumenta o número máximo de sockets em espera
net.ipv4.tcp_max_syn_backlog = 8192
# Reduz o tempo que conexões ficam no estado TIME_WAIT
net.ipv4.tcp_tw_reuse = 1
# Habilita a seleção de timestamp para melhorar a detecção de pacotes duplicados
net.ipv4.tcp_timestamps = 0
# Aumenta o limite de memória TCP
net.ipv4.tcp_mem = 8388608 12582912 16777216
Para aplicar as alterações imediatamente sem reiniciar a máquina:
sudo sysctl --system
6. Monitoramento Contínuo e Manutenção
A otimização não termina com a configuração inicial. Você deve monitorar o comportamento da aplicação em tempo real.
Uso do PM2 para Logs e Métricas
O PM2 mantém logs separados para cada processo, facilitando a identificação de erros específicos:
pm2 logs --lines 100
Além disso, utilize o comando pm2 monit para visualizar gráficos em tempo real de uso de CPU e memória por processo. Isso ajuda a identificar se um processo específico está consumindo mais recursos que os outros ou se há vazamentos de memória.
Habilitando Logs do Sistema (systemd)
Para uma integração mais profunda, configure o PM2 para usar o systemd como gerenciador de processos. Isso garante que sua aplicação inicie junto com o servidor e seja gerenciada pelas ferramentas nativas do Linux:
pm2 startup
sudo env PATH=$PATH /usr/bin/pm2 start app.js -i max --name "meu-app-node"
pm2 save
Agora, você pode usar comandos padrão do Ubuntu para gerenciar o serviço:
sudo systemctl status meu-app-node
sudo systemctl restart meu-app-node
Conclusão
Otimizar um servidor Node.js no Ubuntu é um processo iterativo que combina boas práticas de código, configuração eficiente do runtime e ajustes finos no sistema operacional. Ao implementar limites de memória via PM2, utilizar o Cluster Mode para paralelismo, colocar o Nginx como proxy reverso e ajustar as configurações de TCP do kernel, você transforma uma instalação básica em uma infraestrutura robusta e escalável.
Lembre-se de que cada aplicação tem um perfil de carga único. Utilize ferramentas de monitoramento como htop, pm2 monit e métricas de aplicação (como Prometheus ou New Relic) para ajustar esses valores conforme a necessidade real do seu tráfego. A performance não é um estado fixo, mas um equilíbrio dinâmico entre recursos disponíveis e demanda.