Distributed Transactions: Demystifying the Two-Phase Commit Protocol
Distributed transactions are among the most challenging concepts in modern software architecture. When an application transitions from a monolithic database to a microservice architecture, simple ACID guarantees provided out of the box by relational databases cease to exist. A single logical business transaction now spans multiple network boundaries, disparate databases, and independent services.
The Two-Phase Commit (2PC) protocol is a foundational consensus-like mechanism designed to achieve cross-service Atomicity (all operations succeed or none do) and Isolation in distributed systems.
The Problem: The 10-Minute Food Delivery Constraint
Consider a real-world hyper-local 10-minute food delivery guarantee (such as Zomato Instant):
- Dark Store Warehousing: Demand is predicted per locality; food is purchased in bulk and stocked at localized micro-warehouses (“dark stores”).
- Execution Window: Once an order is placed, the store has 2 minutes to warm and pack the meal, and a delivery partner has 8 minutes to pick up and deliver the food to the customer.
The Engineering Invariant
To guarantee a 10-minute delivery, an order cannot be placed unless two mutually dependent conditions are simultaneously true:
- The exact food item is physically available in the designated local store.
- A delivery partner is physically stationed nearby and available to take the run.
┌────────────────┐
│ User Request │
└───────┬────────┘
│
▼
┌──────────────────┐
│ Order Service │ (Transaction Coordinator)
└───┬──────────┬───┘
Reserve/ │ │ Reserve/
Book Food │ │ Book Rider
▼ ▼
┌────────────────────┐ ┌──────────────────────┐
│ Store Service │ │ Delivery Service │
│ (Food Inventory) │ │ (Fleet Management) │
└────────────────────┘ └──────────────────────┘
Why Naive REST Calls Fail (The Partial Failure Problem)
Suppose the Order Service sequentially executes simple HTTP REST calls without transactional coordination:
# NAIVE (BUGGY) IMPLEMENTATION
def place_order(user_id, item_id):
# Step 1: Assign food
store_service.assign_food(item_id)
# Step 2: Assign driver
delivery_service.assign_driver(user_id)
# Step 3: Record order
order_db.save(user_id, item_id)
If Step 1 succeeds but Step 2 fails (e.g., no drivers are available, or the delivery service times out):
- The store receives the command, begins warming up the food, and packs it.
- No delivery driver ever arrives.
- Consequences: Food hygiene degrades due to reheating, physical inventory is wasted, and the business incurs a direct financial loss.
Conversely, if Step 2 succeeds and Step 1 fails:
- A delivery agent is summoned to the dark store.
- The requested food item is out of stock.
- Consequences: Driver idle time, lost wages, and a severely degraded partner experience.
Because network partitions and hardware failures are inevitable, you must ensure all-or-nothing atomicity across separate service boundaries.
The Two-Phase Commit (2PC) Protocol
Two-Phase Commit solves this dilemma by dividing the execution into two distinct stages: the Prepare Phase (Voting/Reservation) and the Commit Phase (Execution/Assignment). In this topology, the Order Service acts as the Coordinator, while the Store Service and Delivery Service act as Participants (or Cohorts).
sequenceDiagram
autonumber
participant C as Order Service (Coordinator)
participant S as Store Service (Participant 1)
participant D as Delivery Service (Participant 2)
Note over C,D: PHASE 1: PREPARE (Reservation Phase)
C->>S: Prepare: Reserve Food (Item Lock + TTL)
S-->>C: Vote YES (Reserved)
C->>D: Prepare: Reserve Driver (Driver Lock + TTL)
D-->>C: Vote YES (Reserved)
Note over C,D: PHASE 2: COMMIT (Execution Phase)
C->>S: Commit: Book Reserved Food
S-->>C: Acknowledged (Cooking/Packing Initiated)
C->>D: Commit: Assign Reserved Driver
D-->>C: Acknowledged (Dispatch Dispatched)
Note over C: Finalize Order in Orders DB
Phase 1: Prepare (Reservation Phase)
Instead of immediately triggering downstream side effects (such as instructing staff to microwave food or dispatching an agent), the coordinator first asks participants if they can guarantee the necessary resources.
- Reserve Food: The coordinator calls
store_service.reserve_food(item_id, order_id). The store service acquires a row-level or semantic lock on that specific unit in its database, marking it as RESERVED. The kitchen staff is not notified.
- Reserve Driver: The coordinator calls
delivery_service.reserve_driver(order_id). The delivery service locks an available partner in its database, marking them as RESERVED. The driver is not notified.
What is a Reservation?
A reservation guarantees exclusivity. It ensures that no concurrent transaction can claim that specific burger or driver while this transaction is under consideration.
Phase 2: Commit (Assignment Phase)
Once the coordinator successfully receives a YES vote from every participant:
- Book Food: The coordinator invokes
store_service.book_food(reservation_id). The food state changes from RESERVED to COMMITTED. The dark store screen updates: staff begins heating and packaging the order.
- Book Driver: The coordinator invokes
delivery_service.book_driver(reservation_id). The partner is officially dispatched to the dark store.
- Confirm to Customer: Once both commit acknowledgments return, the coordinator commits the order to the
Orders DB and notifies the client that their 10-minute delivery has begun.
Because the items were already reserved during Phase 1, contention is zero during Phase 2. Barring transient network blips, commit calls are guaranteed to succeed.
Failure Handling & Fault Tolerance
Distributed systems fail in unpredictable ways. 2PC accounts for these failure modes via deterministic rollbacks and time-based resource leases.
1. Failure During the Prepare Phase (Rollback)
If any participant votes NO (e.g., food is out of stock or no drivers are nearby) or fails to respond within a timeout window:
sequenceDiagram
autonumber
participant C as Order Service
participant S as Store Service
participant D as Delivery Service
C->>S: Prepare: Reserve Food
S-->>C: OK (Food Reserved)
C->>D: Prepare: Reserve Driver
D-->>C: ERROR / Timeout (No Drivers)
Note over C: Transaction Aborted
C->>S: Abort: Cancel Reservation (Release Lock)
S-->>C: Acknowledged (Lock Released)
Note over C: Inform User: