Node.js em produção: performance e PM2 cluster

    Node.js roda em uma única thread por padrão — em uma VPS com 4 CPUs, você aproveita apenas 25% do processador. PM2 em modo cluster e Worker Threads mudam isso, distribuindo a carga entre todos os núcleos. Combinado com compressão, cache de respostas e profiling, você multiplica o throughput sem mudar o hardware.

    PM2 em modo cluster

    Aproveitar todos os CPUs da VPS com PM2:

    javascript
    # ecosystem.config.js
    module.exports = {
      apps: [{
        name: 'minha-api',
        script: './dist/index.js',
        instances: 'max',        // um processo por CPU
        exec_mode: 'cluster',    // compartilhar porta entre processos
        watch: false,
        max_memory_restart: '500M',
        env_production: {
          NODE_ENV: 'production',
          PORT: 3000,
        },
        // Zero-downtime deploy:
        wait_ready: true,
        listen_timeout: 10000,
        kill_timeout: 5000,
      }],
    }
    
    # Iniciar em produção:
    pm2 start ecosystem.config.js --env production
    pm2 save
    pm2 startup    # gerar comando para iniciar no boot
    
    # Reload sem downtime (reinicia instâncias uma a uma):
    pm2 reload minha-api
    
    # Monitorar em tempo real:
    pm2 monit
    Dica
    No modo cluster, o PM2 usa o módulo cluster nativo do Node.js — todas as instâncias compartilham a mesma porta e o PM2 distribui as conexões em round-robin entre elas.

    Worker Threads para tarefas CPU-intensivas

    Mover processamento pesado para threads separadas:

    typescript
    // worker.ts — tarefa CPU-intensiva
    import { workerData, parentPort } from 'worker_threads'
    
    function processarRelatorio(dados: unknown[]) {
      // processamento pesado (PDF, imagem, cálculos)
      return dados.length * 2
    }
    
    parentPort!.postMessage(processarRelatorio(workerData))
    
    // api.ts — usar o worker
    import { Worker } from 'worker_threads'
    import path from 'path'
    
    function executarWorker(dados: unknown[]): Promise<number> {
      return new Promise((resolve, reject) => {
        const worker = new Worker(
          path.resolve(__dirname, 'worker.js'),
          { workerData: dados }
        )
        worker.on('message', resolve)
        worker.on('error', reject)
      })
    }
    
    app.post('/relatorio', async (req, res) => {
      // Não bloqueia o event loop principal:
      const resultado = await executarWorker(req.body.dados)
      res.json({ resultado })
    })

    Compressão e cache de respostas

    Reduzir bytes trafegados e evitar reprocessamento:

    typescript
    import express from 'express'
    import compression from 'compression'
    import NodeCache from 'node-cache'
    
    const app = express()
    const cache = new NodeCache({ stdTTL: 60 }) // cache de 60s
    
    // Compressão gzip/brotli (reduz payload 60-80%):
    app.use(compression({
      threshold: 1024,     // comprimir apenas respostas > 1KB
      level: 6,            // nível de compressão (1-9, 6 é o balanço ideal)
    }))
    
    // Middleware de cache em memória para rotas pesadas:
    function cacheMiddleware(ttl: number) {
      return (req: express.Request, res: express.Response, next: express.NextFunction) => {
        const chave = req.originalUrl
        const cached = cache.get(chave)
        if (cached) {
          return res.json(cached)
        }
        const originalJson = res.json.bind(res)
        res.json = (body) => {
          cache.set(chave, body, ttl)
          return originalJson(body)
        }
        next()
      }
    }
    
    // Aplicar cache em rota pesada:
    app.get('/api/relatorio-mensal', cacheMiddleware(300), async (req, res) => {
      const dados = await gerarRelatorio()  // executa apenas se não estiver no cache
      res.json(dados)
    })

    Profiling com Node.js --inspect e Clinic.js

    Identificar gargalos de performance na aplicação:

    bash
    # Instalar Clinic.js (suite de profiling para Node.js):
    npm install -g clinic
    
    # Doctor: diagnóstico geral (I/O, event loop lag, memory):
    clinic doctor -- node dist/index.js
    # Acessar: http://localhost:3000 com carga e pressionar Ctrl+C
    
    # Flame: flamegraph de CPU (onde o tempo está sendo gasto):
    clinic flame -- node dist/index.js
    
    # Bubbleprof: análise do event loop e async/await:
    clinic bubbleprof -- node dist/index.js
    
    # Gerar carga durante o profiling:
    # Em outro terminal:
    npx autocannon -c 100 -d 30 http://localhost:3000/api/endpoint
    
    # Profiling nativo do Node.js (sem ferramentas externas):
    node --prof dist/index.js &
    # Gerar carga...
    # Analisar:
    node --prof-process isolate-*.log > processed.txt
    cat processed.txt | head -100

    Variáveis de ambiente e configuração de produção

    Configurações do Node.js que impactam performance:

    bash
    # Variáveis de ambiente importantes para produção:
    
    # Habilitar otimizações do V8:
    NODE_ENV=production
    
    # Aumentar memória disponível (padrão é ~1.5GB):
    NODE_OPTIONS=--max-old-space-size=4096
    
    # UV_THREADPOOL_SIZE: threads para I/O assíncrono (padrão: 4)
    # Aumentar para workloads com muito I/O simultâneo:
    UV_THREADPOOL_SIZE=16
    
    # ecosystem.config.js com variáveis:
    module.exports = {
      apps: [{
        name: 'api',
        script: './dist/index.js',
        instances: 'max',
        exec_mode: 'cluster',
        node_args: '--max-old-space-size=512',  // por instância
        env_production: {
          NODE_ENV: 'production',
          UV_THREADPOOL_SIZE: 16,
        },
      }],
    }
    
    # Verificar event loop lag em produção:
    # Adicionar ao código:
    import { monitorEventLoopDelay } from 'perf_hooks'
    const h = monitorEventLoopDelay({ resolution: 20 })
    h.enable()
    setInterval(() => {
      console.log('Event loop lag (p99):', h.percentile(99) / 1e6, 'ms')
    }, 30_000)

    $ runstack deploy --plan starter

    Não quer configurar manualmente?

    Não quer configurar manualmente? Implante o VPS para Node.js em menos de 3 minutos com a Runstack. Infraestrutura da OPEN DATACENTER, com servidores no Brasil.

    Perguntas frequentes