Saltar al contenido

Commit en dos fases (2PC) explicado: prepare, commit y por qué bloquea

Cómo el commit en dos fases hace atómica una transacción entre bases de datos, qué pasa si cae el coordinador y cuándo encajan mejor las sagas o un outbox.

· Fundador y desarrollador

Publicado el

Diagrama de secuencia con una aplicación, un coordinador, una base de datos de pedidos y otra de pagos. Un fragmento par muestra PREPARE enviado a ambas bases y los votos YES. Un fragmento alt muestra COMMIT y las confirmaciones si todos votan sí, y ROLLBACK si alguno vota no.

Un pedido se escribe en una base de datos y el pago en otra. Deben ocurrir las dos cosas o ninguna. Cada base de datos puede confirmar su parte de forma atómica, pero nada local impide que una confirme mientras la otra se cae. El commit en dos fases (2PC) es el protocolo clásico para este problema: viene integrado en la mayoría de las bases de datos relacionales a través del estándar XA y hoy se usa dentro de las bases de datos SQL distribuidas.

También tiene una debilidad famosa: si el coordinador falla en el peor momento, los participantes pueden quedarse atascados con sus bloqueos, sin forma de decidir por su cuenta. Este artículo recorre el protocolo con un diagrama de secuencia, cada caso de fallo, cómo lo implementan los sistemas reales y cuándo una saga o un outbox es la mejor herramienta.

Los roles

  • Coordinador, o gestor de transacciones: dirige el protocolo y decide el resultado. Puede ser una biblioteca dentro de tu servidor de aplicaciones, un servicio aparte o un componente de una base de datos distribuida.
  • Participantes, o gestores de recursos: las bases de datos o colas que guardan cada una una parte de la transacción.

Cada parte mantiene un log duradero. Las escrituras en el log no son un detalle de implementación; son lo que permite que el protocolo sobreviva a las caídas.

El protocolo

Fase 1: prepare.

  1. La aplicación hace su trabajo en cada participante dentro de una transacción global y luego pide al coordinador que confirme.
  2. El coordinador envía PREPARE a cada participante.
  3. Cada participante se asegura de que podrá confirmar pase lo que pase después: escribe los cambios de la transacción y un registro de preparación en su log duradero, mantiene sus bloqueos y vota YES. Si no puede confirmar, por ejemplo porque falló una restricción, vota NO y revierte.

Fase 2: commit o abort.

  1. Si todos los votos son YES, el coordinador escribe una decisión COMMIT en su propio log. Esa escritura es el punto de commit: a partir de ahí la transacción está confirmada, aunque todas las máquinas se caigan un milisegundo después.
  2. El coordinador envía COMMIT a cada participante. Cada uno confirma, libera sus bloqueos y responde con un acuse.
  3. Si algún participante vota NO o no responde a tiempo, el coordinador decide ABORT y envía ROLLBACK en su lugar.

Un voto YES es una promesa. Tras votar YES, un participante ha renunciado al derecho de decidir solo: debe confirmar si se lo ordenan, incluso después de reiniciarse, y no puede revertir por su cuenta porque puede que el coordinador ya haya decidido confirmar.

El coste

Con N participantes, un commit correcto requiere dos viajes de ida y vuelta desde el coordinador y cuatro mensajes por participante: PREPARE, voto, COMMIT y acuse. Cada participante fuerza su log a disco dos veces, en el prepare y en el commit, y el coordinador fuerza su decisión una vez. Los bloqueos se mantienen todo el tiempo, así que el participante más lento decide cuánto tiempo bloquean todos los demás el trabajo que entra en conflicto.

Qué pasa cuando algo falla

Fallo Cuándo Resultado
Un participante se cae o agota su timeout Antes de votar El coordinador aborta la transacción
Un participante vota NO Fase 1 El coordinador aborta la transacción
Un participante se cae Después de votar YES Al reiniciarse encuentra el registro de preparación y pregunta el resultado al coordinador, manteniendo sus bloqueos mientras tanto
El coordinador se cae Antes de escribir la decisión Los participantes preparados esperan. Al recuperarse, sin decisión en su log, el coordinador aborta
El coordinador se cae Después de escribir COMMIT Al recuperarse lee su log y reenvía COMMIT
Partición de red Después de que un participante votara YES Ese participante queda bloqueado hasta que puede llegar al coordinador

