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

04 - Boas Práticas

#boas-praticas#checklist#connection-pool#monitoramento#transacoes
video explicativo
Video explicativoEspaco reservado para o video desta anotação — adicione video: https://youtu.be/... no frontmatter da nota.

Boas Práticas de Pool de Conexão

As boas práticas de pooling convergem para um conjunto pequeno de princípios que, quando aplicados com disciplina, eliminam a maior parte dos incidentes de conexão que se vê em produção. O tema não é configurar um pool uma vez, e sim governar o ciclo de vida das conexões como recurso compartilhado de plataforma: quem pode abrir quantas, por quanto tempo, com que monitoramento e qual resposta a saturação. Este arquivo organiza essas práticas em quatro frentes: o escopo correto de empréstimo da conexão (requisição versus transação), a contabilidade agregada de conexões por serviço, o monitoramento essencial com seus limiares e um checklist objetivo para revisão de produção. O objetivo é que o pool deixe de ser fonte de surpresas e vire componente previsível, com comportamento conhecido sob pico, sob failover e sob deploy.

Escopo do empréstimo: requisição versus transação

A decisão de quando abrir e devolver a conexão define a eficiência de todo o sistema. A prática recomendada é o empréstimo por transação: a conexão é obtida no início da unidade transacional e devolvida no commit ou rollback, nunca antes e nunca depois. O empréstimo por requisição inteira é aceitável em fluxos curtos que tocam o banco uma ou duas vezes, mas torna-se desperdício quando a requisição faz chamadas externas lentas entre acessos ao banco, pois a conexão fica presa sem trabalhar. O padrão a evitar é o empréstimo por método utilitário, com múltiplas aquisições e devoluções dentro da mesma requisição, o que fragmenta transações e esconde problemas de consistência.

  • Transação curta e única: obtenha a conexão no limite do caso de uso (o handler do caso de negócio), e não em cada repositório, para que a transação cubra a invariante inteira.
  • Sem I/O externo dentro da transação: chamadas HTTP, envio de e-mail e leitura de fila devem ficar fora do escopo da conexão; o padrão outbox existe exatamente para isso.
  • Devolução garantida por infraestrutura: context managers, try-with-resources e escopos de transação do framework devolvem a conexão mesmo em exceção; abertura manual é exceção justificada.
  • Transações longas como alerta de desenho: se o negócio exige transação de minutos, o problema é de modelagem; repense escopo antes de ajustar timeouts para acomodar.

Um pool por serviço e limites agregados

Cada serviço mantém seu pool, mas o banco enxerga apenas a soma de todos eles, e é esse número agregado que precisa caber no max_connections com folga para operação, migrações e emergências. A contabilidade faz parte do desenho da plataforma, não é detalhe deixado para o primeiro incidente.

Serviço Instâncias Pool por instância Total direto Via pooler
api-pedidos 12 8 96 96
api-pagamentos 6 8 48 48
worker-notificacoes 4 4 16 16
jobs-batch 2 6 12 12
funções serverless variável 1 imprevisível 60 (teto no proxy)
Total no banco 172 direto 232 com teto arbitrado

O exemplo ilustra dois desenhos possíveis: sem pooler, o total direto precisa caber no banco; com pooler em transaction mode, o teto de conexões de servidor é arbitrado no pooler, e as funções serverless param de ser ameaça variável. Práticas associadas:

  • Reserve uma fração do max_connections para superusuário e operação, e monitore o consumo por usuário de banco, não apenas o total.
  • Estabeleça cota por serviço no pooler (max_db_connections por papel), de modo que um serviço descontrolado não consuma a fatia dos demais.
  • Revise a contabilidade a cada novo serviço e a cada mudança de réplicas máxima do auto scaling, tratando a soma como capacidade planejada.
  • Prefira muitas instâncias com pools pequenos a poucas instâncias com pools gigantes, pois a contenção muda de lugar, mas o custo de conexão não.

Monitoramento essencial do pool

O conjunto mínimo de métricas responde três perguntas: o pool está saturado, o banco está lento ou há vazamento. Sem elas, o diagnóstico é adivinhação sob pressão.

