O 'sim' no altar
O celebrante não declara o casamento e depois pergunta se cada um aceita. Ele primeiro pergunta a cada um — 'você aceita?' — e só declara quando os dois já disseram sim. Se um hesita, ninguém está casado. O prepare é essa pergunta: cada participante promete o sim antes de qualquer coisa ser oficial. E, como no altar, se o celebrante desmaiar entre o 'sim' de um e a declaração final, os dois ficam num limbo constrangedor esperando alguém dizer o que vale — o famoso estado in-doubt.
A janela travada e o coordenador que cai
Entre o prepare e o commit, cada participante que votou sim mantém os recursos travados: ele prometeu commitar e precisa poder cumprir. Essa janela dura o tempo da rodada inteira — o participante mais lento define o ritmo, e ninguém mais toca aquelas linhas até a decisão.
O ponto fatal é o coordenador cair depois de coletar os votos e antes de mandar a decisão. Os participantes ficaram in-doubt: prometeram, travaram, e agora esperam uma ordem que não vem. Eles não podem abortar (talvez o coordenador tenha decidido commit) nem commitar (talvez tenha sido abort). Ficam travados.
É por isso que se diz que o 2PC é um protocolo bloqueante: existe um estado do qual os participantes não conseguem sair sozinhos. Variações como o 3PC tentam reduzir esse bloqueio adicionando uma fase, mas trocam por outras suposições (sobre tempo e falhas) que raramente se sustentam na prática — e por isso quase ninguém usa.
Ligando ao CAP: o 2PC escolhe consistência em detrimento de disponibilidade. Basta um participante indisponível para a transação inteira não poder concluir — a disponibilidade do conjunto é o produto das disponibilidades individuais, então ela só cai à medida que você adiciona participantes. Num sistema de três serviços com 99,9% cada, o 2PC entre eles já vale menos que qualquer um sozinho.
Daí a preferência moderna pela saga: em vez de travar tudo e commitar junto, cada passo commita localmente na hora e, se um passo posterior falhar, os anteriores são desfeitos por ações de compensação (estornar a cobrança, liberar a reserva). A saga abre mão da atomicidade instantânea — existe um intervalo em que o sistema está parcialmente aplicado — em troca de não travar recurso nenhum e de sobreviver a participantes que falham. É o trade-off central de transações distribuídas: atomicidade travada (2PC) × consistência eventual por compensação (saga).
- Coordenador
- pergunta a cada participanteFase 1: prepare
- cada um trava e prometeTodos votam sim
- coordenador decideFase 2: commit
- todos aplicam e liberamAplicado em todos
Como isso aparece na prática
Banco e fila num commit só
Cenário
Um serviço precisa gravar um pedido no banco e publicar um evento numa fila, de forma que nunca aconteça um sem o outro. Uma falha entre as duas ações deixaria o sistema inconsistente.
Como o padrão entra
Como banco e broker suportam XA, um coordenador conduziu o 2PC entre os dois recursos, dentro do mesmo processo e da mesma rede de baixa latência.
Trade-off: Funciona porque são dois participantes confiáveis e próximos. Ainda assim, muitos times preferem o padrão Outbox aqui — grava tudo no banco numa transação local e publica depois — justamente para não depender de XA.
O 2PC entre microsserviços que travou o checkout
Cenário
Um time tentou garantir atomicidade entre os serviços de estoque, pagamento e nota fiscal com 2PC. Numa instabilidade de rede, o serviço de nota ficou lento e travou reservas de estoque em toda a plataforma.
Como o padrão entra
O 2PC foi substituído por uma saga: cada serviço commita seu passo localmente, e uma falha adiante dispara compensações (liberar a reserva, estornar a cobrança) em vez de segurar locks.
Trade-off: Passou a existir uma janela em que o pedido está parcialmente processado antes de compensar ou concluir. Em troca, um serviço lento não trava mais os outros, e a disponibilidade deixou de ser refém do participante mais fraco.
2PC entre microsserviços
Compila, roda, passa no code review. E quebra depois.
Alguém quer que três serviços 'aconteçam juntos' e alcança o commit distribuído para conseguir atomicidade. Parece a escolha correta e rigorosa — até a primeira instabilidade de rede, quando os locks distribuídos transformam um serviço lento numa parada geral e um coordenador caído deixa recursos travados sem ninguém para destravá-los.
// "So preciso que estas tres coisas aconteçam juntas."
async function confirmarPedido(pedido) {
const tx = await coordenador.iniciar(); // 2PC entre 3 servicos
await estoque.reservar(tx, pedido.itens); // participante 1: TRAVA linhas
await pagamentos.cobrar(tx, pedido.total); // participante 2: TRAVA conta
await notas.emitir(tx, pedido); // participante 3: TRAVA sequencia
await coordenador.commit(tx); // so agora os locks sao liberados
}
// Os locks ficam presos do "prepare" ate o "commit" — a duracao
// da chamada MAIS LENTA das tres, somada a latencia de rede.
//
// Agora o coordenador cai depois do prepare e antes do commit.
// Os tres participantes ficam "in-doubt": travados, sem saber se
// commitam ou abortam, esperando um coordenador que nao volta.
// O estoque daquele item esta congelado. Ninguem mais compra.O que quebra
- Um participante fica lentoOs locks do prepare ficam presos pelo tempo do mais lento, e a latência do conjunto vira a soma das piores — a plataforma inteira arrasta.
- O coordenador caiOs participantes ficam in-doubt: travados, sem poder commitar nem abortar, esperando uma decisão que não chega até o coordenador voltar.
- Mais serviços entramA disponibilidade da transação é o produto das disponibilidades de cada participante, então cada serviço novo a derruba um pouco mais.
A saída: Entre serviços independentes, troque atomicidade travada por consistência eventual: use uma saga. Cada passo commita localmente na hora, e uma falha adiante dispara compensações que desfazem os passos anteriores — ninguém segura lock esperando os outros, e um serviço fora do ar não congela o resto.
Onde isto dá errado
Usar 2PC entre microsserviços
É a tentação clássica de quem quer atomicidade entre serviços. Os locks distribuídos e o coordenador único derrubam a disponibilidade e acoplam serviços que deveriam ser independentes. Quase sempre a resposta certa é uma saga.
Ignorar o estado in-doubt
Um coordenador que cai entre as fases deixa participantes travados sem saber a decisão. Sem um coordenador com log durável e recuperação, esses recursos ficam presos até intervenção manual — e ninguém planejou para isso.
Subestimar a duração dos locks
Os recursos ficam travados do prepare ao commit, não por um instante. Sob latência de rede ou um participante lento, essa janela vira gargalo de concorrência e o throughput despenca.
Achar que mais participantes é de graça
Cada participante adicional multiplica a chance de a transação não concluir: basta um indisponível para tudo parar. A disponibilidade do 2PC cai a cada nó que entra, não sobe.
As duas fases, e onde mora o perigo
// 2PC: um coordenador conduz duas fases entre os participantes.
// Fase 1 (prepare): "voce consegue commitar? trave e prometa."
// Fase 2 (commit): "todos disseram sim -> apliquem de verdade."
async function doisFases(coord: Coordenador, parts: Participante[]) {
// FASE 1 — prepare: cada um trava recursos e vota
const votos = await Promise.all(
parts.map((p) => p.prepare()), // retorna "sim" (pronto) ou "nao"
);
if (votos.every((v) => v === "sim")) {
// FASE 2a — todos prometeram: nao ha volta, commita em todos
await Promise.all(parts.map((p) => p.commit()));
} else {
// FASE 2b — alguem recusou: aborta em todos
await Promise.all(parts.map((p) => p.abort()));
}
}
// Entre o prepare e o commit os recursos ficam TRAVADOS.
// Se o coordenador morrer nessa janela, os participantes ficam
// "in-doubt" — travados sem saber a decisao. Esse e o calcanhar
// do 2PC, e o motivo de sagas existirem.Quando usar × quando evitar
Use quando
3- Atomicidade inegociável entre sistemas, com passos curtos.
- Dentro de uma fronteira confiável e de baixa latência.
- Entre recursos que já falam XA, como um banco e um broker.
Evite quando
2- Entre microsserviços independentes — prefira saga.
- Quando os passos são longos ou dependem de terceiros.
Testar o que ficou
4 armadilhas de Two-Phase Commit (2PC) viram perguntas, com o nome do padrão escondido, misturadas com padrões parecidos.
Onde isto aparece de verdade
Você já usa isto, talvez sem saber o nome.
transações XAO padrão que coordena o commit entre bancos e filas de mensagens diferentes através de um coordenador — 2PC com outro nome.PREPARE TRANSACTION do PostgresO comando que deixa uma transação pronta para o commit de duas fases, aguardando a decisão final do coordenador.MSDTC no mundo .NETO coordenador de transações distribuídas da Microsoft, que orquestra o prepare e o commit entre os recursos participantes.
O padrão em uma linha
Sem nome de domínio, sem cerimônia. Só a ideia.
// Prepare em todos; depois commit ou abort.
await coordenador.prepare(participantes);O que ele cobra
2 saltos a mais entre a chamada e o efeito.
- Trava os recursos de todos os participantes do prepare até o commit — a duração da chamada mais lenta, somada à rede
- Um coordenador que cai entre as fases deixa participantes 'in-doubt', travados sem saber se commitam ou abortam
- A disponibilidade despenca: basta um participante fora do ar para a transação inteira não poder concluir
Não vale se: os passos toleram acontecer em momentos diferentes e ser desfeitos por compensação. Aí uma saga entrega o mesmo objetivo sem travar recurso nenhum.
Costuma ser confundido com
Onde isto aparece
Continue por aqui
- SagaCoordena uma transação distribuída de longa duração como uma sequência de transações locais, cada uma com uma ação compensatória para desfazer semanticamente em caso de falha.
- Teorema CAPQuando a rede parte um sistema distribuído em dois, você só pode manter uma entre consistência e disponibilidade — não as duas. O CAP não é um menu de três opções: é uma escolha forçada que só aparece durante a partição, e que a maioria dos sistemas afina por operação, não de uma vez.
- Níveis de isolamentoTransações concorrentes se atrapalham de formas específicas e nomeadas. O nível de isolamento é a escolha de quais dessas anomalias o banco vai impedir — e cada degrau a mais custa concorrência, ou custa transações abortadas.