Home Comunidade Eventos YouTube Networks Desafios // produtos Academy News CTI | CVE Contato

02 - Configuração (Tamanho, Timeout)

#connection-pool#hikaricp#pgbouncer#sizing#timeout
video explicativo
Video explicativoEspaco reservado para o video desta anotação — adicione video: https://youtu.be/... no frontmatter da nota.

Configuração do Pool (Tamanho, Timeout)

Configurar um pool de conexões é um problema de concorrência e de tempos de espera, não de adivinhação com valores bonitos. O tamanho do pool define quantas consultas podem estar em execução simultânea no banco a partir daquele serviço, e os timeouts definem quanto tempo o sistema tolera em cada etapa do ciclo de vida da conexão antes de falhar de forma controlada. A maioria dos pools mal configurados que se vê em produção erra para cima, com dezenas ou centenas de conexões por instância, gerando contenção no banco em vez de resolver gargalo. Este arquivo apresenta as fórmulas de dimensionamento, os knobs de timeout que precisam existir em toda configuração séria, a validação de conexões, a detecção de vazamento e a métrica que resume a saúde do pool: o tempo de espera por conexão.

Conceito

O tamanho certo do pool nasce de uma pergunta física: quantas consultas o banco consegue executar ao mesmo tempo com ganho real de paralelismo. A resposta quase nunca é "quantas as requisições pedirem", e sim um número próximo ao número de núcleos de CPU disponíveis para o banco, somado a algum slack para consultas que esperam I/O. Cada conexão ativa ocupa CPU, memória e capacidade de cache do servidor; conexões demais competem entre si, aumentam latência de todas e pioram o uso do cache de dados. O dimensionamento correto faz o contrário do instinto: restringe a concorrência no banco e enfileira o excesso na aplicação, onde esperar é barato e controlável. Timeouts completam o desenho, transformando degradação silenciosa em falha rápida e rastreável, que é sempre mais barata de operar.

Sizing do pool

A fórmula clássica de referência, popularizada pela documentação do HikariCP, é direta:

pool_size = cores_do_banco * 2 + discos_efetivos (spin disk)
pool_size = cores_do_banco * 2  (SSD, caso comum hoje)

Em um banco com 8 núcleos dedicados, um pool de 16 conexões por serviço consumidor já é generoso; quatro serviços multiplicam para 64 conexões físicas, e o número deve caber em max_connections com folga para operação, replicação e emergências. Duas correções ao modelo básico são obrigatórias. Primeiro, cargas de leitura que passam a maior parte do tempo esperando I/O toleram um multiplicador maior que cargas de CPU pura; meça o tempo de consulta real antes de ajustar. Segundo, a fórmula vale por serviço; o tamanho agregado é a soma dos pools de todas as instâncias, e auto scaling multiplica o problema. Em Kubernetes, dez réplicas com pool de 20 são 200 conexões no banco; se isso estourar o teto, o pooler externo ou o limite por instância precisa entrar no desenho. Comece pequeno, meça a espera por conexão e o throughput, e aumente por experimentos controlados; pool desnecessariamente grande custa contenção, não performance.

# HikariCP: configuração de produção típica
maximum_pool_size=10            # cores do banco * 2, por serviço
minimum_idle=10                 # pool fixo evita rajadas de abertura
connection_timeout=3000         # espera máxima por conexão livre
validation_timeout=1000         # teto da validação antes de usar
idle_timeout=0                  # irrelevante quando min == max
max_lifetime=1740000            # abaixo do timeout de idle do servidor
keepalive_time=30000            # ping periódico em conexões ociosas
leak_detection_threshold=60000  # alerta de conexão emprestada > 60 s

Teorema de Little e concorrência

O teorema de Little dá o vocabulário formal do dimensionamento: a concorrência média é a taxa de chegada multiplicada pelo tempo de serviço. Com 200 requisições por segundo e consultas de 5 milissegundos, o serviço precisa em média de uma única conexão para sustentar a carga; o que exige pool é o pico e a variância, não a média. O dimensionamento sério pega o p99 do tempo de consulta e o pico de requisições por segundo, calcula a concorrência desse cenário e soma folga para rajadas. A conta explica por que pools gigantes não ajudam: quando o tempo de serviço sobe (banco lento), a concorrência sobe junto, e o pool grande apenas amplifica quantas consultas lentas competem simultaneamente no banco, aprofundando o problema. O pool correto limita o dano, mantém o banco em regime estável e transfere a espera para a fila da aplicação, onde ela é observável e tratável.

