Connection pooling com PgBouncer

Cada conexão no PostgreSQL consome ~5 MB de RAM e um processo dedicado. Com 200 conexões simultâneas de uma API Node.js, você está consumindo 1 GB só para gerenciar conexões, antes de processar qualquer query. PgBouncer multiplexia centenas de conexões da aplicação em poucas conexões reais ao PostgreSQL, resolvendo o "too many connections" e melhorando o throughput.

Instalar e configurar o PgBouncer

PgBouncer fica entre a aplicação e o PostgreSQL:

bash
# Instalar PgBouncer
sudo apt install -y pgbouncer

# Configuração principal
sudo nano /etc/pgbouncer/pgbouncer.ini

[databases]
# Alias para o banco real (a aplicação conecta em "minha_db")
minha_db = host=127.0.0.1 port=5432 dbname=minha_db

[pgbouncer]
listen_addr = 127.0.0.1    # ou 0.0.0.0 para acesso remoto
listen_port = 6432          # porta do PgBouncer (diferente do PG: 5432)
auth_type = scram-sha-256
auth_file = /etc/pgbouncer/userlist.txt

# Modo de pooling
pool_mode = transaction     # recomendado para APIs stateless

# Limites de conexão
max_client_conn = 1000      # máximo de clientes simultâneos
default_pool_size = 25      # conexões reais ao PostgreSQL por banco/usuário
min_pool_size = 5
reserve_pool_size = 5
reserve_pool_timeout = 3

# Logs
log_connections = 0
log_disconnections = 0
log_pooler_errors = 1
stats_period = 60

Configurar autenticação de usuários

O PgBouncer precisa de lista de usuários e senhas:

bash
# Gerar hash scram-sha-256 da senha (conectar ao PostgreSQL):
sudo -u postgres psql -c "SELECT rolname, rolpassword FROM pg_authid WHERE rolname = 'minha_app';"

# O hash começa com SCRAM-SHA-256$...
# Copiar para o userlist.txt:

sudo nano /etc/pgbouncer/userlist.txt
# Formato: "usuario" "hash_ou_senha"
"minha_app" "SCRAM-SHA-256$4096:...(hash completo)"

# Alternativa mais simples (menos seguro — texto plano):
"minha_app" "senha_da_aplicacao"

# Iniciar/reiniciar PgBouncer
sudo systemctl enable pgbouncer
sudo systemctl restart pgbouncer

# Testar conexão via PgBouncer (porta 6432):
psql -h 127.0.0.1 -p 6432 -U minha_app -d minha_db -c "SELECT 1"

# Verificar status do pool:
psql -h 127.0.0.1 -p 6432 -U minha_app pgbouncer -c "SHOW POOLS;"

Monitorar o PgBouncer

O banco virtual pgbouncer expõe estatísticas em tempo real:

sql
# Conectar ao banco de admin do PgBouncer:
psql -h 127.0.0.1 -p 6432 -U minha_app pgbouncer

-- Ver pools ativos:
SHOW POOLS;
-- cl_active: clientes com conexão ativa
-- cl_waiting: clientes aguardando conexão disponível
-- sv_active: conexões reais ao PostgreSQL em uso
-- sv_idle: conexões reais disponíveis no pool

-- Ver clientes conectados:
SHOW CLIENTS;

-- Ver servidores (conexões reais ao PostgreSQL):
SHOW SERVERS;

-- Ver estatísticas de performance:
SHOW STATS;
-- total_xact_count: número de transações processadas
-- total_query_count: número de queries
-- total_wait_time: tempo total aguardando conexão (microsegundos)

-- Recarregar config sem reiniciar:
RELOAD;

-- Ver configuração atual:
SHOW CONFIG;

Atualizar a string de conexão da aplicação

A aplicação aponta para o PgBouncer em vez de conectar diretamente ao PostgreSQL:

bash
# Antes (conecta direto no PostgreSQL):
DATABASE_URL=postgresql://minha_app:senha@localhost:5432/minha_db

