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
| Arquitetura | Como funciona | Quando usar |
|---|---|---|
| Pool na aplicação | Cada processo da aplicação mantém o seu pool | O 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 reais | Muitas instâncias, funções sem servidor, ou a soma dos pools passando do limite do banco |
| Os dois | Pool pequeno na aplicação, intermediário na frente do banco | Sistemas 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
| Modo | Empresta a conexão do banco por | Estado de sessão | Aproveitamento |
|---|---|---|---|
| Sessão | Toda a conexão do cliente | Funciona | Baixo: igual a conectar direto |
| Transação | Uma transação | Vaza entre clientes (teste do nível Conceito) | Alto |
| Comando | Um comando | Não há transações de vários comandos | Má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 efetivosPara 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:
| Banco | Instâncias da aplicação | Pool por instância |
|---|---|---|
| 8 núcleos, alvo de cerca de 20 conexões | 2 | 10 |
| 4 | 5 | |
| 20 | 1 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ção | O que faz | Valor de partida |
|---|---|---|
| Máximo de conexões | O teto por instância | Pela conta acima |
| Mínimo de conexões | Quantas ficam abertas mesmo sem uso | Pequeno, ou igual ao máximo para evitar a abertura no primeiro pico |
| Espera por conexão | Quanto um pedido espera antes de falhar | Curto: 1 a 5 segundos |
| Tempo ocioso | Fecha conexões paradas | Alguns minutos |
| Vida máxima | Renova cada conexão periodicamente | 30 minutos; menor que os limites do banco, do proxy e do firewall |
| Tempo limite da consulta | Cancela 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ída | Como 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 HTTP | Alguns bancos gerenciados oferecem consultas por HTTP, sem conexão persistente |
| Pool de uma conexão, fora do manipulador | A conexão é criada uma vez por instância e reaproveitada entre invocações |
Resumo da decisão
- Um pool por processo, criado na inicialização e compartilhado.
- Tamanho pelo banco: algo como núcleos × 2 + discos no total, dividido pelas instâncias, e medido com a sua carga.
- Espera por conexão curta, para falhar rápido com o pool esgotado.
- Vida máxima e tempo ocioso configurados.
- Intermediário quando a soma dos pools passa do limite do banco, ou com funções sem servidor.
- Modo transação com disciplina:
SET LOCAL, nada de estado de sessão. - 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.