Timeouts essenciais

Uma configuração de produção completa precisa de quatro famílias de timeout, cada uma protegendo uma etapa diferente. Faltou timeout, e a degradação vira fila infinita; timeout errado, e o sistema falha antes de tentar o bastante. Os nomes variam entre implementações, mas os papéis são universais.

Timeout Protege o quê Valor usual Falta dele
Connection timeout (aquisição) Thread esperando conexão livre 1 a 5 segundos Fila infinita e thread pool esgotado
Idle timeout Conexão ociosa além do mínimo 5 a 10 minutos Conexões zumbis cortadas pelo firewall
Max lifetime Vida total da conexão 30 minutos (abaixo do idle do banco) Conexões mortas descobertas em produção
Validation timeout Tempo da checagem antes do uso 1 segundo Handshakes lentos travando entrega
Statement/query timeout Consulta presa no banco Por consulta, segundos Consultas ruins saturando o pool
Lock timeout Espera por lock no banco 1 a 5 segundos Deadlock lento e transações empilhadas
Leak detection threshold Conexão nunca devolvida 30 a 90 segundos Vazamento invisível até esgotar o pool

A interação com o servidor importa: o PostgreSQL encerra conexões ociosas conforme idle_in_transaction_session_timeout e tcp_keepalives, e provedores como RDS têm timeout de idle próprio; o max_lifetime do pool deve ficar abaixo desses limites para que a reciclagem aconteça no seu controle, não na hora da falha. Statement timeout é a proteção mais valiosa e mais esquecida: uma consulta sem índice que roda por dois minutos prende uma conexão de pool por dois minutos, e basta um punhado delas para saturar qualquer pool bem dimensionado.

Valores iniciais por perfil de carga

Fórmulas dão o ponto de partida, e o perfil de carga do serviço calibra o ajuste fino. Os valores abaixo servem como chute inicial para revisão em teste de carga, nunca como configuração final eterna:

Perfil de carga Tamanho inicial do pool Connection timeout Statement timeout
API transacional web 8 a 16 3 s 5 a 10 s
Job batch noturno 4 a 8 10 s 60 s a 5 min
Consulta analítica em réplica 4 a 8 10 s 30 a 120 s
Autenticação e sessão 4 a 8 2 s 2 a 5 s
Serverless atrás de proxy 1 a 2 por instância 3 s 10 s

Em serverless, o número agregado é o que importa: mil funções com pool de 2 ainda somam 2 mil conexões potenciais, e a única contenção eficaz mora no proxy, com o pool local quase mínimo.

Validação e teste de conexão

Conexões morrem silenciosamente: firewall corta a sessão, o banco reinicia, o failover troca o primário. Sem validação, a requisição que receber a conexão morta paga o custo do erro e do retry. O mecanismo clássico é a test query (SELECT 1) executada antes da entrega, barata e suficiente para a maioria dos casos; drivers modernos e o próprio protocolo PostgreSQL/MySQL oferecem ping nativo mais leve que uma consulta SQL completa. A validação deve ter timeout próprio curto, para que uma rede degradada não transforme cada checkout em espera de segundos. Em pools por trás de PgBouncer ou RDS Proxy, a validação pode morar na camada externa, e a aplicação confia na conexão entregue; o desenho precisa definir quem valida, porque validar em duas camadas dobra custo sem ganho proporcional. Keepalive periódico em conexões ociosas evita que firewalls estaduais reciclem sessões paradas, o modo de falha mais comum em redes corporativas.

-- PostgreSQL: timeouts definidos por papel, no próprio banco
ALTER ROLE app_oltp SET statement_timeout = '10s';
ALTER ROLE app_oltp SET lock_timeout = '3s';
ALTER ROLE app_batch SET statement_timeout = '5min';
ALTER ROLE app_batch SET idle_in_transaction_session_timeout = '2min';
-- A camada de aplicação respeita os mesmos limites nos parâmetros do pool

Leak detection

Vazamento de conexão é o erro de aplicação que nenhum dimensionamento compensa: código que pede conexão e não devolve, geralmente por falta de close ou de transação nunca commitada. O sintoma é o pool esvaziar gradualmente até todas as requisições esperarem o connection timeout, e a recuperação só vem com restart. A detecção baseia-se em thresholds: se a conexão está emprestada há mais de N segundos, o pool registra o stack trace de quem a pegou, apontando a linha exata do vazamento. Em produção, o valor típico fica entre 30 e 90 segundos, acima do p99 de consulta mais lenta legítima, e o alerta deve ser tratado como bug de aplicação, nunca como sinal para aumentar o pool. Padrões de uso diminuem o risco estrutural: gerencie conexões com context managers ou try-with-resources, centralize acesso ao banco em repositórios, e proíba abertura manual de conexões fora da infraestrutura de persistência.

