O Blueprint de Pooling de Conexões
Cada conexão de cliente PostgreSQL mapeia para um processo de backend real no servidor, não para um handle barato em memória.
Esse único fato impulsiona quase tudo sobre como o pooling de conexões funciona e por que ele existe.
Frameworks de aplicativos que abrem uma nova conexão por solicitação, ou que escalam horizontalmente para dezenas de instâncias, podem multiplicar esse custo em milhares de processos de backend competindo pela mesma CPU e memória.
O pooling de conexões existe para quebrar essa multiplicação, posicionando-se entre muitas conexões de cliente e um conjunto muito menor e estável de backends de servidor.
Esta página é a base conceitual para o restante da seção connection-pooling-workload.
Resumo
- Um pooler multiplexa muitas conexões de cliente em um conjunto menor e estável de backends de servidor PostgreSQL, desacoplando a contagem de conexões do aplicativo da contagem de processos do lado do servidor.
- Por que Importa: Processos de backend carregam sobrecarga real de memória e agendamento, então conexões não agrupadas em escala degradam a taxa de transferência e a latência, mesmo quando a CPU tem ciclos livres.
- Conceitos-Chave: processo de backend, pool_mode, multiplexação, pool wait, estado da sessão, isolamento de carga de trabalho.
- Quando Usar: Qualquer serviço com mais clientes concorrentes do que o banco de dados pode executar confortavelmente como backends nativos, especialmente aplicativos sem servidor ou escalados horizontalmente.
- Limitações / Trade-offs: Modos de pooling agressivos trocam recursos SQL de nível de sessão, como tabelas temporárias e locks de aviso, por maior densidade de conexão.
- Tópicos Relacionados: planejamento de capacidade, separação de carga de trabalho OLTP/OLAP, configuração do PgBouncer, comportamento de prepared statements.
Fundamentos
Um processo de backend é o que o PostgreSQL inicia para cada conexão, um processo completo do sistema operacional com seu próprio contexto de memória, não uma thread ou uma corrotina leve.
Cada backend pode alocar até work_mem por operação de ordenação ou hash que executa, e ele mantém seus próprios caches privados e estado de nível de conexão enquanto existir.
É por isso que max_connections não é simplesmente um dial de capacidade para aumentar quando um aplicativo fica sem espaço.
Dobrar max_connections dobra a pegada de memória no pior caso e o número de processos que o agendador do SO tem que gerenciar, e além de um certo ponto essa sobrecarga de agendamento custa mais taxa de transferência do que as conexões extras fornecem.
Um pooler resolve isso posicionando-se entre os clientes do aplicativo e o PostgreSQL, aceitando muitas conexões de cliente leves e multiplexando-as em um conjunto muito menor e fixo de backends de servidor reais.
A analogia útil é um restaurante com um número fixo de mesas: um recepcionista na porta (o pooler) acomoda um fluxo muito maior de convidados que chegam (conexões de cliente) reutilizando mesas assim que um grupo termina, em vez de a cozinha tentar construir fisicamente uma nova mesa para cada convidado que entra.
O PgBouncer é o pooler em que esta seção se concentra, pois é o padrão de fato na frente de clusters PostgreSQL executando a versão 18.4 e anteriores.
A questão central que um pooler tem que responder é exatamente quando um backend de servidor é reutilizado para um cliente diferente, e essa resposta é o que pool_mode controla.
Mecânicas e Interações
pool_mode define a granularidade com que um backend de servidor retorna ao pool, e essa granularidade determina quais recursos SQL permanecem seguros.
Session pooling (pooling de sessão) fixa um backend de servidor a uma conexão de cliente por toda a sua vida útil, o que preserva tudo o que uma conexão direta normal suporta, incluindo tabelas temporárias, LISTEN/NOTIFY e SET de nível de sessão.
Transaction pooling (pooling de transação) retorna o backend ao pool assim que uma transação é confirmada ou revertida, o que multiplica drasticamente a densidade de conexão, mas quebra qualquer coisa que dependa de estado que sobreviva entre transações.
Statement pooling (pooling de instrução) retorna o backend após cada instrução individual, o que é raro na prática, pois quebra completamente transações de múltiplas instruções e é incompatível com quase todos os ORMs.
A troca não é abstrata: uma tabela temporária criada em modo de transação pode desaparecer antes que a próxima instrução seja executada, porque a próxima instrução pode cair em um backend completamente diferente.
-- Seguro em pooling de transação: SET LOCAL tem escopo para a transação,
-- então ele sobrevive mesmo que o backend seja trocado depois
BEGIN;
SET LOCAL statement_timeout = '5s';
SELECT count(*) FROM orders WHERE status = 'open';
COMMIT;O dimensionamento do pool segue uma mecânica diferente, mais próxima da teoria de filas do que da contagem bruta de conexões.
Um pool muito pequeno não perde solicitações, ele as enfileira, e esse tempo de fila aparece como latência que o aplicativo geralmente não consegue distinguir da lentidão do banco de dados.
Um pool muito grande apenas recria o problema original, entregando ao PostgreSQL mais backends concorrentes do que seus núcleos de CPU e subsistema de I/O podem atender utilmente de uma vez.
O sinal de dimensionamento que importa é o número de consultas ativas concorrentes em relação aos núcleos disponíveis, não o número de instâncias ou threads do aplicativo tentando se conectar.
Observar cl_waiting no console de administração do PgBouncer, juntamente com a latência real da consulta, diz muito mais sobre o dimensionamento correto do que observar apenas cl_active ou contagens de conexão.
Prepared statements adicionam mais uma complicação mecânica que vale a pena mencionar aqui, já que um prepared statement vive em uma conexão de backend específica, e o pooling de transação pode entregar a próxima solicitação de um cliente a um backend diferente que nunca viu esse PREPARE.
Considerações Avançadas e Aplicações
O isolamento de carga de trabalho é onde o pooling deixa de ser apenas um truque de desempenho e se torna uma decisão arquitetural.
Um único pool compartilhado servindo tanto uma API interativa de checkout quanto um relatório de lote noturno dá a ambas as cargas de trabalho timeouts e prioridade idênticos, o que raramente é o que ambas as cargas de trabalho realmente precisam.
Pools separados por função, cada um com seu próprio pool_size, statement_timeout e pool_mode, permitem que um trabalho analítico lento enfileire atrás de seu próprio limite em vez de sufocar o tráfego OLTP sensível à latência que compartilha o mesmo orçamento de backend.
Arquiteturas sem servidor e de edge function levam isso adiante, já que cada invocação pode abrir e fechar uma conexão em milissegundos, e sem um pooler na frente do PostgreSQL, esse padrão sozinho pode esgotar max_connections sob tráfego moderado.
Poolers gerenciados na nuvem, como RDS Proxy ou Supavisor do Supabase, implementam o mesmo conceito de multiplexação por trás de uma interface gerenciada, então o modelo mental se transfere mesmo quando o próprio PgBouncer não é o software literal na frente do banco de dados.
A alta disponibilidade complica ainda mais o pooling, pois um pooler configurado para um host primário específico precisa de sua própria lógica de reconexão ou redirecionamento baseada em DNS quando o primário muda, independentemente de como a camada de aplicativo detecta o failover.
Escolher entre estratégias de pooling é realmente uma escolha sobre quanta compatibilidade de nível de sessão um fluxo de trabalho está disposto a ceder em troca de densidade de conexão.
| Abordagem | Força | Fraqueza | Melhor Ajuste |
|---|---|---|---|
| Session pooling | Compatibilidade total com recursos SQL (tabelas temporárias, LISTEN/NOTIFY, locks de aviso) | Baixa multiplexação, mais próximo das contagens de conexão brutas | Workers de jobs e ETL que precisam de estado de sessão |
| Transaction pooling | Alta multiplexação, o padrão para a maioria dos OLTP web | Quebra tabelas temporárias e complica prepared statements | APIs stateless de requisição/resposta |
| Statement pooling | A maior multiplexação possível | Quebra completamente transações de múltiplas instruções | Raro; apenas cargas de trabalho de instrução única, não transacionais |
| Sem pooler, conexões diretas | Mais simples de raciocinar, sem salto extra | A contagem de backend escala linearmente com a contagem de cliente | Apenas desenvolvimento local |
Equívocos Comuns
- "Aumentar max_connections me dá mais capacidade." Cada conexão adicional é um processo real do SO com custo real de memória e agendamento, então, além de um certo ponto, mais conexões reduzem a taxa de transferência em vez de aumentá-la.
- "O pooling de conexões serve apenas para economizar o handshake TCP." O custo do handshake é trivial em comparação com o custo de um processo de backend ocioso mantendo memória e um slot em
max_connections, que é o problema real que o pooling resolve. - "O pooling de transação é um substituto direto para o pooling de sessão." Ele quebra silenciosamente tabelas temporárias,
SETde nível de sessão, locks de aviso e reutilização confiável de prepared statements, então mudar de modo sem auditar o SQL do aplicativo é uma fonte comum de incidentes de produção. - "Uma configuração de pool deve funcionar para todas as cargas de trabalho." Tráfego OLTP e batch ou de relatórios têm necessidades conflitantes de latência e concorrência, e forçá-los através do mesmo pool com os mesmos limites atende mal a um ou a ambos.
- "Pooling corrige consultas lentas." Um pooler gerencia a concorrência de conexões, não o tempo de execução da consulta, então uma consulta lenta consome sua duração total em um backend, independentemente de como as conexões foram multiplexadas para alcançá-la.
FAQs
Por que não posso simplesmente aumentar max_connections quando fico sem conexões?
Porque cada conexão é um processo de backend completo com sobrecarga real de memória e agendamento, então aumentar o limite troca um gargalo por outro, muitas vezes piorando a taxa de transferência geral em vez de melhorá-la.
O que um pooler de conexões realmente faz?
Ele se posiciona entre os clientes do aplicativo e o PostgreSQL, aceitando muitas conexões do lado do cliente e multiplexando-as em um conjunto menor e fixo de backends de servidor reais, reutilizando um backend assim que ele fica livre.
Qual é a diferença entre pooling de sessão, transação e instrução?
- O pooling de sessão fixa um backend a um cliente por toda a vida útil da conexão, preservando todo o estado da sessão.
- O pooling de transação retorna o backend ao pool a cada commit ou rollback, quebrando o estado entre transações, como tabelas temporárias.
- O pooling de instrução o retorna após cada instrução, o que quebra completamente transações de múltiplas instruções.
Por que as tabelas temporárias desaparecem quando mudo para o pooling de transação?
Porque a tabela temporária vive em um backend específico, e o pooling de transação pode direcionar a próxima instrução do cliente para um backend diferente que nunca a criou.
Como devo dimensionar um pool?
Dimensiona-o a partir dos núcleos da CPU e da latência de consulta observada, não do número de instâncias do aplicativo, e trate cl_waiting sustentado no pooler como o sinal de que o pool está subdimensionado, em vez de contagens brutas de conexão.
Prepared statements são seguros com um pooler?
Apenas com cuidado - um prepared statement vive no backend que o criou, e o pooling de transação pode rotear a próxima solicitação para um backend que nunca viu esse PREPARE, razão pela qual esta seção tem uma página dedicada às soluções alternativas.
O tráfego OLTP e batch devem compartilhar um pool?
Não - dar a eles pools separados com diferentes pool_size, statement_timeout e, às vezes, diferentes pool_mode impede que um trabalho batch lento prejudique o tráfego interativo sensível à latência.
O pooling ajuda arquiteturas sem servidor ou de edge function?
Sim, muitas vezes criticamente, pois esses ambientes podem abrir uma nova conexão em cada invocação, e sem um pooler na frente do PostgreSQL, esse padrão pode esgotar max_connections sob tráfego moderado.
O PgBouncer é a única opção?
Não - alternativas gerenciadas como RDS Proxy ou Supavisor implementam o mesmo conceito de multiplexação por trás de uma interface gerenciada, então o modelo mental de pooling se transfere mesmo quando o software específico difere.
O que acontece com as conexões agrupadas durante um failover primário?
O pooler precisa de sua própria lógica de reconexão ou redirecionamento apontada para o novo primário, pois um pool configurado para um host específico não segue automaticamente um failover por conta própria.
O pooling torna consultas lentas mais rápidas?
Não - o pooling gerencia quantas conexões chegam ao PostgreSQL por vez, não quanto tempo uma consulta individual leva depois de estar em execução em um backend.
Qual é o maior erro de dimensionamento que as equipes cometem?
Definir max_connections ou tamanho do pool com base no número de instâncias do aplicativo em vez de núcleos de CPU e concorrência medida, o que leva à exaustão de conexões ou capacidade desperdiçada e disputada.
Relacionados
- Noções Básicas de Pooling - consultas de dimensionamento práticas e comandos do console de administração do PgBouncer
- Modos do PgBouncer - pooling de sessão, transação e instrução em detalhes de configuração
- Prepared Statements e Pooling - as correções voltadas para ORM para o modo de transação
- Pool por Função e Timeout - separando tráfego OLTP, batch e admin
- Noções Básicas de Carga de Trabalho - por que o tráfego OLTP e OLAP precisa de limites de recursos diferentes
Versões da Stack: Esta página foi escrita para PostgreSQL 18.4 (estável 18, manutenção 17) e PgBouncer 1.x.