Maintaining the Atomicity property of ACID transactions across multiple physically isolated databases requires solving the Distributed Atomic Commitment Problem: either all participating nodes execute and commit their local transactional state updates, or all abort and rollback, leaving no partial modifications. While consensus protocols (Paxos, Raft) elect leaders or order identical log entries, atomic commit requires universal unanimous consent. The classical solution, Two-Phase Commit (2PC) (Gray, 1978), enforces atomicity but introduces catastrophic blocking vulnerabilities. In modern microservices and geo-distributed architectures, the paradigm shifts to non-blocking protocols such as Three-Phase Commit (3PC) and compensating Sagas.
1. The Two-Phase Commit Protocol (2PC)
In 2PC, a centralized Coordinator coordinates a transaction across multiple Participants (cohorts). The protocol executes in two strict phases:
Phase 1: Prepare (Voting Phase)
- The coordinator writes a `PREPARE` record to its WAL and broadcasts `PREPARE` to all participants.
- Each participant executes the transaction locally up to the commit point, writing updates and undo/redo logs to its local storage.
- If the participant successfully reserves locks and guarantees it can commit, it logs `VOTE_COMMIT` and responds `YES`. If it fails or encounters a constraint violation, it logs `VOTE_ABORT` and responds `NO`.
Phase 2: Commit (Decision Phase)
- Unanimous Consent: If the coordinator receives `YES` from every single participant, it writes `GLOBAL_COMMIT` to its WAL and broadcasts `COMMIT` to all cohorts.
- Abort on Single Failure: If even one participant votes `NO` (or times out), the coordinator writes `GLOBAL_ABORT` to its WAL and broadcasts `ABORT`.
- Participants apply the decision, release all acquired locks, and respond with an acknowledgment `ACK`. Once all ACKs are collected, the coordinator writes `END` to its log.
2. The Fatal Flaw of 2PC: The Coordinator Blocking Problem
Despite its conceptual simplicity, 2PC is a blocking protocol. Consider the following critical failure scenario:
- All participants vote `YES` during Phase 1.
- The coordinator receives all votes, decides to commit, writes `GLOBAL_COMMIT` to disk, but crashes before sending the `COMMIT` message across the network.
Participants that voted `YES` enter an indeterminate state. They cannot unilaterally abort, because the coordinator may have sent a commit to another node. They cannot unilaterally commit, because another cohort might have voted `NO`. Consequently, participants are blocked indefinitely, holding database row and table locks, rejecting subsequent client traffic, and causing system-wide cascading deadlock until the crashed coordinator is manually recovered.
3. Three-Phase Commit (3PC): Eliminating the Blocking State
In 1981, Dale Skeen proved that a non-blocking commit protocol requires an extra communication round and introduced Three-Phase Commit (3PC). 3PC splits the decision phase into two distinct steps to ensure no state transition directly bridges uncertainty and commitment:
- Can-Commit?: The coordinator asks participants if they are able to commit. Participants reply with `YES` or `NO`.
- Pre-Commit: If all agree, the coordinator issues `PRE-COMMIT`. Participants enter the Pre-Commit state, acknowledging receipt. At this point, all participants know that everyone voted yes.
- Do-Commit: Upon gathering ACKs from the pre-commit round, the coordinator issues the final `DO-COMMIT`.
The Network Partition Limitation
While 3PC prevents indefinite blocking in a pure crash-stop environment with reliable failure detectors, it fails completely under network partitions. If a network partition isolates nodes during 3PC, nodes on one side of the split may timeout in the pre-commit state and commit, while nodes on the other side timeout in an uncertain state and abort, violating atomic consistency. As a result, 3PC is rarely deployed on WANs.
4. The Saga Pattern: Long-Running Distributed Transactions
In modern cloud-native architectures spanning microservices with independent databases, holding synchronous distributed locks (as in 2PC) destroys availability, latency, and scalability. In 1987, Hector Garcia-Molina and Kenneth Salem proposed the Saga Pattern.
A Saga decomposes a distributed transaction into a sequence of $n$ local transactions $T_1, T_2, dots, T_n$. Each local transaction $T_i$ commits immediately in its local database, updating local state and releasing locks. Atomicity across the overarching workflow is preserved through Compensating Transactions ($C_1, C_2, dots, C_{n-1}$):
$$ ext{Forward Flow: } T_1 o T_2 o dots o T_k quad ( ext{failure at } T_k)$$
$$ ext{Backward Rollback: } C_{k-1} o C_{k-2} o dots o C_1$$
For every forward step $T_i$ that commits, there exists a compensating step $C_i$ that semantically reverses its real-world side effects (e.g. issuing a credit refund if a flight booking step fails). Compensating transactions must be mathematically idempotent and guaranteed to eventually succeed.
Choreography vs. Orchestration
- Choreography: Microservices react to domain events emitted over an event log (Kafka). There is no central controller; each service listens to previous events and executes local transactions. Best for simple workflows (2–4 steps).
- Orchestration: A dedicated state-machine orchestrator (e.g. Temporal, AWS Step Functions) centrally executes forward steps, tracks execution state, and coordinates compensatory rollbacks upon failure. Essential for complex, mission-critical business transactions.