¿Qué es el commit de dos fases, y por qué la mayoría de los sistemas distribuidos lo evitan a gran escala?
El commit de dos fases compra atomicidad real entre servicios haciendo que cada participante prometa hacer commit antes de que nadie lo haga — y esa promesa es justo lo que puede congelar el sistema por completo.
APRENDE HACIENDOPon a prueba tu intuición3 pasos
¿Qué es el commit de dos fases, y por qué la mayoría de los sistemas distribuidos lo evitan a gran escala?
Usa los ejemplos y la explicación de la lección para comprobar tu idea.
Reflexión personal. No se envía a ningún servicio de IA.#El protocolo que garantiza atomicidad negándose a avanzar hasta que todos estén de acuerdo
El commit de dos fases (2PC) resuelve un problema más estrecho y más difícil que una saga: atomicidad real a través de múltiples gestores de recursos — o bien todos los participantes hacen commit, o todos abortan, sin ninguna ventana en la que un observador pudiera ver un resultado mixto. Es el protocolo que subyace a las transacciones relacionales distribuidas (las transacciones XA en JDBC/JTA, por ejemplo), y entender exactamente dónde se bloquea es lo que explica por qué casi nadie lo usa en una arquitectura de microservicios a escala de internet.
#Fase 1: Prepare (votación)
Un coordinador envía un mensaje PREPARE a cada participante. Cada participante hace todo excepto hacer visible el cambio: escribe la información de redo/undo de la transacción en almacenamiento duradero y adquiere todos los bloqueos que va a necesitar. Luego responde YES (puedo garantizar que esto hará commit pase lo que pase) o NO (aborta, no puedo hacer esto). Votar YES es una promesa vinculante, no una actualización de estado — desde ese instante, el participante ya no puede abortar ni hacer commit unilateralmente; debe esperar la decisión del coordinador, manteniendo sus bloqueos todo ese tiempo.
#Fase 2: Commit (o abort)
Si todos los participantes votaron YES, el coordinador registra la decisión de forma duradera ("commit") y envía COMMIT a todos; cada participante aplica el cambio, libera sus bloqueos y confirma. Si algún participante votó NO — o no respondió dentro de un tiempo límite — el coordinador envía ABORT en su lugar, y cada participante descarta sus cambios preparados y libera sus bloqueos.
#Dónde exactamente ocurre el bloqueo
El modo de fallo que le da a 2PC su reputación es el hueco resaltado arriba: en cuanto un participante vota YES, renuncia a su derecho a decidir unilateralmente. Si el coordinador se cae (o la red se particiona) después de recolectar los votos pero antes de entregar el COMMIT/ABORT final, cada participante queda en un estado incierto, "in-doubt" — no puede hacer commit (el coordinador podría haber decidido abortar por un NO de otro participante) y no puede abortar (el coordinador podría ya haber decidido hacer commit, y otros participantes podrían ya haberlo aplicado). El único movimiento totalmente seguro es seguir reteniendo todos los bloqueos adquiridos en la Fase 1 y esperar —potencialmente para siempre— a que el coordinador se recupere y le comunique la respuesta. Cualquier otra transacción que necesite esas mismas filas también se bloquea. Por eso a 2PC se le describe formalmente como un protocolo bloqueante: el fallo de un solo nodo puede congelar parte del sistema indefinidamente, que es exactamente el modo de fallo que un sistema de alta disponibilidad está construido para evitar.
#El resto de la factura
Incluso cuando nada se cae, 2PC es costoso por construcción:
- Latencia. Cada transacción paga al menos dos viajes de ida y vuelta completos por red a cada participante antes de poder liberar un solo bloqueo — entre zonas de disponibilidad o regiones, esto es brutal para el throughput.
- Duración de los bloqueos. Los bloqueos se mantienen desde
PREPAREhasta elACKfinal, no solo durante el trabajo local — bajo contención esto serializa transacciones que un diseño basado en sagas dejaría correr de forma concurrente. - Acoplamiento de disponibilidad. Cada participante debe estar accesible para que la transacción se complete; 2PC elige consistencia por encima de disponibilidad durante una partición, lo cual rara vez es el trade-off correcto para un sistema de cara al público (esto es una consecuencia directa del teorema CAP: 2PC ante una partición o se bloquea o viola la atomicidad, nunca se mantiene disponible y consistente a la vez).
- El coordinador como punto único de fallo. El propio log de recuperación de fallos del coordinador se convierte en infraestructura crítica — si se pierde, las transacciones en duda nunca pueden resolverse automáticamente.
El commit de tres fases (3PC) intenta reducir la ventana de bloqueo añadiendo un paso de confirmación "pre-commit" para que los participantes puedan agotar el tiempo de espera y abortar con seguridad en más escenarios, pero de todas formas asume que no hay particiones de red para garantizar un comportamiento no bloqueante — que es exactamente la suposición que falla en la práctica — así que 3PC tiene poco uso real.
#Por qué (y dónde) los sistemas lo evitan
A escala de internet, la respuesta estándar es renunciar a la atomicidad estricta y adoptar el patrón saga: cada servicio hace commit de su propia transacción local de forma independiente y publica lo que ocurrió, manejando los fallos con acciones compensatorias en lugar de un bloqueo compartido mantenido a través de la red (ver la lección hermana sobre sagas para la mecánica completa). Cuando un equipo todavía necesita un efecto que parezca atómico entre dos recursos que sí controla de punta a punta — el caso más común es "escribir en mi base de datos y publicar un evento" — la respuesta moderna suele ser el patrón de outbox transaccional: escribir el evento en una tabla outbox dentro de la misma transacción ACID local que el cambio de negocio, y dejar que un relay separado lo publique después, lo cual evita por completo necesitar 2PC entre la base de datos y el broker. El 2PC genuino sobrevive sobre todo dentro de middleware empresarial fuertemente acoplado (gestores de recursos compatibles con XA dentro de un mismo datacenter, un puñado de participantes, red de baja latencia) donde la ventana de bloqueo es lo bastante corta y rara como para tolerarla.
:::danger La ventana de incertidumbre es todo el problema 2PC no es peligroso porque sea lento — es peligroso porque una caída del coordinador en el instante exacto equivocado deja a los participantes reteniendo bloqueos sin ningún movimiento unilateral seguro, durante un tiempo potencialmente indefinido. Cualquier planificación de capacidad que asuma que las transacciones 2PC "normalmente se resuelven rápido" está planificando para el caso común e ignorando el modo de fallo para el cual existe el protocolo. :::
:::note Esto es un trade-off del teorema CAP disfrazado de protocolo Durante una partición de red, 2PC o se bloquea (favoreciendo consistencia sobre disponibilidad) o, si un participante decide unilateralmente sin noticias del coordinador, arriesga que dos participantes lleguen a decisiones distintas (favoreciendo disponibilidad sobre consistencia, y rompiendo la atomicidad que 2PC existe para proveer). No hay una tercera opción dentro del protocolo mismo. :::
:::tip Cuando de verdad necesites atomicidad entre servicios, recurre primero al patrón outbox "Actualizar mi base de datos y publicar un evento" no necesita 2PC entre una base de datos y un broker — escribe el evento en una fila de outbox dentro de la misma transacción local que la escritura de negocio, y deja que un proceso poller/CDC separado lo transmita con entrega al menos una vez. Obtienes la atomicidad que realmente necesitas sin un protocolo bloqueante distribuido. :::