Las filas que importan son aquellas en las que un participante ha votado YES y no puede oír la decisión. No puede confirmar, porque otro participante podría haber votado NO. No puede abortar, porque el coordinador podría haber confirmado. Espera, con sus bloqueos puestos, mientras el coordinador sea inalcanzable. Cualquier otra transacción que toque esas filas también espera. A eso se refiere la gente cuando dice que 2PC es un protocolo bloqueante.

Transacciones preparadas atascadas en la práctica

PostgreSQL implementa el lado del participante con PREPARE TRANSACTION, COMMIT PREPARED y ROLLBACK PREPARED. La función está desactivada por defecto (max_prepared_transactions vale 0), y la documentación advierte que está pensada para gestores de transacciones externos, no para aplicaciones. Una transacción preparada olvidada conserva sus bloqueos e impide que VACUUM recupere espacio.

Encontrar y resolver una se ve así:

-- 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 resuelvas una transacción preparada adivinando. Si el coordinador confirmó y tú reviertes un participante, has creado justo la incoherencia que 2PC existe para evitar. MySQL ofrece el mismo mecanismo con XA PREPARE, XA COMMIT y XA RECOVER.

Variantes que alivian el dolor

  • Presumed abort. Si el coordinador no tiene registro de una transacción, la respuesta es abortar. Esto ahorra escrituras de log en las transacciones abortadas y simplifica la recuperación; la mayoría de las implementaciones lo usan.
  • Participantes de solo lectura. Un participante que no cambió nada puede votar «read-only» y saltarse la fase 2.
  • Commit en tres fases. Añade una ronda de pre-commit para que los participantes puedan terminar sin el coordinador, pero solo si los retrasos de los mensajes están acotados y la red nunca se particiona. Esas suposiciones rara vez se cumplen, así que 3PC es sobre todo un tema de manual.
  • Replica el coordinador y los participantes. El remedio real del bloqueo es convertir cada rol en un grupo replicado que sobreviva a la pérdida de una máquina. Spanner, de Google, ejecuta el commit en dos fases sobre grupos Paxos justo por eso, y otras bases de datos SQL distribuidas aplican la misma idea con Raft. El commit en dos fases sigue decidiendo el resultado; el consenso mantiene disponible a cada parte.

Cuándo usar 2PC y cuándo no

2PC encaja cuando todos los participantes son bases de datos o brokers que admiten un paso de preparación, los controlas todos, las transacciones son cortas y necesitas atomicidad y aislamiento entre ellos. Funciona mal entre servicios de otros equipos, entre API HTTP que no tienen paso de preparación (un proveedor de pagos no mantendrá tu cargo en estado preparado) y en procesos de negocio que duran minutos o días.

Commit en dos fases Saga Transactional outbox
Consistencia Atómica; los bloqueos dan aislamiento Eventual, con pasos de compensación Eventual entre una base de datos y un broker
Bloqueo Sí, mientras hay una decisión pendiente No No
Requiere Participantes que admitan prepare Lógica de compensación para cada paso Una base de datos más un relay
Uso típico Bases de datos o shards que operas tú Flujos de negocio entre servicios Publicar eventos de forma fiable tras una escritura

El diagrama del patrón saga muestra la alternativa con compensaciones, y el patrón transactional outbox cubre el caso más habitual en el que los equipos recurren a 2PC: escribir en una base de datos y publicar un mensaje a la vez. A menudo la mejor solución es estructural: pon los datos que deben cambiar juntos en una sola base de datos, o en un solo shard, como explica el diagrama de sharding de bases de datos.

El diagrama del commit en dos fases

Diagrama de secuencia con una aplicación, un coordinador, una base de datos de pedidos y otra de pagos. Un fragmento par muestra PREPARE enviado a ambas bases y los votos YES. Un fragmento alt muestra COMMIT y las confirmaciones si todos votan sí, y ROLLBACK si alguno vota no.

10 componentes · 12 conexiones

