Polar Proxy elimina contenção de locks no caminho crítico de conexões de leitura
- Leitura
- 8 min
- Fontes
- 1
Um fork do ProxySQL adaptado para o PolarDB mostra como tirar o pool de conexões do caminho crítico de cada query, reaproveitando réplicas de leitura com correspondência exata e sem contenção de lock.
Quem opera um proxy na frente de réplicas de leitura do PostgreSQL↳PostgreSQL11 conteúdosPostgreSQL via SSL com GolangData · abr 20195 itens legais sobre data types do PostgreSQLData · mar 20195 serviços gratuitos na cloud para bancos de dados PostgresData · fev 2025Ver tudo em Data → conhece o problema: abrir uma conexão nova custa caro (handshake TCP, TLS, autenticação, troca de parâmetros de startup), então todo proxy sério mantém um pool de conexões já abertas para reaproveitar. O difícil não é guardar conexões ociosas, é decidir quando duas conexões são realmente intercambiáveis. Um post publicado em 8 de novembro de 2026 no blog da Awide Labs, assinado por Vadim Iatsenko e replicado pelo Planet PostgreSQL, descreve como o Polar Proxy (uma base derivada do ProxySQL, adaptada para o PolarDB) reorganizou esse pool para tirar a sobrecarga de cada query do caminho crítico.
O problema da correspondência exata
Duas conexões só são seguras para reaproveitar se foram abertas com o mesmo usuário, o mesmo banco, as mesmas opções de sessão e, no caso do PolarDB, o mesmo perfil de startup, que decide se a conexão negocia relato de LSN e esperas in-band. Uma conexão que negocia essas extensões também carrega uma identidade de startup do cliente (hoje o endpoint, com os campos de TLS reservados na mesma chave), que entra na comparação.
Isso importa porque qualquer leitor que já operou roteamento read-after-write sobre réplicas de streaming replication sabe que o diabo mora nesse detalhe: reaproveitar uma conexão que não rastreia LSN para atender um cliente que pediu consistência causal quebra a garantia sem dar nenhum erro visível. O Polar Proxy resolve isso com uma chave de correspondência de quatro partes: geração do perfil de startup, hash de usuário/banco, identidade de startup do cliente e opções de startup. Qualquer divergência força uma conexão nova.
Onde o ProxySQL genérico perde tempo
A fonte é direta ao apontar os gargalos do pool genérico do ProxySQL upstream, que não foi desenhado para roteamento sensível a LSN e metadados de transação. Quatro pontos se destacam:
- Correspondência rasa: o pool genérico não enxerga as dimensões de estado do PolarDB, então uma conexão sem o estado exigido fica indistinguível de uma que o tem.
- Trabalho repetido por query: a cada leitura, o caminho upstream varre listas de conexão, reconstrói um array temporário de candidatos e segura de novo o lock do hostgroup-manager, mesmo que esse trabalho já tenha sido feito instantes antes.
- Contenção em pontos sem relação com roteamento: estatísticas de query digest são atualizadas em um mapa compartilhado atrás de um único lock por processo, a cada query, e o flush do logger é verificado em toda passada do event loop mesmo com o log desligado.
- Cópia por pacote: resultados grandes são encaminhados ao cliente copiando cada pacote por um buffer intermediário.
Um único dono para as conexões de leitura
A mudança central é organizacional: o pool central de conexões passa a ser o único dono de cada conexão de leitura reutilizável. O pool de leitura não guarda mais conexões próprias; ele só decide qual servidor usar e qual política aplicar (alvo de LSN, teto de lag, warmup). Mesmo uma conexão que um worker está segurando brevemente para reuso local continua contabilizada na lista USED do pool central, o cache do worker é um handle de reuso, não um segundo dono.
Cada lista de conexões por servidor carrega a chave de quatro partes mais um índice de correspondência exata, e conexões se movem entre FREE e USED sob um único mutex por servidor, com lista, índice e contadores atualizados juntos. Isso elimina a ambiguidade que o pool genérico tinha: ou a conexão bate exatamente com o que a leitura exige, ou ela não é reaproveitada.
Reuso local e seleção por power-of-two-choices
Dois micro-ajustes cortam o custo por query de encontrar uma conexão. O primeiro é o reuso local do worker: depois de escolher um servidor, o worker guarda a conexão usada num cache thread-local, por padrão só até o fim daquela passada do event loop. A próxima leitura no mesmo servidor reaproveita sem tocar o pool compartilhado.
O segundo é a seleção por power-of-two-choices (P2C). Quando há mais de dois leitores competindo, o proxy compara apenas dois candidatos e só os contadores de carga desses dois são lidos.
Power-of-two-choices escolhe um bom servidor comparando a carga de dois candidatos; o reaproveitamento local no worker atende as leituras repetidas sem sequer tocar no pool compartilhado.
No original: Power-of-two-choices picks a good server by comparing two candidates' load; worker-local reuse then serves repeat reads without touching the shared pool.
Com exatamente dois leitores configurados, não há nada para amostrar: uma sequência ponderada local ao worker alterna entre eles, e uma leitura com alvo de LSN desloca a escolha para quem já replicou até lá. Com três ou mais réplicas de peso igual e nenhuma restrição (sem teto de lag, sem endpoint excluído), o proxy amostra um par aleatório e só para nele, escolhendo o menos carregado, quando ambos são utilizáveis e, no caso de uma leitura com alvo de LSN, ambos já atingiram esse alvo; caso contrário, expande para a lista inteira, reaproveitando o par já avaliado em vez de reavaliá-lo.
O power-of-two-choices escolhe um bom servidor comparando a carga de dois candidatos; o reaproveitamento local no worker atende as leituras repetidas sem sequer tocar no pool compartilhado.
Vadim Iatsenko, Awide LabsPower-of-two-choices picks a good server by comparing two candidates' load; worker-local reuse then serves repeat reads without touching the shared pool.
Esperar em vez de girar
Quando toda conexão compatível está em uso e não há slot livre para criar outra, o ProxySQL upstream gira e revarre as listas. O Polar Proxy, em vez disso, registra a requisição pendente, com sua chave de correspondência, o plano de leitura e o alvo de espera, e a estaciona: o event loop segue atendendo outras sessões enquanto a requisição espera por um despertar direcionado ou um prazo de retry limitado.
Quando uma conexão é devolvida ou criada, ela vai direto para a requisição pendente, e só o worker responsável é acordado, por um byte escrito no pipe de wakeup daquele event loop. O warmup de fundo (pré-abertura de conexões para que leituras divididas em transação não travem num leitor frio) roda numa thread dedicada, fora do lock do hostgroup-manager.
Tirando os locks do caminho quente
Três mudanças separadas atacam contenção que, à primeira vista, nem parece ligada ao pool de conexões, mas competia pelo mesmo tempo de CPU:
- Digests locais ao worker: cada worker acumula estatísticas de query digest no seu próprio mapa pequeno e só sincroniza com o mapa compartilhado quando o mapa local atinge o limite de entradas, na housekeeping periódica ou quando alguém lê os contadores.
- Logger pulado quando desligado: o worker lê primeiro as duas flags de "logfile aberto"; se ambas estão fechadas, retorna sem ler relógio, sem lookup de contexto compartilhado e sem travar buffer.
- Contadores em lote: cada conexão de backend↳Back-end49 conteúdosIntegração front-end com backend: 7 decisões que evitam caos entre APIs, BFF e GraphQLDev (Back & Front) · abr 2026Como criar uma FAKE API REST para testes — JSONPlaceholderDev (Back & Front) · set 2025Construindo um aplicativo de bate-papo de IA simples com Spring AI e AngularDev (Back & Front) · jul 2025Ver tudo em Dev (Back & Front) → acumula bytes e contagem de queries e só repassa ao contador compartilhado do servidor em lotes, a cada 64 queries ou quando o volume pendente de bytes passa de 64 KiB em qualquer direção.
Encaminhamento sem cópia
O caminho de resposta ganhou duas otimizações para parar de copiar bytes desnecessariamente. Uma libpq modificada consegue identificar e destacar sequências contíguas de pacotes de linha de um resultado, com as rotinas PSpeekRowRun e PSdetachRowRun. Esse caminho rápido, chamado pgsql-polardb_result_fast_forward, transfere a posse do buffer inteiro em vez de copiar pacote por pacote, mas é opt-in e vem desligado por padrão.
A segunda otimização é ativada por padrão: quando o lote na fila de saída acumula ao menos dois pacotes (ou um pacote grande demais para o buffer clássico) e o frontend é um socket puro sem TLS e sem pacote parcial em trânsito, o proxy monta um iovec direto a partir dos pacotes de resultado enfileirados e os envia com uma única chamada sendmsg do tipo scatter-gather, no limite de 64 pacotes por chamada dentro de um orçamento de bytes.
O que muda (e o que não muda) para quem opera réplicas
A fonte é enfática num ponto que vale repetir para quem avalia essa engenharia: nenhuma garantia de consistência muda. É uma camada pura de performance sobre o mesmo roteamento baseado em LSN e as mesmas regras de fallback seguro que já existiam.
Para quem administra bancos, o recorte prático é outro: o Polar Proxy é específico para PolarDB, a variante distribuída de Postgres, e depende do conceito de alvo de LSN que essa plataforma expõe nativamente. Quem roteia leituras hoje com ProxySQL genérico, PgBouncer ou PgCat não ganha esse recurso de graça só por ler o artigo.
O que é exportável, independente da stack, são os padrões: chave de correspondência exata em vez de pool genérico quando a aplicação depende de consistência de réplica; cache local ao worker para evitar tocar estruturas compartilhadas em todo request; P2C em vez de varrer todos os servidores; parking de requisições em vez de spin quando falta capacidade; e separar contabilidade (digests, logs, contadores) do caminho de execução da query, sincronizando em lote.
São decisões de desenho que qualquer equipe mantendo um proxy próprio, ou avaliando por que um pool de conexões está competindo com o próprio trabalho de responder queries, pode cobrar de qualquer ferramenta candidata antes de trocar de proxy em produção.
Fonte 1: Planet PostgreSQL (https://postgr.es/p/9Bi)
"The Server-First Reader Pool: Exact Connection Reuse and Hot-Path Performance", Awide Labs Blog.
Fonte: Planet PostgreSQL
Highlights
- Polar Proxy elimina contenção de locks no pool de conexões ao separar contabilidade do caminho de execução
- Correspondência exata em quatro partes garante reuso seguro sem quebrar consistência causal em leituras
- Power-of-two-choices e cache local ao worker reduzem custo por query para encontrar conexão compatível
- Padrões como parking de requisições, digests locais e sendmsg scatter-gather cortam overhead de spin e cópia
- Recorte prático: Polar Proxy é específico para PolarDB; padrões de design são exportáveis para qualquer stack
Este artigo foi escrito por Roberto Diniz, colunista de banco de dados. Conteúdo produzido por agente de IA da redação iMasters, sob revisão editorial humana. Saiba como produzimos no expediente.
PostgreSQL e NUMA na nuvem: por que huge pages e CAP_SYS_NICE decidem a performance
Série de quatro artigos do consultor Chris Travers, da Stormatics, mostra como a arquitetura NUMA continua afetando o PostgreSQL mesmo dentro de VMs e containers, e por que ignorar isso custa latência em produção.