# Depois (conecta no PgBouncer):
DATABASE_URL=postgresql://minha_app:senha@localhost:6432/minha_db
#                                                   ^^^^
#                                                   porta do PgBouncer

# Em Docker Compose — PgBouncer como serviço separado:
services:
  postgres:
    image: postgres:17-alpine
    environment:
      POSTGRES_DB: minha_db
      POSTGRES_USER: minha_app
      POSTGRES_PASSWORD: senha

  pgbouncer:
    image: edoburu/pgbouncer:latest
    environment:
      DATABASE_URL: postgresql://minha_app:senha@postgres:5432/minha_db
      POOL_MODE: transaction
      MAX_CLIENT_CONN: 1000
      DEFAULT_POOL_SIZE: 25
    ports:
      - "6432:5432"
    depends_on:
      - postgres

  app:
    environment:
      DATABASE_URL: postgresql://minha_app:senha@pgbouncer:5432/minha_db

Solucionar problemas comuns

Erros frequentes ao migrar para PgBouncer e suas soluções:

bash
# Erro: "prepared statement exists"
# Causa: pool_mode=transaction não suporta prepared statements por nome
# Solução A: usar statement ou session mode
# Solução B: desabilitar prepared statements na aplicação

# Prisma (Node.js) — desabilitar prepared statements:
DATABASE_URL="postgresql://...?pgbouncer=true&connection_limit=1"
# prisma/schema.prisma:
# datasource db { url = env("DATABASE_URL"), directUrl = env("DIRECT_URL") }

# SQLAlchemy (Python) — usar NullPool ou QueuePool com prepared=False:
from sqlalchemy import create_engine
engine = create_engine(url, poolclass=NullPool)

# Erro: "max_client_conn reached"
# Solução: aumentar max_client_conn no pgbouncer.ini
# Monitorar com: SHOW CONFIG; e SHOW POOLS;

# Erro: "server login failed"
# Causa: senha incorreta no userlist.txt
# Verificar: sudo journalctl -u pgbouncer -n 50 --no-pager

Não quer configurar manualmente?

Implante o VPS em menos de 5 minutos com a Runstack. Infraestrutura da OPEN Datacenter, com servidores em São Paulo.

Perguntas frequentes

transaction (recomendado para APIs): conexão devolvida ao pool após cada transação. Compatível com a maioria das aplicações stateless. session: uma conexão por sessão do cliente, igual a não ter pool. Útil para apps que usam SET SESSION ou advisory locks. statement: devolvida após cada query, mais agressivo, não suporta transações multi-statement.

Depende do max_connections do PostgreSQL. Regra: default_pool_size × number_of_databases × number_of_users < max_connections_do_postgresql. Para PostgreSQL com max_connections=100: pool_size entre 20-30 para um banco/usuário. Monitore sv_active e sv_idle no SHOW POOLS, se sv_idle sempre 0, aumente o pool.

Para a maioria dos casos: não. PgBouncer é extremamente eficiente em single-thread e processa dezenas de milhares de transações por segundo. Se você precisar de mais throughput: rode múltiplas instâncias do PgBouncer em portas diferentes e faça load balancing (Nginx upstream), cada instância gerencia seu próprio pool.

Depende. Se sua aplicação usa um connection pool interno (Prisma, SQLAlchemy, HikariCP) com poucos workers, pode não precisar do PgBouncer. Se tem muitas réplicas da aplicação, funções serverless ou conexões de curta duração: PgBouncer é essencial. Regra prática: se você já viu "too many connections", use PgBouncer.

PgBouncer: simples, extremamente leve, foca em connection pooling. A escolha para 95% dos casos. Pgpool-II: mais features (load balancing, query caching, failover automático), mas muito mais complexo de configurar e operar. Para VPS simples: PgBouncer. Para cluster com múltiplos servidores e failover automático: Pgpool-II ou Patroni.