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
    Dica
    pool_mode = transaction: a conexão é devolvida ao pool ao fim de cada transação — modo mais eficiente para APIs stateless. Não compatível com SET SESSION, prepared statements sem nomeação ou advisory locks.

    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

    $ runstack deploy --plan starter

    Não quer configurar manualmente?

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

    Perguntas frequentes