Métrica Fonte Limiar típico de alerta Interpretação
Tempo de aquisição (p95/p99) Pool (Hikari, driver) p99 > 50 ms sustentado Fila por conexão; checar causa
Threads esperando (wait queue) Pool Qualquer valor estável > 0 Saturação de pool ou vazamento
Timeouts de aquisição Pool > 0 em regime estável Incidente imediato
Conexões ativas versus idle Pool e banco Idle próximo de zero no pico Pool pequeno para a carga
Conexões por banco no agregado Pooler ou pg_stat_activity > 80% do max_connections Risco de recusa de conexão
Conexões emprestadas há > 60 s Leak detection Qualquer ocorrência Vazamento: bug de código
Transações mais longas Banco (pg_stat_activity) > 30 a 60 s Risco de bloqueio e de vacuum
# Regras de alerta (Prometheus) para a saúde do pool
groups:
  - name: pool-conexoes
    rules:
      - alert: PoolEsperaAlta
        expr: histogram_quantile(0.99, rate(db_pool_acquire_seconds_bucket[5m])) > 0.05
        for: 5m
      - alert: PoolTimeoutAquisicao
        expr: increase(db_pool_acquire_timeout_total[5m]) > 0
        for: 1m
      - alert: ConexoesNoBancoAcimaDoTeto
        expr: pg_stat_database_numbackends / pg_settings_max_connections > 0.8
        for: 10m
      - alert: LeakSuspeito
        expr: increase(db_pool_leak_detected_total[10m]) > 0
        for: 1m

Proteção contra bloqueio e transação longa

Transação longa é o inimigo silencioso do pool: ela prende conexão, retém locks, bloqueia vacuum no PostgreSQL e enfileira tudo atrás dela. As práticas de proteção combinam limite, observação e desenho:

  • Defina idle_in_transaction_session_timeout no banco (por exemplo, 60 segundos) para matar sessões que abriram transação e pararam de trabalhar, o erro clássico de código que esquece commit.
  • Configure statement_timeout e lock_timeout por papel, conforme o arquivo de configuração deste módulo, e trate exceções por fluxo, não por exceção geral.
  • Monitore a transação mais longa corrente e alerte quando passar do orçamento; no runbook, a ação é identificar a sessão e terminar com cuidado.
  • Padrões de alívio aplicam-se aqui também: bulkhead separa o pool de consultas lentas do pool transacional, e o circuit breaker evita que fila de banco encha durante degradação.
  • Operações em massa (backfill, migração de dados) rodam em janela própria, com pool dedicado e limites próprios, nunca no pool do OLTP.

Deploy, failover e eventos que mexem no pool

Dois eventos rotineiros testam o desenho do pool sem que ninguém os tenha configurado para isso: o deploy que recicla todas as instâncias e o failover do banco. No deploy, a rajada de instâncias novas abrindo pools completos somada às antigas derrubando conexões pode dobrar temporariamente o consumo; padrões de proteção incluem rampa no auto scaling, aquecimento do pool antes do health check liberar tráfego e gateways que drenam instâncias antigas com elegância. No failover, conexões presas em transação morrem com erro, e o comportamento esperado é o retry com idempotência no lado da aplicação; o pool deve fechar imediatamente conexões marcadas como quebradas, evitando entregar conexão morta em cascata. Teste os dois eventos periodicamente em staging, medindo o tempo de recuperação do pool e o p99 das rotas críticas durante a transição, pois o desenho que funciona em dia tranquilo é o que costuma falhar exatamente no dia anômalo.

