Polar Proxy divide transação aberta no PostgreSQL entre primária e réplica
- Leitura
- 8 min
- Tabelas
- 1
- Código
- 1
- Fontes
- 1
Um artigo técnico do blog da Awide Labs detalha como o Polar Proxy manda leituras de uma transação PostgreSQL aberta pra réplica sem perder snapshot, locks ou a consistência de read committed.
O problema: transação não se divide fácil
Mandar um SELECT solto, em autocommit, pra uma réplica é trivial: qualquer proxy decente já faz isso. O problema aparece quando o cliente abre uma transação (BEGIN, alguns comandos, COMMIT), porque a sequência inteira precisa se comportar como uma unidade só. A saída mais comum, descrita por Vadim Iatsenko num artigo publicado em 1º de novembro de 2026 no blog da Awide Labs, é a mais conservadora possível: toda transação, do primeiro ao último comando, vai pra primária, mesmo os SELECTs que nunca escrevem nada.
Essa conservação tem motivo técnico, não preguiça. Uma transação no 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 → fixa um snapshot (a fotografia do banco que ela enxerga), os locks que já tomou e as configurações de sessão num único 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) →. Se o proxy mandasse o segundo comando pra outro servidor, esse servidor não teria as mudanças não commitadas, os locks nem o snapshot do primeiro comando, e a transação simplesmente quebraria.
Por que o nível de isolamento decide tudo
A chave pra entender onde dá pra abrir uma brecha está no nível de isolamento. Em repeatable read e serializable, a transação tira um snapshot no primeiro comando e mantém esse mesmo snapshot até o fim: uma leitura no meio da transação precisa enxergar exatamente o que a primeira leitura enxergou, o que torna o offload pra réplica praticamente impossível.
Em read committed, porém, cada comando tira um snapshot novo. Um SELECT só precisa ver tudo que foi commitado antes dele, e é exatamente isso que dá pra garantir esperando a réplica alcançar um determinado LSN (log sequence number, a posição no fluxo de WAL). É essa janela que o Polar Proxy explora.
O que uma conexão pooled carrega consigo
Duas características de uma conexão em pool condicionam toda a solução. A primeira é identidade: um pool de conexões PostgreSQL só reutiliza uma conexão quando o usuário e o banco de dados batem com os do cliente, porque é essa identidade que decide quais tabelas a sessão pode ler. Dar pro cliente uma conexão autenticada como outro usuário faz a leitura rodar silenciosamente sob permissões erradas.
A segunda é o ciclo de vida. Pra um comando autocommit a resposta é óbvia: pega a conexão, roda, devolve. Mas uma leitura dividida roda dentro de uma transação aberta, e não há precedente upstream pra dizer quanto tempo a réplica emprestada deve ficar reservada, o time do PolarDB teve que decidir isso do zero.
Como o resto do ecossistema lida (ou não) com isso
Antes de entrar no mecanismo do Polar Proxy, vale medir a régua contra quem já existe.
| Ferramenta | Divide a transação? | Como garante consistência |
|---|---|---|
| Pgpool-II | Não: a documentação é explícita que load balancing não é garantido dentro de transação | Não se aplica |
| PgBouncer | Não roteia: encaminha comandos sem analisá-los | Não se aplica |
| Vitess (MySQL) | Só em autocommit ou transação explicitamente read-only | Rastreamento de GTID |
| Amazon Aurora PostgreSQL | Sim, mas dentro do motor | aurora_replica_read_consistency = 'session', com acesso ao LSN de commit no kernel |
| ProxySQL upstream (PostgreSQL) | Não: roteia a transação inteira pra primária | Não se aplica |
| Polar Proxy | Sim, na camada de proxy | Import de XID mais espera por LSN |
O caso da Aurora é o mais revelador: ela resolve o mesmo problema, mas só porque é dona do motor de armazenamento e consegue levar o LSN de commit por dentro de cada backend. Um proxy não tem esse acesso, e é exatamente essa lacuna que o Polar Proxy tenta preencher de fora.
Enxergar o estado da transação sem mais uma ida e volta
O primeiro obstáculo é que o protocolo padrão não conta quase nada sobre a transação em curso. O ReadyForQuery (RFQ) do PostgreSQL carrega só um byte de estado: ocioso, em transação, ou em transação falha. Nada sobre XIDs nem sobre se ela já escreveu algo.
O padrão é a segurança; o offload é a exceção.
No original: The default is safety; the offload is the exception.
O time da Awide estendeu o libpq embutido no Polar Proxy pra pedir esse detalhe no startup, via o parâmetro _polar_send_xact. O backend passa a anexar um byte extra a cada RFQ: x significa transação divisível (sem WAL próprio pendente de flush), w significa que ela já escreveu e esse WAL ainda não foi persistido. Uma string de XIDs acompanha o marcador quando a transação é divisível; vazia significa que ela ainda está no prefixo somente leitura.
Três novas chamadas, PQgetXactSplitXids, PQisXactSplittable e PQisXactWalPending, expõem esses fatos ao proxy sem round trip extra.
As cinco condições pra liberar uma leitura
Uma leitura só vai pra réplica se passar em todos estes testes:
- A transação roda sob
read committed(nuncarepeatable readouserializable). - Ela é divisível: sem WAL próprio pendente de flush, ou, como exceção aceita, o monitor já viu uma réplica do grupo replicando além do LSN reportado.
- O comando é um
SELECTde topo, sem cláusula de lock (FOR UPDATE,FOR SHARE,FOR NO KEY UPDATE,FOR KEY SHARE). - É um único comando, sem multi-statement e sem protocolo estendido.
- Se a leitura vem antes da primeira escrita, a sessão não rodou nenhum
SETdentro da transação ainda.
Qualquer falha numa dessas checagens mantém a leitura na primária.
O padrão é a segurança; o offload é a exceção.
Vadim Iatsenko, autor do artigo no blog da Awide LabsThe default is safety; the offload is the exception.
O mecanismo: quatro SET antes do SELECT
Quando uma leitura passa em todos os testes, o proxy escolhe uma conexão de réplica compatível (mesmo usuário, mesmo banco, mesma configuração de sessão) sem fechar a conexão com a primária. Ele envia, no mesmo round trip, uma sequência fixa de comandos ocultos seguidos do SELECT original:
SET polar_xact_split_xids = '1,1234,5678'; -- command id, depois o XID da transação e dos filhos
SET polar_consistency_mode = 'strict'; -- forçado internamente pelo wrapper
SET polar_proxy_wait_timeout_ms = 5000; -- prazo da espera, em milissegundos
SET polar_xact_split_wait_lsn = '97500059720'; -- espera a réplica replicar até aqui
-- aqui entra o SELECT originalDuas rotas diferentes usam esse mecanismo. Antes da primeira escrita, a leitura usa o caminho REPLICA_WITH_WAIT: não há XID pra importar ainda, só a espera pelo LSN. Depois da primeira escrita, o caminho vira REPLICA_TXN_SPLIT, que também importa os XIDs da transação pra que a réplica enxergue as próprias escritas que ainda não viu replicadas.
Vale notar que o modo strict e o timeout não vêm da sessão do cliente: o wrapper os força por conta própria, justamente pra uma leitura dividida nunca servir dado desatualizado sem avisar.
Prender a réplica até o fim da transação
A primeira leitura elegível de uma transação escolhe uma réplica, e a sessão a mantém reservada até o fim daquela transação; cada leitura seguinte reutiliza a mesma conexão em vez de escolher outra. A conexão com a primária permanece intocada o tempo todo, ainda segurando o snapshot, os locks e as configurações.
Essa é uma decisão de projeto do time do PolarDB, sem precedente upstream, porque o ProxySQL original nunca dividiu uma transação pra precisar responder isso. Manter a mesma réplica evita uma nova seleção a cada comando, e permite que uma leitura posterior pule a espera por LSN se a réplica já alcançou o ponto necessário. A única exceção é se o alvo de roteamento muda ou a réplica escolhida cai: aí a sessão libera e escolhe outra.
Quando a réplica falha no meio da leitura
Uma leitura dividida pode falhar de três formas: a conexão com a réplica cai, o tempo de espera estoura, ou a réplica devolve um erro de SQL reexecutável. O proxy classifica a falha e aplica uma política configurável, com padrões sensatos: perda de conexão tenta outra réplica compatível; timeout e erro reexecutável caem de volta pra primária, cujo resultado é considerado autoritativo.
Há um limite claro: assim que alguma linha de resultado já foi enviada ao cliente, o proxy para de tentar de novo e preserva o caminho de falha original. O cliente nunca vê um resultado parcial seguido de uma segunda tentativa.
O que isso muda pra quem administra o banco
O ganho concreto é o offload de leitura onde antes ele era simplesmente impossível: uma transação longa, predominantemente de leitura (o perfil típico de relatório ou job de batch), pode ter seus SELECTs atendidos por réplicas enquanto a transação continua coerente na primária. É uma solução de proxy, não de motor: diferente da Aurora, o Polar Proxy não tem acesso ao kernel do PostgreSQL, e compensa isso com importação de XID e espera por LSN.
A régua de elegibilidade é estreita de propósito. Transações sob repeatable read ou serializable, leituras com cláusula de lock, múltiplos comandos no mesmo round trip e qualquer SET de sessão antes da primeira escrita ficam de fora, e é bom que fiquem, porque nenhuma dessas situações permite reconstruir a visibilidade da transação numa réplica sem risco de dado inconsistente. Quem administra uma aplicação com muitas transações mistas, de leitura e escrita intercaladas, com isolamento mais forte ou locks explícitos, não deve esperar alívio de carga: a maior parte desse tráfego continua na primária.
Vale a ressalva de quem lê plano de execução antes de confiar em qualquer promessa de escala: o artigo da Awide descreve o desenho e as garantias, mas não publica números de latência ou throughput, isso fica para um próximo texto da série, prometido pelo próprio autor. Antes de adotar esse padrão em produção, o caminho responsável é medir o custo da espera por LSN sob a carga real do seu banco, não assumir que ele é desprezível só porque o desenho é elegante.
Fonte: Planet PostgreSQL
Highlights
- Polar Proxy divide SELECTs de transações abertas para réplicas sem perder consistência em read committed
- Proxy estende libpq com _polar_send_xact para saber se a transação é divisível antes de rotear
- Leituras só são offloadadas se respeitarem cinco condições: isolation level, WAL pending, sem locks, single statement, sem SET prévio
- Réplica é mantida reservada até o fim da transação e pula espera por LSN em comandos subsequentes
- Aurora faz algo parecido mas dentro do motor; Polar Proxy replica comportamento a partir da camada de proxy
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.
O restore_command do PostgreSQL confunde falha de rede com fim do archive de WAL
Quando o comando de restauração falha, o PostgreSQL assume por definição que o archive de WAL acabou ali, mesmo que a causa real tenha sido instabilidade de rede. Christophe Pettus mostra como essa confusão de código de saída pode encerrar uma recuperação de forma silenciosa, sem nenhum erro no log.












