Two-Phase Commit

Qué es

Two-Phase Commit (2PC) es un protocolo para implementar atomic-commit entre participantes transaccionales. Un coordinador solicita primero que cada participante se prepare; después decide commit si todos aceptaron, o abort si no puede obtener esa condición.

Las fases separan una promesa durable de poder confirmar y una decisión final. Una secuencia de llamadas «guardar en A; guardar en B» no es 2PC: sus confirmaciones independientes conservan el dual-write-problem.

Roles y transacciones locales

El coordinador identifica los participantes, reúne sus votos y conserva la decisión global. Cada participante controla una transacción local y los recursos necesarios para ejecutarla, abortarla o confirmar su estado preparado.

El trabajo de negocio ocurre dentro de esas transacciones locales. Prepararse no es hacer commit definitivo ni liberar todo: el participante promete que podrá respetar la decisión posterior, incluso después de reiniciar. Esto exige registrar suficiente estado en almacenamiento durable antes de votar yes.

Si los recursos usan locks para aislar sus escrituras, normalmente los conservan hasta resolver la transacción. Otras implementaciones pueden usar mecanismos distintos, pero siguen reteniendo estado y recursos necesarios. PostgreSQL ilustra esta distinción: una transacción preparada sobrevive al fin de la sesión y continúa manteniendo sus locks hasta commit o rollback. PREPARE TRANSACTION.

Cómo funciona

Coordinator                 Participants
prepare ------------------> validate + persist prepared state
        <------------------ yes / no
persist commit or abort
decision -----------------> apply decision + release resources
        <------------------ acknowledgement

Prepare y vote

El coordinador pide preparar cada rama. Un participante que encuentra una restricción violada o no puede asegurar su recuperación vota no y no promete commit. Uno que persiste su estado preparado vota yes.

Todos los yes permiten decidir commit; un no obliga a abort. Si falta un voto, el coordinador puede decidir abort antes de haber registrado commit. No necesita interpretar el timeout como prueba de que el participante está muerto: simplemente no obtuvo todos los votos necesarios.

Commit o abort

Antes de difundir el resultado, el coordinador lo registra de forma durable. Si decide commit y cae luego, la recuperación debe repetir commit. No puede cambiarlo a abort porque algunos acknowledgements no llegaron.

Los participantes aplican la decisión, registran su resolución y liberan recursos. Los mensajes pueden perderse o repetirse. Identificar la transacción y recordar su resultado permite reconocer una orden repetida sin confirmar dos veces la misma rama. El orden entre registros durables y mensajes sostiene la recuperación. Gray y Lamport: descripción y recuperación de 2PC, sección 3.

Ejemplo

Order Service coordina una operación entre Inventory DB y Payment DB. Son recursos transaccionales internos: el segundo guarda un asiento contable, no representa una llamada arbitraria a una tarjeta externa.

Inventory DB prepara la reducción de una unidad; Payment DB prepara el débito correspondiente. Ninguna rama termina en commit definitivo solo porque su escritura local sea válida. Si Payment DB rechaza el débito por saldo insuficiente, el coordinador decide abort para ambas. Si las dos votan yes, registra commit y lo comunica.

Si Inventory DB confirma y Payment DB está temporalmente desconectada, la segunda debe completar commit al recuperarse. No se compensa la primera para «deshacer» una decisión ya tomada. La API debe distinguir un resultado desconocido de un rechazo definitivo; el pedido tampoco queda automáticamente incluido si su store no participa.

Fallos y comportamiento bloqueante

SituaciónComportamiento esperado
Participante falla antes de prepararseEl coordinador puede abortar si no obtiene todos los votos.
Participante falla después de votar yesRecupera su estado preparado y consulta la decisión; no elige unilateralmente.
Coordinador falla sin que un preparado conozca el resultadoEl participante puede quedar esperando información autoritativa.
Coordinador cae después de persistir commitRecupera el log y vuelve a difundir commit.
Se pierde un acknowledgement finalSe reenvía o consulta el resultado de la misma transacción.

El caso característico de bloqueo aparece cuando un participante preparado no puede obtener evidencia suficiente del desenlace. Hay ejecuciones en las que conserva locks y estado hasta que vuelve el coordinador o una fuente válida de recuperación. Otros participantes pueden ayudar si conocen la decisión; no siempre disponen de esa información.

Liberar un preparado por timeout eligiendo abort puede contradecir un commit ya registrado y aplicado en otro recurso. Una resolución heurística administrativa sale de la garantía normal y puede exigir reconciliación. Los logs deben permitir distinguir trabajo no preparado, preparado pendiente y resultado final; perderlos permanentemente excede el modelo usual de crash y recuperación.

Por qué no es consenso

2PC usa un coordinador y unanimidad de preparación de recursos para cumplir atomic commit. No incorpora por sí solo la replicación y el cambio seguro de líder necesarios para mantener decisiones disponibles cuando ese coordinador falla.

Replicar la coordinación mediante consenso puede mejorar tolerancia a fallos, pero no elimina la necesidad de que cada participante requerido pueda preparar y recuperar su parte. Tampoco elimina contención en los datos ni particiones de un recurso. Son capas con responsabilidades diferentes.

Cuándo usarlo y trade-offs

2PC es razonable para operaciones acotadas entre recursos compatibles cuando confirmar juntos evita un estado de negocio inaceptable. Sus costos incluyen rondas de red, escrituras durables, retención de recursos y procedimientos para resolver preparados pendientes. Una interrupción breve puede prolongar la espera de otras transacciones que compiten por esos recursos.

No es una elección automática para procesos con espera humana, envíos físicos o APIs que no pueden preparar una transacción. Tampoco ofrece aislamiento global por sí solo: necesita combinarse con control de concurrencia apropiado.

saga-pattern acepta commits locales y recuperación semántica; try-confirm-cancel expone reservas de negocio. No son sustituciones equivalentes. Una transacción local, una frontera de datos distinta o una intención durable para publicación posterior pueden ser opciones más simples si satisfacen la regla requerida. idempotency sigue siendo útil al reintentar solicitudes, pero no reemplaza el acuerdo de commit.