Guia Completo de Estratégias de Connection Pooling e Query Optimization em Node.js Já leu

Connection Pooling em Node.js Connection pooling é uma técnica fundamental para otimizar o uso de conexões com bancos de dados. Em vez de criar uma nova conexão a cada requisição (operação custosa), mantemos um conjunto de conexões reutilizáveis. Isso reduz latência, economiza recursos e melhora a throughput da aplicação. No Node.js, a maioria das bibliotecas de database já implementa pooling nativamente. Vamos explorar com PostgreSQL usando : Configuração Avançada A chave está em encontrar o equilíbrio entre (número máximo de conexões) e consumo de memória. Para aplicações de médio porte, 20-50 conexões costumam ser suficientes. Se sua aplicação está em produção com alta concorrência, monitore métricas: quantas conexões estão ativas vs ociosas. Use e para debug. Ativas: ${pool.totalCount}, Disponíveis: ${pool.availableCount} --- Query Optimization Otimizar queries é onde a maioria ganha performance exponencial. A diferença entre uma query ruim e uma boa pode ser de 100x ou mais. Foque em três pilares: índices, evitar N+1 queries, e prepared statements. Índices e

Connection Pooling em Node.js

Connection pooling é uma técnica fundamental para otimizar o uso de conexões com bancos de dados. Em vez de criar uma nova conexão a cada requisição (operação custosa), mantemos um conjunto de conexões reutilizáveis. Isso reduz latência, economiza recursos e melhora a throughput da aplicação.

No Node.js, a maioria das bibliotecas de database já implementa pooling nativamente. Vamos explorar com PostgreSQL usando pg:

const { Pool } = require('pg');

const pool = new Pool({
  user: 'usuario',
  password: 'senha',
  host: 'localhost',
  port: 5432,
  database: 'meu_banco',
  max: 20,           // máximo de conexões no pool
  idleTimeoutMillis: 30000,  // fecha conexões ociosas
  connectionTimeoutMillis: 2000,
});

// Usar a conexão
pool.query('SELECT * FROM usuarios WHERE id = $1', [1], (err, res) => {
  if (err) console.error(err);
  else console.log(res.rows);
});

// Fechar o pool quando a app encerrar
pool.end();

Configuração Avançada

A chave está em encontrar o equilíbrio entre max (número máximo de conexões) e consumo de memória. Para aplicações de médio porte, 20-50 conexões costumam ser suficientes. Se sua aplicação está em produção com alta concorrência, monitore métricas: quantas conexões estão ativas vs ociosas. Use pool.totalCount e pool.availableCount para debug.

setInterval(() => {
  console.log(`Ativas: ${pool.totalCount}, Disponíveis: ${pool.availableCount}`);
}, 5000);

Query Optimization

Otimizar queries é onde a maioria ganha performance exponencial. A diferença entre uma query ruim e uma boa pode ser de 100x ou mais. Foque em três pilares: índices, evitar N+1 queries, e prepared statements.

Índices e Explain

Sempre use EXPLAIN ANALYZE antes de considerar uma query pronta para produção. Isso mostra o plano de execução e se está usando índices corretamente.

EXPLAIN ANALYZE SELECT * FROM usuarios WHERE email = 'teste@example.com';

-- Criar índice se necessário
CREATE INDEX idx_usuarios_email ON usuarios(email);

Em Node.js, pratique isso antes de fazer o deploy:

async function analisarQuery() {
  const resultado = await pool.query(`
    EXPLAIN ANALYZE 
    SELECT u.id, u.nome, COUNT(p.id) as posts
    FROM usuarios u
    LEFT JOIN posts p ON u.id = p.usuario_id
    GROUP BY u.id
  `);

  console.log(resultado.rows);
}

Evitar o Problema N+1

Este é o erro mais comum. Você faz uma query que retorna N linhas, depois faz N queries adicionais em um loop. Solução: use JOINs ou batch loading.

❌ Errado — N+1 queries:

async function buscarUsuariosComPostsErrado() {
  const usuarios = await pool.query('SELECT * FROM usuarios LIMIT 10');

  for (let user of usuarios.rows) {
    const posts = await pool.query(
      'SELECT * FROM posts WHERE usuario_id = $1', 
      [user.id]
    ); // 10 queries adicionais!
    user.posts = posts.rows;
  }
  return usuarios.rows;
}

✅ Correto — Uma única query com JOIN:

async function buscarUsuariosComPostsCerto() {
  const resultado = await pool.query(`
    SELECT 
      u.id, u.nome, u.email,
      json_agg(json_build_object('id', p.id, 'titulo', p.titulo)) as posts
    FROM usuarios u
    LEFT JOIN posts p ON u.id = p.usuario_id
    GROUP BY u.id
    LIMIT 10
  `);

  return resultado.rows;
}

Isso reduz 10+ queries para exatamente 1. Performance dramática.

Prepared Statements e Parametrização

Sempre use placeholders ($1, $2) ao invés de concatenar strings. Isso previne SQL injection e permite que o banco reutilize planos de execução compilados.

// ✅ Correto e seguro
await pool.query(
  'SELECT * FROM usuarios WHERE email = $1 AND ativo = $2',
  ['teste@example.com', true]
);

// ❌ Perigoso - nunca faça isso
const email = req.body.email;
await pool.query(`SELECT * FROM usuarios WHERE email = '${email}'`);

Monitoramento e Boas Práticas

Aplicações em produção precisam de observabilidade. Implemente métricas de performance de queries: tempo de execução, quantidade de linhas retornadas, e uso do pool.

const { performance } = require('perf_hooks');

async function queryComMonitoramento(sql, params) {
  const inicio = performance.now();

  try {
    const resultado = await pool.query(sql, params);
    const duracao = performance.now() - inicio;

    console.log(`[QUERY] ${duracao.toFixed(2)}ms - ${sql.substring(0, 50)}`);
    return resultado;
  } catch (erro) {
    console.error(`[ERRO] Query falhou após ${(performance.now() - inicio).toFixed(2)}ms`);
    throw erro;
  }
}

Checklist de Otimização

  • Use índices nas colunas de filtro (WHERE, JOIN, ORDER BY)
  • Selecione apenas as colunas necessárias (evite SELECT *)
  • Aggregate dados no banco (GROUP BY, SUM) em vez de na aplicação
  • Cache resultados que mudam pouco frequentemente
  • Use transactions (BEGIN/COMMIT) para múltiplas operações relacionadas
  • Configure timeouts: statement_timeout no PostgreSQL para queries que travam

Conclusão

Connection pooling e query optimization são competências essenciais para desenvolvedores Node.js que lidam com dados. Comece dominando EXPLAIN ANALYZE e eliminando N+1 queries—esses dois pontos podem resolver 80% dos problemas de performance. Em segundo lugar, configure seu pool conscientemente: nem muito pequeno (sufoca requisições) nem muito grande (desperdiça memória). Por fim, meça tudo: implemente logging e monitoramento desde o início, porque otimização sem métricas é apenas adivinhação.


Referências

  1. PostgreSQL Documentation - EXPLAIN
  2. node-postgres Pool Documentation
  3. High Performance Node.js - Use The Index, Luke
  4. Optimization Tips for Node.js + SQL - LogRocket
  5. PostgreSQL Query Tuning Official Guide

Artigos relacionados