Marcio Cunha

Conexões esgotadas no banco? Como usar PgBouncer no Node.js

Descubra como o PgBouncer resolve gargalos de conexões simultâneas no PostgreSQL, evitando quedas em aplicações Node.js durante picos de tráfego intenso.

Marcio Cunha•5 min
Também disponível em:EnglishEspañol
Resumo
  • Cada conexão aberta no PostgreSQL consome memória RAM real e recursos preciosos do sistema operacional para gerenciar o processo dedicado.
  • Aplicações Node.js assíncronas geram rajadas rápidas de abertura de conexões que esgotam o limite máximo do banco de dados relacional.
  • O PgBouncer atua como um intermediário leve que guarda as conexões em uma fila organizada, aliviando a carga sobre o motor do banco.
  • O modo de pooling de transações oferece o melhor equilíbrio de desempenho, liberando a conexão física logo após cada comando executado.
  • Ajustar os parâmetros de pool de conexões na aplicação Node.js garante que o sistema suporte picos sem gerar erros críticos de timeout.

Por que o PostgreSQL sofre com muitas conexões simultâneas

Quando uma aplicação web cresce, um dos primeiros sustos que os desenvolvedores levam acontece no banco de dados. O PostgreSQL foi desenhado para criar um processo dedicado do sistema operacional para cada cliente que se conecta a ele. Na prática, isso significa que se duzentas pessoas abrirem o site ao mesmo tempo e cada uma disparar quatro consultas paralelas, o banco tentará gerenciar centenas de processos ativos simultaneamente. O custo de memória RAM e processamento para manter toda essa estrutura de pé é altíssimo. Se o servidor exceder o limite de conexões configurado no parâmetro max_connections, novas requisições simplesmente falham com erros crassos de conexão recusada. Para entender como mitigar esse problema em aplicações modernas, vale a pena conferir estratégias complementares de otimização de conexões pooling com Prisma e Node.js, que ajudam a entender o comportamento do cliente de banco.

O drama das rajadas de tráfego no Node.js

O Node.js é famoso por sua arquitetura assíncrona orientada a eventos, capaz de lidar com dezenas de milhares de requisições de I/O por segundo usando poucas threads. Porém, essa mesma velocidade cria uma armadilha perigosa para o banco de dados. Quando uma API Node.js recebe um pico repentino de tráfego, ela tenta abrir dezenas de conexões TCP com o PostgreSQL quase no mesmo milissegundo. O banco de dados, que funciona de forma mais linear e cadenciada, engasga ao tentar atender a todas essas aberturas abruptas de uma só vez. A aplicação começa a acumular promessas pendentes, o tempo de resposta dispara, e o sistema inteiro entra em colapso por exaustão de recursos. Descobrir gargalos na ponta do banco é essencial, e você pode aprofundar a investigação analisando o desempenho das consultas com uma análise detalhada usando o EXPLAIN ANALYZE para garantir que o banco não gaste tempo processando queries ineficientes sob pressão.

O que é o PgBouncer e como ele resolve o problema

Para colocar um policial de trânsito inteligente entre a sua aplicação e o banco, a comunidade usa o PgBouncer. Ele é um gerenciador de conexões leve, escrito em C puro, que fica posicionado entre o Node.js e o PostgreSQL. Na prática, ele funciona como uma casa de câmbio: a sua aplicação abre conexões com o PgBouncer (e não diretamente com o banco real), e o PgBouncer mantém um grupo menor e constante de conexões reais abertas com o PostgreSQL. Quando uma consulta chega, o PgBouncer pega uma conexão já existente que esteja ociosa, entrega a consulta, recebe a resposta e imediatamente reaproveita essa mesma conexão para outro cliente. Isso reduz drasticamente o número de processos ativos no banco, permitindo que milhares de clientes concorram por apenas poucas dezenas de conexões físicas reais sem perder desempenho.

Configurando o PgBouncer no modo transação

Existem três modos de funcionamento principais no PgBouncer: session pooling, transaction pooling e statement pooling. O modo mais recomendado para aplicações Node.js modernas é o transaction pooling. Na prática, isso significa que o PgBouncer empresta uma conexão física do banco apenas durante a execução de uma transação ou consulta individual, devolvendo-a para o pool comunitário logo após o comando terminar. No entanto, esse modo exige cuidado com comandos que mantêm estado entre requisições, como transações explícitas usando BEGIN e COMMIT na mesma sessão persistente, ou comandos como SET search_path. Se a sua aplicação utiliza ORMs que dependem fortemente de sessões estaduais, você precisará configurar o PgBouncer corretamente no arquivo de configuração pgbouncer.ini.

Implementando a conexão no código Node.js

Ajustar a sua aplicação Node.js para conversar com o PgBouncer é simples, mas exige alterar alguns hábitos. Como o PgBouncer gerencia as conexões de forma agressiva, o tamanho do pool configurado no seu driver (como pg ou node-postgres) deve ser ajustado para números menores. Em vez de abrir dez conexões por instância de servidor Node.js, você pode abrir apenas duas ou três, confiando que o PgBouncer fará o trabalho pesado de enfileiramento. Veja abaixo um exemplo prático de configuração usando o driver padrão do PostgreSQL no Node.js apontando para a porta padrão do PgBouncer que costuma rodar na porta 6432.

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

const pool = new Pool({
  host: 'localhost',
  port: 6432,
  database: 'meu_banco',
  user: 'postgres',
  password: 'sua_senha',
  max: 3,
  idleTimeoutMillis: 30000,
  connectionTimeoutMillis: 2000,
});

module.exports = {
  query: (text, params) => pool.query(text, params),
};

Monitorando e validando o comportamento sob carga

Depois de colocar o PgBouncer em produção, o trabalho de engenharia não termina. É fundamental monitorar o comportamento das filas para garantir que o tamanho do pool seja adequado. Ferramentas de observabilidade ajudam a rastrear se as conexões estão ficando presas por timeouts ou se o número de clientes aguardando na fila (cl_waiting) está subindo perigosamente. Caso o número de clientes na fila aumente de forma constante, significa que o PostgreSQL real ainda está demorando muito para responder às consultas, ou que o limite de conexões reais permitidas pelo PgBouncer (default_pool_size) precisa ser redimensionado com cautela. Para aprofundar esses conceitos na prática em produção, confira também nossa análise técnica sobre IA gerando lixo? Como usar DSPy para otimizar prompts.

Considerações Finais

O uso do PgBouncer em conjunto com o Node.js transforma a estabilidade de uma aplicação em momentos de pico de tráfego. Ao desacoplar a abertura de conexões TCP da aplicação em relação ao motor pesado do PostgreSQL, ganhamos previsibilidade, escalabilidade e proteção contra quedas abruptas. O segredo está em entender os trade-offs do modo de pooling escolhido, ajustar o tamanho dos pools na aplicação e manter uma rotina de monitoramento constante dos recursos do servidor de banco de dados.