Home > Blog > Desenvolvimento Web
Desenvolvimento Web
JavaScript
Programação

Pool PostgreSQL no Node.js

Atualizado em: 6 de agosto de 2026

Rack de servidores processando fluxos de dados no Node.js

Abrir uma nova conexão com PostgreSQL para cada consulta aumenta latência e pode esgotar rapidamente o limite do banco. Um Pool PostgreSQL no Node.js mantém um conjunto controlado de conexões reutilizáveis, permitindo atender várias requisições sem criar um socket e autenticar novamente a cada operação.

O pool não cria capacidade infinita. Quando todas as conexões estão ocupadas, novas consultas esperam em uma fila. Se o tamanho for alto demais, várias instâncias podem ultrapassar o limite do PostgreSQL; se for baixo demais, a aplicação acumula espera. Também é essencial liberar clientes, definir timeouts e fechar o pool durante o desligamento.

Neste guia, você aprenderá a configurar pg.Pool, executar consultas simples, controlar transações, evitar vazamentos, escolher o tamanho, monitorar a fila e integrar health checks e graceful shutdown.

Instalando o node-postgres

npm install pg

Crie um módulo central:

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

const pool = new Pool({
  connectionString: process.env.DATABASE_URL,
  max: 10,
  idleTimeoutMillis: 30_000,
  connectionTimeoutMillis: 5_000
});

module.exports = pool;

A documentação oficial de pooling do node-postgres explica o comportamento do cliente. A documentação do PostgreSQL sobre configuração de conexões apresenta limites do servidor.

Para estruturar uma API, veja como criar uma API com Node.js e o que é Node.js.

Consulta simples com pool.query()

async function findUserById(id) {
  const result = await pool.query(
    'SELECT id, name, email FROM users WHERE id = $1',
    [id]
  );

  return result.rows[0] || null;
}

pool.query() pega uma conexão, executa a consulta e libera automaticamente. Essa é a opção preferida para uma única instrução que não precisa compartilhar sessão com outras.

Use parâmetros

Nunca concatene valores do usuário na SQL:

const result = await pool.query(
  'SELECT * FROM products WHERE category_id = $1 LIMIT $2',
  [categoryId, limit]
);

Parâmetros reduzem risco de SQL injection e evitam problemas de escape. Nomes de tabela e coluna não podem ser parametrizados da mesma maneira; use uma lista permitida quando forem dinâmicos.

Obtendo um cliente dedicado

const client = await pool.connect();

try {
  const result = await client.query('SELECT NOW()');
  console.log(result.rows[0]);
} finally {
  client.release();
}

Sempre libere no bloco finally. Um caminho de erro que esquece release() mantém a conexão ocupada e, depois de algumas requisições, toda a fila trava.

Transações

Uma transação precisa usar o mesmo cliente do início ao fim:

async function transferBalance(fromId, toId, amount) {
  const client = await pool.connect();

  try {
    await client.query('BEGIN');

    await client.query(
      'UPDATE accounts SET balance = balance - $1 WHERE id = $2',
      [amount, fromId]
    );

    await client.query(
      'UPDATE accounts SET balance = balance + $1 WHERE id = $2',
      [amount, toId]
    );

    await client.query('COMMIT');
  } catch (error) {
    await client.query('ROLLBACK');
    throw error;
  } finally {
    client.release();
  }
}

Não use pool.query() separadamente dentro da transação, pois cada chamada pode pegar uma conexão diferente.

Helper de transação

async function withTransaction(callback) {
  const client = await pool.connect();

  try {
    await client.query('BEGIN');
    const result = await callback(client);
    await client.query('COMMIT');
    return result;
  } catch (error) {
    try {
      await client.query('ROLLBACK');
    } catch (rollbackError) {
      error.rollbackError = rollbackError;
    }
    throw error;
  } finally {
    client.release();
  }
}

O helper centraliza rollback e liberação. Evite transações longas que fazem chamadas HTTP ou aguardam interação do usuário, pois elas mantêm conexão e locks.

Escolhendo o tamanho do pool

O valor ideal depende de:

  • limite max_connections do PostgreSQL;
  • conexões reservadas para administração e outros serviços;
  • quantidade máxima de instâncias da aplicação;
  • concorrência e duração das consultas;
  • uso de um pooler externo, como PgBouncer.

Se o banco aceita 100 conexões e existem 10 instâncias com pool máximo 20, o limite teórico seria 200. O dimensionamento precisa considerar o pico de escalonamento, não apenas o estado atual.

Pool grande não significa mais desempenho

PostgreSQL executando centenas de consultas simultâneas pode gastar mais tempo alternando CPU, memória e I/O. Um pool menor cria fila na aplicação e protege o banco. Meça latência total e utilização antes de aumentar.

Fila de espera

O node-postgres expõe contadores úteis:

logger.info({
  total: pool.totalCount,
  idle: pool.idleCount,
  waiting: pool.waitingCount
}, 'Estado do pool PostgreSQL');

waitingCount crescente indica consultas lentas, pool pequeno, vazamento ou banco degradado. Monitore também duração das consultas.

Timeout de conexão

connectionTimeoutMillis limita o tempo para obter uma nova conexão física. Ele não necessariamente limita o tempo esperando um cliente disponível em todas as situações. Defina também um orçamento total na camada de serviço.

Timeout de consulta

Configure statement_timeout no banco, no usuário ou por sessão:

