Pular para o conteúdo

Commit em duas fases (2PC) explicado: prepare, commit e por que ele bloqueia

Como o commit em duas fases torna atômica uma transação entre bancos de dados, o que acontece se o coordenador cai e quando sagas ou um outbox se saem melhor.

· Fundador e desenvolvedor

Publicado em

Diagrama de sequência com uma aplicação, um coordenador, um banco de pedidos e um banco de pagamentos. Um fragmento par mostra PREPARE enviado aos dois bancos e os votos YES. Um fragmento alt mostra COMMIT e as confirmações se todos votam sim, e ROLLBACK se algum vota não.

Um pedido é gravado em um banco de dados e o pagamento em outro. As duas coisas precisam acontecer, ou nenhuma. Cada banco consegue confirmar a sua parte de forma atômica, mas nada local impede que um confirme enquanto o outro cai. O commit em duas fases (2PC) é o protocolo clássico para esse problema: vem embutido na maioria dos bancos relacionais por meio do padrão XA e é usado hoje dentro de bancos SQL distribuídos.

Ele também tem uma fraqueza famosa: se o coordenador falhar na hora errada, os participantes podem ficar presos segurando locks, sem como decidir sozinhos. Este post percorre o protocolo com um diagrama de sequência, todos os casos de falha, como os sistemas reais o implementam e quando uma saga ou um outbox é a ferramenta melhor.

Os papéis

  • Coordenador, ou gerenciador de transações: conduz o protocolo e decide o resultado. Pode ser uma biblioteca dentro do seu servidor de aplicação, um serviço separado ou um componente de um banco de dados distribuído.
  • Participantes, ou gerenciadores de recursos: os bancos de dados ou filas que guardam, cada um, uma parte da transação.

Cada parte mantém um log durável. As gravações no log não são um detalhe de implementação; são elas que fazem o protocolo sobreviver a quedas.

O protocolo

Fase 1: prepare.

  1. A aplicação faz o seu trabalho em cada participante dentro de uma transação global e depois pede ao coordenador que confirme.
  2. O coordenador envia PREPARE a cada participante.
  3. Cada participante garante que vai conseguir confirmar aconteça o que acontecer depois: grava as alterações da transação e um registro de preparação no log durável, mantém os locks e vota YES. Se não puder confirmar, por exemplo porque uma restrição falhou, vota NO e reverte.

Fase 2: commit ou abort.

  1. Se todos os votos forem YES, o coordenador grava uma decisão COMMIT no próprio log. Essa gravação é o ponto de commit: daí em diante a transação está confirmada, mesmo que todas as máquinas caiam um milissegundo depois.
  2. O coordenador envia COMMIT a cada participante. Cada um confirma, libera os locks e responde com uma confirmação de recebimento.
  3. Se algum participante votar NO ou não responder a tempo, o coordenador decide ABORT e envia ROLLBACK no lugar.

Um voto YES é uma promessa. Depois de votar YES, o participante abriu mão do direito de decidir sozinho: precisa confirmar se receber a ordem, mesmo depois de reiniciar, e não pode reverter por conta própria, porque o coordenador talvez já tenha decidido confirmar.

O custo

Com N participantes, um commit bem-sucedido exige duas idas e voltas a partir do coordenador e quatro mensagens por participante: PREPARE, voto, COMMIT e confirmação. Cada participante força a gravação do log em disco duas vezes, no prepare e no commit, e o coordenador força a sua decisão uma vez. Os locks ficam presos o tempo todo, então o participante mais lento decide por quanto tempo todos os outros bloqueiam trabalho conflitante.

O que acontece quando algo falha

Falha Quando Resultado
Participante cai ou estoura o timeout Antes de votar O coordenador aborta a transação
Participante vota NO Fase 1 O coordenador aborta a transação
Participante cai Depois de votar YES Ao reiniciar, encontra o registro de preparação e pergunta o resultado ao coordenador, segurando os locks enquanto isso
Coordenador cai Antes de gravar a decisão Os participantes preparados esperam. Na recuperação, sem decisão no log, o coordenador aborta
Coordenador cai Depois de gravar COMMIT Na recuperação, lê o log e reenvia COMMIT
Partição de rede Depois que um participante votou YES Esse participante fica bloqueado até conseguir falar com o coordenador

