DevGuideTK
← Trilha Backend

Pool de conexões

Por que abrir uma conexão por pedido não escala e como dimensionar o pool.

As decisões sobre conexões são quatro: onde fica o pool, de que tamanho, como configurar os tempos e o que muda em ambientes sem servidor.

Onde fica o pool

ArquiteturaComo funcionaQuando usar
Pool na aplicaçãoCada processo da aplicação mantém o seu poolO padrão, enquanto a soma dos pools couber no banco
Intermediário (PgBouncer, pgcat, RDS Proxy)A aplicação conecta no intermediário, que multiplexa em poucas conexões reaisMuitas instâncias, funções sem servidor, ou a soma dos pools passando do limite do banco
Os doisPool pequeno na aplicação, intermediário na frente do bancoSistemas grandes: o pool local evita abrir conexões com o intermediário a cada pedido

O pool na aplicação basta para a maioria dos sistemas. O intermediário entra quando a conta das instâncias não fecha: 30 instâncias com pools de 10 já são 300 conexões, e o escalonamento automático pode dobrar isso num pico.

Modos do PgBouncer

ModoEmpresta a conexão do banco porEstado de sessãoAproveitamento
SessãoToda a conexão do clienteFuncionaBaixo: igual a conectar direto
TransaçãoUma transaçãoVaza entre clientes (teste do nível Conceito)Alto
ComandoUm comandoNão há transações de vários comandosMáximo, raramente útil

O modo transação é o que justifica o PgBouncer, e exige disciplina: SET LOCAL em vez de SET, travas consultivas por transação (pg_advisory_xact_lock) em vez de sessão, nada de tabelas temporárias que atravessam transações nem de LISTEN.

De que tamanho

O ponto de partida é o banco, e não a aplicação. No teste do nível Conceito, com 2 núcleos, o pico de vazão veio com 4 conexões ativas para leitura e 16 para carga mista. Uma fórmula conhecida, da documentação do HikariCP e baseada em testes do PostgreSQL, resume essa ideia:

conexões ≈ (núcleos do banco × 2) + discos efetivos

Para um banco com 8 núcleos e SSD, algo em torno de 20 conexões ativas no total. A fórmula é um começo; o número certo vem de medir com a sua carga.

Depois, divida pelo número de instâncias:

BancoInstâncias da aplicaçãoPool por instância
8 núcleos, alvo de cerca de 20 conexões210
45
201 ou 2, ou um intermediário

Com muitas instâncias, o pool por instância fica pequeno demais para absorver picos locais, e o intermediário passa a compensar: cada instância mantém algumas conexões com o PgBouncer, e ele mantém as 20 com o banco.

Lembre que a aplicação raramente usa todas as conexões ao mesmo tempo. Uma instância que faz 200 pedidos por segundo, cada um segurando a conexão por 5 ms, ocupa em média uma conexão (200 × 0,005). O pool grande só ajuda quando as consultas são lentas, e aí o problema é a consulta.

Os tempos do pool

ConfiguraçãoO que fazValor de partida
Máximo de conexõesO teto por instânciaPela conta acima
Mínimo de conexõesQuantas ficam abertas mesmo sem usoPequeno, ou igual ao máximo para evitar a abertura no primeiro pico
Espera por conexãoQuanto um pedido espera antes de falharCurto: 1 a 5 segundos
Tempo ociosoFecha conexões paradasAlguns minutos
Vida máximaRenova cada conexão periodicamente30 minutos; menor que os limites do banco, do proxy e do firewall
Tempo limite da consultaCancela consultas longas (statement_timeout)Pouco acima da consulta mais lenta esperada

A espera por conexão é a mais esquecida. Sem ela, um pool esgotado faz cada pedido novo esperar indefinidamente, os pedidos se acumulam e a aplicação para de responder. Com ela, os pedidos excedentes falham rápido e com uma mensagem clara, como mostra a Implementação: as cinco linguagens recusaram o sexto pedido em cerca de 1 segundo, com o pool de 5 ocupado.

A vida máxima evita conexões que um balanceador ou firewall derrubou sem avisar, e distribui as conexões quando uma réplica nova entra.

Ambientes sem servidor

Funções sem servidor (AWS Lambda, Cloud Functions, Vercel) criam uma instância por pedido simultâneo. Mil pedidos simultâneos são mil instâncias, cada uma com a sua conexão.

SaídaComo funciona
Intermediário (RDS Proxy, PgBouncer, o pooler do Supabase ou do Neon)As funções conectam no intermediário, que mantém poucas conexões com o banco
Driver por HTTPAlguns bancos gerenciados oferecem consultas por HTTP, sem conexão persistente
Pool de uma conexão, fora do manipuladorA conexão é criada uma vez por instância e reaproveitada entre invocações

Resumo da decisão

  1. Um pool por processo, criado na inicialização e compartilhado.
  2. Tamanho pelo banco: algo como núcleos × 2 + discos no total, dividido pelas instâncias, e medido com a sua carga.
  3. Espera por conexão curta, para falhar rápido com o pool esgotado.
  4. Vida máxima e tempo ocioso configurados.
  5. Intermediário quando a soma dos pools passa do limite do banco, ou com funções sem servidor.
  6. Modo transação com disciplina: SET LOCAL, nada de estado de sessão.
  7. Formas que devolvem a conexão sozinhas, para não vazar.

O próximo nível mostra a configuração do pool nas cinco linguagens, testada com o pool esgotado.

Prompts para aprofundar

Ao abrir no ChatGPT ou no Claude, o prompt é enviado ao serviço escolhido pela URL e também fica copiado na sua área de transferência.

  • Dimensionar o pool do meu sistema

    Quero dimensionar o pool de conexões do meu sistema.
    
    Pergunte qual banco e serviço eu uso, quantos núcleos e qual o max_connections do banco, quantas instâncias da aplicação rodam (e quantas podem rodar num pico), quais jobs também conectam e quanto tempo as consultas costumam levar.
    
    Calcule comigo:
    1. O total de conexões que o banco aguenta bem.
    2. O pool por instância, com a margem para jobs e administração.
    3. Se um intermediário compensa.
    4. Os tempos de espera, de ociosidade e de vida máxima.
    5. Como validar os números com um teste de carga.
  • Conexões em ambientes sem servidor

    Minha aplicação roda em funções sem servidor (Lambda, Cloud Functions, Vercel) e conecta num banco relacional.
    
    Pergunte qual plataforma, banco e linguagem eu uso, e quantas execuções simultâneas acontecem nos picos.
    
    Explique:
    1. Por que cada instância da função abre a sua conexão, e o efeito nos picos.
    2. Onde criar a conexão no código da função.
    3. Intermediários gerenciados (RDS Proxy, poolers de bancos gerenciados) e PgBouncer próprio.
    4. Drivers por HTTP, quando o banco oferece.
    5. Os cuidados com estado de sessão em cada opção.
  • Migrar para o PgBouncer em modo transação

    Quero colocar um PgBouncer em modo transação na frente do meu banco.
    
    Pergunte qual linguagem, driver e ORM eu uso, e se o código usa SET, travas consultivas, tabelas temporárias ou LISTEN.
    
    Monte comigo:
    1. A configuração do PgBouncer e o tamanho do pool.
    2. A lista do que precisa mudar no código, e como encontrar cada uso.
    3. A configuração do driver e do ORM para funcionar atrás do PgBouncer.
    4. Um teste que prove que nenhum estado de sessão vaza entre clientes.
    5. Como migrar sem parar o sistema.