const pool = new Pool({
  connectionString,
  options: '-c statement_timeout=5000'
});

Outra opção é executar SET statement_timeout ao conectar. O timeout precisa ser menor que o prazo da requisição e maior que o tempo normal da consulta.

Cancelamento

Quando o cliente HTTP desconecta, a aplicação pode não precisar mais do resultado. Nem todas as versões e APIs oferecem o mesmo suporte a AbortSignal, então confirme a documentação da biblioteca. Mesmo sem cancelamento do PostgreSQL, pare etapas posteriores e limite a consulta no servidor.

Veja AbortController no Node.js para propagar cancelamento.

Eventos do pool

pool.on('connect', client => {
  logger.debug('Nova conexão PostgreSQL');
});

pool.on('error', error => {
  logger.error({ error }, 'Erro em cliente ocioso do pool');
});

pool.on('remove', () => {
  logger.debug('Conexão removida do pool');
});

O evento error precisa ser tratado. Uma conexão ociosa pode falhar por reinício do banco ou problema de rede.

SSL

const pool = new Pool({
  connectionString,
  ssl: {
    rejectUnauthorized: true,
    ca: process.env.DATABASE_CA
  }
});

Não desative validação de certificado em produção. O provedor do banco deve fornecer a CA ou uma configuração segura. Veja HTTPS e TLS no Node.js.

Variáveis de ambiente

Valide DATABASE_URL, tamanho do pool e timeouts na inicialização. Não registre a URL completa, pois ela pode conter usuário e senha. Consulte Variáveis de Ambiente no Node.js.

Health check

async function checkDatabase() {
  const startedAt = performance.now();
  await pool.query('SELECT 1');

  return {
    status: 'up',
    durationMs: performance.now() - startedAt
  };
}

Use a verificação em readiness, não em liveness. A consulta deve ser curta e ter timeout. Veja Health Checks no Node.js.

Graceful shutdown

async function shutdown() {
  ready = false;
  await closeServer(server);
  await pool.end();
}

Pare novas requisições antes de fechar o pool. pool.end() encerra conexões e impede novas consultas. O guia de Graceful Shutdown no Node.js detalha a ordem.

PgBouncer e pool duplo

Um pooler externo pode concentrar conexões de muitas instâncias. Nesse caso, o pool da aplicação ainda deve ter limite, mas talvez seja menor. Em modo transaction pooling, recursos dependentes de sessão, prepared statements nomeados e algumas configurações podem ter restrições.

Prepared statements

await pool.query({
  name: 'find-user-by-id',
  text: 'SELECT id, name FROM users WHERE id = $1',
  values: [id]
});

O nome permite preparar por conexão. Use em consultas realmente frequentes e estáveis; não crie nomes dinâmicos ilimitados.

N+1 e pool

Aumentar o pool não corrige uma rota que faz centenas de consultas pequenas. Reduza round trips com JOIN, agregação ou carregamento em lote. O artigo sobre performance de APIs Node.js apresenta otimizações gerais.

Retry

Não repita toda consulta indiscriminadamente. Erros de conexão antes da execução podem ser candidatos; transações com resultado incerto exigem idempotência e verificação. Use a política de Retry com Backoff no Node.js apenas para erros classificados.

Observabilidade

Monitore:

  • total, idle e waiting do pool;
  • duração e taxa de erro por consulta;
  • timeouts;
  • transações abertas;
  • conexões no PostgreSQL;
  • locks e consultas lentas.

Não registre parâmetros sensíveis. Use nomes de operação e traces com OpenTelemetry no Node.js.

Como testar

Use banco isolado ou schema exclusivo por suíte. Teste concorrência, rollback, timeout, cliente não liberado, encerramento e falha de conexão. Não dependa da ordem dos testes nem compartilhe uma transação entre casos concorrentes.

Erros comuns

  • Criar um pool por requisição: conexões se multiplicam.
  • Esquecer release(): o pool esgota.
  • Usar pool.query() dentro de transação: instruções podem usar clientes diferentes.
  • Configurar max alto demais: o banco fica sobrecarregado.
  • Não definir timeouts: consultas travadas ocupam clientes.
  • Fechar pool antes do servidor: requisições em andamento falham.
  • Registrar DATABASE_URL: credenciais vazam.
  • Usar retry sem idempotência: operações são duplicadas.

Boas práticas para produção

  • Crie um pool por processo.
  • Use pool.query() para consultas simples.
  • Libere clientes em finally.
  • Use o mesmo cliente em transações.
  • Dimensione pelo total de instâncias.
  • Defina timeouts de conexão e consulta.
  • Monitore fila e duração.
  • Valide TLS e segredos.
  • Feche o pool no graceful shutdown.
  • Otimize consultas antes de aumentar o pool.

Conclusão

Um Pool PostgreSQL no Node.js reutiliza conexões e protege o banco contra criação descontrolada de sessões. pool.query() simplifica consultas individuais, enquanto clientes dedicados permitem transações consistentes.

O desempenho depende de limites, liberação e observabilidade. Dimensione o pool considerando todas as instâncias, aplique timeouts e feche recursos na ordem correta. Com essas práticas, a camada de banco permanece previsível mesmo sob concorrência.

Os 10 Melhores Cursos de Programação de 2026

Descubra os melhores cursos de programação. Aprenda a escolher o curso ideal para iniciar ou avançar na carreira de desenvolvedor

POSTS RELACIONADOS

Ver todos

Seta para a direita