Abrir en Archboard
Contenido del 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 en dos fases entre dos bases de datos. En la fase 1, el coordinador pide a ambos participantes que se preparen y recoge sus votos; en la fase 2, registra la decisión y ordena a todos confirmar, o revertir si algún voto fue no.

Cómo leerlo:

  • Cuatro líneas de vida. La aplicación, el coordinador, la base de datos de pedidos y la de pagos. El tiempo avanza hacia abajo.
  • El fragmento par es la fase 1. PREPARE va a la vez a los dos participantes, y cada uno devuelve un voto después de escribir su registro de preparación.
  • El fragmento alt es la fase 2. La rama superior es el caso normal: todos los votos son YES, el coordinador registra COMMIT y los dos participantes confirman y responden. La rama inferior es el camino de aborto.
  • La nota marca la ventana de duda entre un voto YES y la decisión, cuando los participantes mantienen bloqueos y no pueden decidir solos.
  • El bloque Markdown enumera los casos de fallo de la tabla anterior.

Dibujar transacciones distribuidas con honestidad

  • Muestra las escrituras del log. Un diagrama de 2PC sin «escribir el registro de preparación» y «escribir la decisión» deja fuera justo lo que lo hace correcto.
  • Marca el punto de commit. Un solo mensaje o escritura de log decide el resultado; señálalo.
  • Dibuja la ventana en la que todo se bloquea. Es la razón por la que los equipos evitan 2PC, así que un diagrama que la oculta engaña.
  • Pon los caminos de fallo en sus propios diagramas. Una caída del coordinador después del prepare merece su propia secuencia con los pasos de recuperación.

Para la notación en sí, el ejemplo de diagrama de secuencia cubre las líneas de vida, los fragmentos y la nomenclatura.

Constrúyelo en Archboard

  1. Inicia sesión con Google o GitHub y crea un diagrama.
  2. Añade cuatro nodos Línea de vida del pack Secuencia: Application, Coordinator, Orders DB y Payments DB.
  3. Dibuja los mensajes como aristas, de arriba abajo: la petición de commit, PREPARE a cada base de datos, los votos YES, COMMIT a cada base de datos y los acuses.
  4. Añade un Fragmento combinado del pack Secuencia con el operador par alrededor de los mensajes de preparación, y un segundo Fragmento combinado con el operador alt alrededor de las ramas de commit y rollback.
  5. Añade una Nota para la ventana de duda y un bloque Markdown del pack Básico con la tabla de fallos.

El ejemplo usa 7 nodos de diagrama, dentro del plan Free.

Preguntas frecuentes

¿Por qué se dice que el commit en dos fases es un protocolo bloqueante? Un participante que ha votado YES no puede confirmar ni abortar por su cuenta. Si el coordinador se vuelve inalcanzable antes de enviar la decisión, el participante espera con sus bloqueos puestos, igual que cualquier transacción que necesite esas filas.

¿El commit en dos fases es lo mismo que una transacción distribuida? Es la forma más común de confirmar una. Una transacción distribuida abarca varios recursos; el commit en dos fases es el protocolo que hace atómica su confirmación.

¿El commit en tres fases resuelve el problema del bloqueo? Solo bajo suposiciones que rara vez se cumplen: retrasos de mensajes acotados y ninguna partición de red. El remedio práctico es replicar el coordinador y los participantes con consenso, como hace Spanner.

¿Puedo usar el commit en dos fases entre microservicios? Rara vez sale bien. Los servicios tendrían que exponer un paso de preparación y mantener bloqueos unos para otros, lo que acopla su disponibilidad. Entre servicios, lo habitual son las sagas y el patrón outbox.

¿El commit en dos fases garantiza el aislamiento? Garantiza la atomicidad. El aislamiento viene de los bloqueos de los participantes, que 2PC mantiene hasta que llega la decisión, de modo que las transacciones concurrentes no ven resultados parciales.

Empieza gratis — un puesto, sin tarjeta de crédito.

Artículos relacionados

← Volver al blog

Abre un lienzo en un minuto

Inicia sesión con Google o GitHub. Gratis para un asiento. Mejora el plan cuando se una el equipo.