# Padrão garantido de devolução da conexão
with pool.connection() as conn:      # context manager devolve sempre
    with conn.cursor() as cur:
        cur.execute("SELECT ...")

A espera por conexão como métrica

O tempo de espera por conexão é a métrica que resume a saúde do pool e deve ser exportada como histograma por serviço. Espera sempre zero indica pool com folga; espera subindo em degraus indica saturação, e a investigação distingue três causas: banco lento (consultas demoram mais, retenção cresce), pool pequeno (banco saudável, mas sem conexões) ou vazamento (conexões somem do ciclo). Acompanhe junto: conexões ativas versus idle, taxa de timeout de aquisição, tempo de consulta p99 e o gráfico agregado de conexões no banco. A regra de operação é agir na causa: saturação com banco folgado pode aceitar aumento controlado de pool; saturação com banco no limite pede otimização de consultas ou partitioning do tráfego, nunca mais conexões. Alertas úteis: p95 de espera acima de dezenas de milissegundos por cinco minutos e qualquer timeout de aquisição acima de zero em regime estável.

Diagnóstico rápido pela tríade de sinais
  espera alta + banco folgado          -> pool pequeno: aumentar com controle
  espera alta + banco saturado         -> consultas lentas: otimizar ou rotear
  espera alta + ativas < tamanho do pool -> vazamento: caçar stack traces
  espera zero + idle alto constante    -> pool grande demais: reduzir

Erros comuns

  • Pool gigante "para garantir", com centenas de conexões por instância, causando contenção de CPU e degradação do cache no banco.
  • minimum_idle diferente do máximo em ambientes com pico abrupto, que coloca a rajada de abertura de conexões no meio do pico de tráfego.
  • max_lifetime acima do timeout de idle do banco ou do provedor, com reciclagem acontecendo via erro em produção em vez de via pool.
  • Statement timeout ausente, deixando consultas desastrosas prenderem conexões por minutos e enfileirarem o serviço inteiro.
  • Leak detection desligado ou sem alerta, permitindo vazamento gradual até o esgotamento completo do pool.
  • Aumento de pool como primeira resposta a saturação, quando a causa real é consulta sem índice ou transação longa.
  • Timeouts herdados de template sem calibração por serviço, com valores altos demais para APIs de usuário e baixos demais para jobs batch.
  • Statement timeout global único para todo o tráfego, que ou trava consultas legítimas ou não protege o OLTP; separe por papel de conexão.
  • Contar apenas o tamanho do pool local e ignorar o agregado, sobretudo com auto scaling multiplicando instâncias em minutos.

Boas práticas

  • Comece com pool pequeno e mensurável (cores * 2 como teto inicial), e aumente apenas com evidência de espera por conexão com banco folgado.
  • Fixe minimum_idle igual a maximum_pool_size na maioria dos serviços, trocando estabilidade de latência por elasticidade que não compensa.
  • Configure a família completa de timeouts em template de plataforma: aquisição, validação, idle, lifetime, statement e lock, com valores revisados por serviço.
  • Posicione max_lifetime com folga abaixo dos timeouts de servidor e provedor, e verifique o comportamento pós-failover com teste real.
  • Exporte métricas do pool para o mesmo backend do serviço (espera p99, ativas, idle, timeouts, vazamentos) e monte alertas com runbook.
  • Estabeleça statement timeout padrão por papel de conexão, com exceções documentadas para cargas analíticas legitimamente longas.
  • Trate todo alerta de leak como incidente de código, com stack trace anexado ao ticket, e carregue o pipeline com teste de carga que exponha o padrão de consumo e devolução.
  • Revise o sizing a cada mudança de réplicas e de perfil de carga, integrando o cálculo de pool ao pipeline de auto scaling para evitar estouro agregado.
  • Registre os valores escolhidos e o motivo em ADR, com a medição de espera por conexão que justificou cada aumento ou redução.
  • Configure keepalive e validação de acordo com o caminho de rede real: NAT, firewall corporativo e proxies TCP reciclam sessões silenciosamente.