As linhas que importam são aquelas em que um participante votou YES e não consegue ouvir a decisão. Ele não pode confirmar, porque outro participante pode ter votado NO. Não pode abortar, porque o coordenador pode ter confirmado. Ele espera, segurando os locks, enquanto o coordenador estiver inacessível. Todas as outras transações que mexem nessas linhas também esperam. É isso que as pessoas querem dizer quando chamam o 2PC de protocolo bloqueante.

Transações preparadas presas na prática

O PostgreSQL implementa o lado do participante com PREPARE TRANSACTION, COMMIT PREPARED e ROLLBACK PREPARED. O recurso vem desativado por padrão (max_prepared_transactions é 0), e a documentação avisa que ele foi feito para gerenciadores de transações externos, não para aplicações. Uma transação preparada esquecida mantém os locks e impede o VACUUM de recuperar espaço.

Encontrar e resolver uma fica assim:

-- Prepared transactions waiting for a decision
SELECT gid, prepared, owner, database
FROM pg_prepared_xacts
ORDER BY prepared;

-- Only after checking the coordinator's log for the real outcome:
COMMIT PREPARED 'order-7f3a';
-- or
ROLLBACK PREPARED 'order-7f3a';

Nunca resolva uma transação preparada no chute. Se o coordenador confirmou e você reverte um participante, criou exatamente a inconsistência que o 2PC existe para evitar. O MySQL oferece o mesmo mecanismo com XA PREPARE, XA COMMIT e XA RECOVER.

Variações que aliviam a dor

  • Presumed abort. Se o coordenador não tem registro de uma transação, a resposta é abortar. Isso economiza gravações de log nas transações abortadas e simplifica a recuperação; a maioria das implementações usa isso.
  • Participantes somente leitura. Um participante que não alterou nada pode votar “read-only” e pular a fase 2.
  • Commit em três fases. Acrescenta uma rodada de pre-commit para que os participantes consigam terminar sem o coordenador, mas só se os atrasos das mensagens forem limitados e a rede nunca se particionar. Essas premissas raramente valem, então o 3PC é sobretudo assunto de livro-texto.
  • Replique o coordenador e os participantes. A solução real para o bloqueio é transformar cada papel em um grupo replicado que sobreviva à perda de uma máquina. O Spanner, do Google, roda o commit em duas fases sobre grupos Paxos exatamente por esse motivo, e outros bancos SQL distribuídos usam a mesma ideia com Raft. O commit em duas fases continua decidindo o resultado; o consenso mantém cada parte disponível.

Quando usar 2PC, e quando não usar

O 2PC serve quando todos os participantes são bancos de dados ou brokers que suportam uma etapa de preparação, você controla todos eles, as transações são curtas e você precisa de atomicidade e isolamento entre eles. Funciona mal entre serviços de outros times, entre APIs HTTP sem etapa de preparação (um provedor de pagamentos não vai manter a sua cobrança em estado preparado) e em processos de negócio que duram minutos ou dias.

Commit em duas fases Saga Transactional outbox
Consistência Atômica; os locks garantem isolamento Eventual, com etapas de compensação Eventual entre um banco de dados e um broker
Bloqueio Sim, enquanto uma decisão está pendente Não Não
Exige Participantes que suportem prepare Lógica de compensação para cada etapa Um banco de dados mais um relay
Uso típico Bancos ou shards que você opera Fluxos de negócio entre serviços Publicar eventos de forma confiável após uma gravação

O diagrama do padrão saga mostra a alternativa com compensações, e o padrão transactional outbox cobre o caso mais comum em que os times recorrem ao 2PC: gravar em um banco de dados e publicar uma mensagem juntos. Muitas vezes a melhor solução é estrutural: coloque os dados que precisam mudar juntos em um único banco, ou em um único shard, como discute o diagrama de sharding de banco de dados.

O diagrama do commit em duas fases

Diagrama de sequência com uma aplicação, um coordenador, um banco de pedidos e um banco de pagamentos. Um fragmento par mostra PREPARE enviado aos dois bancos e os votos YES. Um fragmento alt mostra COMMIT e as confirmações se todos votam sim, e ROLLBACK se algum vota não.

10 componentes · 12 conexões

