03 - Ferramentas (PgBouncer, HikariCP)
video: https://youtu.be/... no frontmatter da nota.Ferramentas de Pool de Conexão (PgBouncer, HikariCP)
As ferramentas de pooling se dividem em duas famílias complementares: pools embutidos no processo da aplicação, como o HikariCP no mundo Java, e poolers externos que ficam na frente do banco, como o PgBouncer no ecossistema PostgreSQL. A primeira família resolve o reuso dentro de um serviço; a segunda resolve o problema agregado de dezenas ou centenas de processos convergindo no mesmo banco. Em produção madura, as duas famílias coexistem, cada uma com papel claro, e a escolha de modos e parâmetros determina se o multiplexamento funciona ou quebra funcionalidades da sessão. Este arquivo detalha o funcionamento e a configuração do PgBouncer e do HikariCP, compara pooler externo com acesso direto ao banco, cobre alternativas gerenciadas como o RDS Proxy e fecha com a matriz de decisão entre as opções.
Conceito
O pool na aplicação vive no mesmo processo do serviço, entrega conexões em microssegundos e conhece o contexto da aplicação, mas escala junto com o número de instâncias. O pooler externo é um proxy de protocolo de banco, transparente para o cliente, que multiplexa um grande número de conexões de cliente em um número pequeno de conexões de servidor; ele é a resposta quando instâncias são muitas, efêmeras ou heterogêneas. A distinção técnica central do pooler externo é o modo de multiplexação: sessão mantém a conexão de servidor presa ao cliente até desconectar, transação presa apenas enquanto a transação roda, e statement troca de servidor a cada consulta. Quanto mais agressivo o modo, maior a economia de conexões e maior a restrição sobre recursos de sessão. No lado da aplicação, ferramentas como HikariCP consolidaram-se por simplicidade, correção e observabilidade integrada, virando o default de frameworks Java modernos.
PgBouncer: modos e comportamento
O PgBouncer é o pooler padrão de mercado para PostgreSQL, leve, single-binary e com overhead mínimo por conexão. Os três modos definem o contrato de multiplexação:
- Session pooling: o cliente fica com a conexão de servidor até se desconectar; compatível com todas as funcionalidades de sessão, mas economiza pouco, útil para números moderados de conexões de longa vida.
- Transaction pooling: a conexão de servidor é devolvida ao fim de cada transação; grande redução de conexões, é o modo mais usado, mas recursos de sessão deixam de ser confiáveis entre transações.
- Statement pooling: a conexão troca a cada statement; agressivo, incompatível com transações multi-statement, aplicável a padrões muito específicos de consulta única.
No modo transaction, recursos ligados à sessão precisam de cuidado: SET de variáveis de sessão, prepared statements do protocolo de cliente, cursors WITH HOLD e advisory locks com tempo de vida além da transação quebram ou se comportam de forma imprevisível. O parâmetro server_reset_query define o comando executado na devolução da conexão (o clássico DISCARD ALL), garantindo limpeza de estado; no modo transaction com clientes disciplinados, DISCARD ALL é o seguro padrão, ao custo de uma ida e volta por devolução. Outros knobs importantes: default_pool_size por par usuário/banco, max_client_conn como teto de clientes, server_idle_timeout para devolver conexões ociosas e reserve_pool_size para absorver picos curtos.
; pgbouncer.ini: núcleo de uma configuração de produção
[databases]
appdb = host=10.0.0.5 port=5432 dbname=appdb
[pgbouncer]
listen_addr = 0.0.0.0
listen_port = 6432
auth_type = scram-sha-256 ; autenticação forte ponta a ponta
auth_file = /etc/pgbouncer/userlist.txt
pool_mode = transaction ; multiplexa por transação
default_pool_size = 20 ; conexões de servidor por (usuário, banco)
max_client_conn = 1000 ; teto de clientes aceitos
reserve_pool_size = 5 ; absorve picos por reserve_pool_timeout
server_reset_query = DISCARD ALL ; limpa estado na devolução
server_idle_timeout = 60 ; fecha servidores ociosos
max_db_connections = 60 ; teto global por banco alvo
admin_users = pgbouncer_admin
O console administrativo responde no pseudo-banco pgbouncer, com comandos SHOW STATS, SHOW POOLS e RELOAD para reconfiguração sem queda. Em transação pooling, clientes PostgreSQL recentes negociam prepared statements por cache de sessão (feature max_prepared_statements do PgBouncer), reduzindo o atrito histórico com drivers que usavam prepared statements por padrão.
HikariCP: o padrão Java
O HikariCP é o pool padrão do Spring Boot e a referência de desempenho no ecossistema Java, conhecido por otimizações de bytecode e gestão de fila sem locks. A configuração mínima correta cabe em poucas propriedades, e a instrumentação nativa expõe as métricas que este módulo preconiza: conexões ativas e idle, threads esperando, tempo total de aquisição e taxa de timeouts.
# HikariCP com SQL Server, MySQL ou PostgreSQL via JDBC
maximum_pool_size=12
minimum_idle=12
connection_timeout=3000
validation_timeout=1000
max_lifetime=1740000
leak_detection_threshold=60000
connection_init_sql=SET search_path TO app, public
data_source_properties.cachePrepStmts=true
Integrado a Micrometer, o Hikari publica métricas que devem ser exportadas para o backend de métricas do serviço:
| Métrica Hikari | O que revela | Ação típica ao alertar |
|---|---|---|
hikaricp_connections_active |
Conexões em uso agora | Correlacionar com latência do banco |
hikaricp_connections_pending |
Threads esperando conexão | Saturação do pool ou do banco |
hikaricp_connections_acquire_seconds |
Tempo de aquisição (p50/p99) | Investigar fila antes de ampliar pool |
hikaricp_connections_timeout_total |
Falhas de aquisição | Incidente: aplicar runbook de saturação |
Uma regra de convivência: quando o serviço já passa por um pooler externo em transaction pooling, o Hikari mantém seu papel de reuso local, e o par de tamanhos (pool local versus pool de servidor no pooler) precisa ser desenhado junto, para que dez instâncias de pool 12 não pretendam 120 conexões físicas no banco.
Pooler externo versus acesso direto
A decisão entre acesso direto com pool local e pooler externo é de escala e de arquitetura de consumo, não de gosto. Acesso direto é mais simples, menos um componente para operar, e entrega latência mínima; funciona enquanto a soma de conexões de todas as instâncias caber no banco com folga. O pooler externo entra quando instâncias multiplicam, funções serverless e jobs abrem conexões erráticas, ou quando o limite de conexões precisa ser arbitrado entre serviços. O custo é um componente extra no caminho crítico, com atenção própria a limites, failover e latência.
| Aspecto | Acesso direto (pool local) | PgBouncer | RDS Proxy |
|---|---|---|---|
| Componente extra | Nenhum | Proxy self-managed | Serviço gerenciado AWS |
| Multiplexação | Nenhuma (soma dos pools) | Session, transaction, statement | Transaction (com ses. pinning) |
| Suporte a prepared statements | Total | Depende do modo | Pinnagem de sessão |
| Failover transparente | Não | Não (requer orquestração) | Sim, integrado |
| Autenticação | Credencial do driver | auth_file ou auth_query | IAM nativo |
| Melhor caso de uso | Poucas instâncias estáveis | Muitas instâncias, PostgreSQL | Lambda e EC2 sobre RDS |
Entre o pool local puro e o pooler dedicado, existe ainda o caminho do proxy direto por infraestrutura, em que o acesso do aplicativo passa a apontar para um endpoint de serviço (PgBouncer atrás de service de Kubernetes, por exemplo) e o banco real fica inacessível a clientes. Esse desenho tem três ganhos práticos que justificam o esforço: o segredo do banco real deixa de estar em cada instância, os limites agregados ficam arbitrados em um ponto único e o failover de banco pode ser absorvido por reconfiguração do pooler sem tocar nas aplicações. O custo é que toda a cadeia de TLS e autenticação precisa ser repensada, com auth_query ou auth_file sincronizados com o gerenciador de credenciais, e o pooler torna-se componente de disponibilidade crítica, exigindo par com monitoramento e procedimento de upgrade. A migração para esse desenho vale a pena a partir do momento em que mais de um serviço relevante compartilha o mesmo banco e a contabilidade de conexões deixa de ser trivial.
Topologias de implantação do pooler
Onde o pooler roda é uma decisão de arquitetura com trade-offs operacionais claros. Na topologia centralizada, um par de PgBouncers atrás de endereço virtual atende todos os serviços, simplificando gestão de limites e métricas globais, ao custo de um hop a mais e de componente compartilhado na rota. Na topologia sidecar, cada instância (ou cada nó de Kubernetes via DaemonSet) roda um PgBouncer local, mantendo o multiplexamento perto da aplicação e eliminando o hop de rede extra, ao custo de gestão distribuída de configuração. Em ambientes Kubernetes, o pooler costuma subir como Deployment dedicado com headless service e monitoramento próprio, dimensionado com limites coerentes com max_connections do primário e das réplicas. A escolha mista também existe: sidecar para tráfego transacional de alta frequência e pooler central para jobs e acessos menos críticos, com limites separados por camada.
| Topologia | Latência adicional | Gestão | Quando usar |
|---|---|---|---|
| Pooler central (par + VIP) | Um hop | Simples, centralizada | Frotas grandes e heterogêneas |
| Sidecar por nó | Quase zero | Distribuída | Kubernetes e tráfego quente |
| Proxy gerenciado | Um hop gerenciado | Zero (provedor) | Lambda e RDS |
| Sem pooler | Nenhum | Nenhuma | Poucas instâncias estáveis |
Alternativas gerenciadas e outros poolers
Além do par clássico, o mercado oferece variações com casos de uso claros. O RDS Proxy adiciona ao pooling o failover transparente de instâncias RDS e autenticação IAM, e é praticamente obrigatório para workloads Lambda sobre RDS, onde cada instância fria ameaçaria abrir conexões próprias. O Odyssey, do time do PostgreSQL Professional, é um pooler moderno com regras de roteamento e TLS robusto, posicionado como alternativa de alta concorrência ao PgBouncer. No MySQL/MariaDB, ProxySQL cumpre papel análogo ao PgBouncer, somando roteamento de leitura para réplicas e reescrita de consultas. Oracle e SQL Server têm mecanismos próprios (reserva de pool no driver OCI, pooling embutido via ADO.NET), e serviços serverless como Aurora Serverless já trazem um data API que elimina conexões persistentes. O critério de escolha entre todos é o mesmo trio: protocolo do seu banco, modo de pooling suportado e custo operacional de mais um componente no caminho crítico.
Erros comuns
- PgBouncer em transaction pooling com recursos de sessão no código (
SET, advisory locks, cursors), gerando comportamento intermitente difícil de reproduzir. server_reset_queryvazio ou removido "por performance", deixando estado sujo vazar entre clientes no mesmo servidor.- Pool local grande somado a pooler externo pequeno, com a fila se formando no pool externo e o pool local ocioso carregando conexões abertas.
max_client_connedefault_pool_sizeconfundidos, onde o teto de clientes é ajustado esperando efeito na concorrência de servidores.- HikariCP
maximum_pool_sizecopiado de outro serviço sem considerar o número de instâncias rodando em paralelo. - RDS Proxy com pinnagem de sessão permanente por causa de
SETnoconnection_init_sql, anulando o multiplexamento prometido. - Pooler externo sem métricas próprias (SHOW STATS desativado), operando às cegas a camada mais sensível do banco.
- Autenticação trocada no pooler sem alinhar
auth_typecom o método do banco (scram versus md5), derrubando todas as conexões no reload. - Espera por conexão monitorada apenas no pool local, enquanto a fila real cresce no pooler externo sem alerta algum.
Boas práticas
- Fixe a matriz de decisão por workload: aplicação estável e pouca instância fica com pool local; muitas instâncias ou serverless recebem pooler externo ou proxy gerenciado.
- Padronize transaction pooling como modo padrão do PgBouncer, com revisão de uso de recursos de sessão como critério de aceite de código.
- Mantenha
DISCARD ALLnoserver_reset_queryaté medir o custo real e justificar redução, tratando limpeza como requisito de correção. - Exporte métricas das duas camadas (Hikari e PgBouncer) para o mesmo dashboard, com o tempo de fila do pooler visível ao lado do tempo de aquisição local.
- Defina limites por banco e por usuário no pooler (
max_db_connections), protegendo o banco de um serviço mal comportado sobressair os demais. - Teste failover com o pooler no caminho, pois o comportamento de conexões presas em transação durante o failover precisa ser observado, não presumido.
- Documente por serviço o desenho completo do caminho de conexão: driver, pool local, pooler, limites e timeouts, com dono e alerta associados.
- Valide no CI o uso de recursos de sessão (advisory locks, SET, cursors) em serviços declarados como compatíveis com transaction pooling.
- Mantenha o pooler fora do caminho de acessos administrativos e de migrações, que exigem sessão dedicada e não devem competir por conexões de aplicação.
- Pratique upgrade do pooler com o mesmo rigor do banco: teste em staging com carga real e tenha rollback definido, pois ele está no caminho crítico.
- Escolha prepared statements conscientemente: em transaction pooling, prefira protocolo simples ou versões do pooler com cache de prepared statements.
- Separe usuários de banco por serviço no pooler, permitindo limites, estatísticas e auditoria por consumidor em vez de um usuário compartilhado.