Erros comuns

  • Pool gigante como resposta a todo sintoma, invertendo a relação de causa: a contenção no banco piora com cada conexão adicionada.
  • Vazamento silencioso de conexões em rotas de erro, onde exceção pula o código de devolução; sem leak detection, o sintoma aparece dias depois como esgotamento.
  • Bloqueio em transação longa segurando fatia do pool e disparando espera em cascata, diagnosticado como "problema do banco" sem olhar pg_stat_activity.
  • Uma conexão por requisição mantida durante chamadas externas lentas, transformando latência de terceiro em consumo de conexão de banco.
  • Limites agregados nunca contabilizados, com a primeira recusa de conexão acontecendo em pleno pico de vendas.
  • Falta de alerta para wait queue, com equipe descobrindo saturação por reclamação de usuário, e não por métrica.
  • Timeouts desconectados entre camadas (requisição maior que aquisição maior que statement), criando esperas sem valor e retries sem sentido.
  • Reuso de um mesmo pool para múltiplos bancos ou papéis, impossibilitando limites e auditoria separados por consumidor.
  • Health check do serviço que não valida a cadeia de banco, liberando tráfego para instâncias com pool morto após falha de rede.

Boas práticas

  • Padronize o empréstimo por transação como política de código, com revisão automática de exceções e justificativa registrada.
  • Mantenha a hierarquia de devolução garantida por infraestrutura: proíba abertura manual de conexão fora da camada de persistência em revisão de código.
  • Mantenha a contabilidade de conexões por serviço viva em documento de plataforma, revisada em todo onboarding de sistema e em mudanças de escala.
  • Exporte o conjunto completo de métricas do pool e do banco para o mesmo dashboard, com limiares e runbook por alerta definidos antes do go-live.
  • Configure a hierarquia de timeouts coerente do maior para o menor (deadline da requisição, aquisição, statement, lock), testada sob falha real.
  • Habilite leak detection em produção com alerta, tratando cada ocorrência como bug com stack trace anexado.
  • Separe pools e papéis por natureza de carga (OLTP, batch, analítico em réplica), com limites independentes e tolerância de queda separada.
  • Inclua o pool no teste de caos: injete atraso e falha no banco e verifique que degradação, retry e recuperação seguem o desenho esperado.
  • Espelhe as práticas em ambiente de desenvolvimento, com pool reduzido mas com a mesma hierarquia de timeouts, para que o comportamento em staging preveja o de produção.
  • Diferencie o tratamento de falha por tipo de consumidor: requisição de usuário recebe erro rápido com degradação; job batch recebe retry longo com backoff.
  • Revise o desenho de pool junto com o plano de capacidade trimestral, usando os últimos três meses de métricas de espera e saturação como evidência.
  • Normalize os nomes de métrica de pool entre serviços da plataforma, para que dashboards globais agreguem sinais sem retrabalho por equipe.
  • Registre decisões de pooling relevantes (modo do pooler, tamanho, mudanças de limite) em ADR, mantendo o histórico de por que cada número existe.

Checklist de produção

O checklist a seguir resume o estado mínimo esperado de um serviço maduro em pooling; cada item é verificável e vale para revisão de arquitetura e para auditoria de operação:

  1. Pool configurado explicitamente (tamanho, mínimos, timeouts), sem defaults de framework herdados sem revisão.
  2. Fórmula de tamanho documentada com a medição que a justificou, e contabilidade agregada compatível com o max_connections do banco.
  3. Timeouts calibrados em camadas coerentes, incluindo statement e lock timeout no banco por papel de conexão.
  4. Leak detection ativo com alerta e procedimento de investigação por stack trace.
  5. Métricas de aquisição, espera e timeouts exportadas, com dashboard e alertas revisados nos últimos seis meses.
  6. Limites por serviço no pooler (quando houver) e cota agregada monitorada contra o teto do banco.
  7. Teste de carga com pico real executado no desenho atual do caminho de conexão, incluindo pooler e failover.
  8. Runbook de saturação e de vazamento publicado, com passos objetivos e dono definido.
  9. Transações longas monitoradas com limiar de alerta, e padrões de I/O externo dentro de transação proibidos em revisão de código.
  10. Plano de capacidade revisitado a cada mudança de réplicas máximas, de arquitetura ou de perfil de tráfego.

Serviços que cumprem esse conjunto raramente aparecem em post-mortems com "esgotamento de conexões" como causa raiz, e quando aparecem, o runbook encurta o tempo de restauração a minutos.