Abrir no Archboard
Conteúdo do diagrama
  • Application
  • Coordinator
  • Orders DB
  • Payments DB
  • phase 1: prepare
  • phase 2: all votes YES
  • commit()
  • PREPARE
  • YES (prepare record on disk)
  • COMMIT
  • ACK, locks released
  • ACK
  • ROLLBACK
  • committed or aborted
Commit em duas fases entre dois bancos de dados. Na fase 1, o coordenador pede aos dois participantes que se preparem e coleta os votos; na fase 2, registra a decisão e manda todos confirmarem, ou reverterem se algum voto foi não.

Como ler:

  • Quatro linhas de vida. A aplicação, o coordenador, o banco de pedidos e o banco de pagamentos. O tempo corre para baixo.
  • O fragmento par é a fase 1. PREPARE vai ao mesmo tempo para os dois participantes, e cada um devolve um voto depois de gravar o registro de preparação.
  • O fragmento alt é a fase 2. O ramo de cima é o caso normal: todos os votos são YES, o coordenador registra COMMIT e os dois participantes confirmam e respondem. O ramo de baixo é o caminho do abort.
  • A nota marca a janela de dúvida entre um voto YES e a decisão, quando os participantes seguram locks e não podem decidir sozinhos.
  • O bloco Markdown lista os casos de falha da tabela acima.

Desenhar transações distribuídas com honestidade

  • Mostre as gravações no log. Um diagrama de 2PC sem “gravar o registro de preparação” e “gravar a decisão” deixa de fora a parte que o torna correto.
  • Marque o ponto de commit. Uma única mensagem ou gravação de log decide o resultado; aponte para ela.
  • Desenhe a janela em que tudo bloqueia. Ela é o motivo de os times evitarem o 2PC, então um diagrama que a esconde engana.
  • Coloque os caminhos de falha em diagramas próprios. Uma queda do coordenador depois do prepare merece a sua própria sequência com as etapas de recuperação.

Para a notação em si, o exemplo de diagrama de sequência cobre linhas de vida, fragmentos e nomenclatura.

Monte no Archboard

  1. Entre com Google ou GitHub e crie um diagrama.
  2. Adicione quatro nós Linha de vida do pack Sequência: Application, Coordinator, Orders DB e Payments DB.
  3. Desenhe as mensagens como arestas, de cima para baixo: o pedido de commit, PREPARE para cada banco, os votos YES, COMMIT para cada banco e as confirmações.
  4. Adicione um Fragmento combinado do pack Sequência com o operador par em volta das mensagens de preparação e um segundo Fragmento combinado com o operador alt em volta dos ramos de commit e rollback.
  5. Adicione uma Nota para a janela de dúvida e um bloco Markdown do pack Básico com a tabela de falhas.

O exemplo usa 7 nós de diagrama e fica dentro do plano Free.

Perguntas frequentes

Por que o commit em duas fases é chamado de protocolo bloqueante? Um participante que votou YES não pode confirmar nem abortar sozinho. Se o coordenador ficar inacessível antes de enviar a decisão, o participante espera segurando os locks, assim como toda transação que precisa dessas linhas.

Commit em duas fases é o mesmo que transação distribuída? É a forma mais comum de confirmar uma. Uma transação distribuída abrange vários recursos; o commit em duas fases é o protocolo que torna a sua confirmação atômica.

O commit em três fases resolve o problema do bloqueio? Só sob premissas que raramente valem: atrasos de mensagem limitados e nenhuma partição de rede. A solução prática é replicar o coordenador e os participantes com consenso, como faz o Spanner.

Posso usar commit em duas fases entre microsserviços? Raramente dá certo. Os serviços precisariam expor uma etapa de preparação e segurar locks uns para os outros, o que acopla a disponibilidade deles. Entre serviços, as escolhas usuais são sagas e o padrão outbox.

O commit em duas fases garante isolamento? Garante atomicidade. O isolamento vem dos locks dos participantes, que o 2PC mantém até a decisão chegar, então transações concorrentes não veem resultados parciais.

Comece grátis — um assento, sem cartão de crédito.

Artigos relacionados

← Voltar ao blog

Abra um canvas em um minuto

Entre com Google ou GitHub. Grátis para um assento. Faça upgrade quando a equipe entrar.