Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 64

Phases of Two-Phase Commit (2PC)

Recommended resources

Hello! Welcome to the next lesson in our journey through distributed databases.

In the last lesson, we explored how a single complex query, like a hash join, can be parallelized across many machines. We saw that the core challenge was shuffling data so that related information ends up on the same node for processing. Now, we're going to shift our focus from a single parallel query to a distributed transaction—an operation that needs to make changes on multiple database nodes and must succeed or fail as a single, atomic unit.

This brings us to a fundamental problem in distributed systems: how do you guarantee atomicity when any of the participating nodes, or the network itself, could fail? Today's lesson addresses this directly. Your learning outcome is to be able to describe the phases of the two-phase commit (2PC) protocol for distributed transactions. This classic algorithm is the foundation for achieving transactional guarantees in a distributed environment.

The Problem: Achieving Atomic Commitment

In a single-node database, transactions are atomic: they either commit all their changes or abort, leaving the database state untouched. In a distributed system, a single transaction might involve updates to several independent databases or services. For example, placing an order in an e-commerce system might require:

  1. Decrementing inventory in the inventory-service.
  2. Creating a charge in the payment-service.
  3. Recording the order in the order-service.

If the payment service succeeds but the inventory service fails, you end up in an inconsistent state—charging a customer for an item that is out of stock. We need a way for all participants to agree on the final outcome: either they all commit, or they all abort. This is the atomic commitment problem.

To start, let's get a clear conceptual framing of this problem.

Distributed Systems 7.1: Two-phase commit

This video from Martin Kleppmann, an expert in distributed systems, introduces the concept of atomic commitment and distinguishes it from the related problem of consensus. This will help you understand the specific constraints that 2PC is designed to solve.

Please watch the segment from 02:28 to 06:34. Focus on why atomicity is essential for distributed transactions and the key difference between atomic commitment and general-purpose consensus algorithms like Raft.

As the video explains, unlike consensus where any proposed value can be chosen, atomic commitment has a strict rule: if even one participant votes to abort, the entire transaction must abort.


The Players and The Protocol

The Two-Phase Commit protocol formalizes this coordination. It involves two types of actors:

  • The Coordinator: The single node that initiates the transaction and is responsible for making the final commit-or-abort decision. In our e-commerce example, this might be the order-service.
  • The Participants: All the other nodes that are part of the transaction (e.g., inventory-service, payment-service).

The protocol unfolds in two distinct phases, as its name suggests. Let's walk through them. The state diagram below provides a formal view of the states and transitions for both the coordinator and a participant. We will refer to this as we discuss each phase.

Two-Phase Commit Protocol State Diagrams
These state diagrams show the flow of the 2PC protocol. (a) illustrates the Coordinator's states and transitions, while (b) details the Participant's states. The arrows represent messages that trigger state changes.

Phase 1: The Prepare (or Voting) Phase

This phase is about checking if every participant is able and willing to commit.

  1. Coordinator sends PREPARE: The Coordinator sends a PREPARE message to all Participants. This is the "get ready" signal.
  2. Participants Prepare and Vote: Upon receiving PREPARE, each Participant determines if it can commit the transaction. This isn't a hypothetical check. The Participant must perform all the necessary work, such as validating constraints and acquiring locks. Critically, it writes all its changes to a durable log (like a Write-Ahead Log) on disk.
    • If successful, the Participant has now made a binding promise. It is no longer allowed to unilaterally abort. It force-writes a READY or PREPARED record to its log and sends a VOTE-COMMIT message back to the Coordinator.
    • If the Participant fails for any reason (e.g., a constraint violation), it can immediately decide to abort. It sends a VOTE-ABORT message to the Coordinator.

The most important part of this phase is the promise made by participants who vote to commit. By writing their state to a durable log, they guarantee that they can still commit the transaction even if they crash and restart later.


Phase 2: The Commit (or Decision) Phase

The Coordinator now acts as the final arbiter based on the votes it received.

  1. Coordinator Makes a Decision:
    • If the Coordinator receives VOTE-COMMIT from all Participants, it decides to COMMIT.
    • If it receives even one VOTE-ABORT, or if a participant times out, it decides to ABORT.
  2. Coordinator Logs and Broadcasts: The Coordinator force-writes its final decision (COMMIT or ABORT) to its own durable log. This is crucial for its own recovery. It then broadcasts this decision message to all Participants.
  3. Participants Execute the Decision: Upon receiving the decision from the Coordinator, each Participant:
    • Writes the decision to its own log.
    • If the decision is COMMIT, it applies the changes from its log, making them permanent, and releases any locks.
    • If the decision is ABORT, it uses its log to undo any changes and releases locks.
  4. Acknowledgment: After carrying out the decision, each Participant sends an ACK message back to the Coordinator. Once the Coordinator has received ACKs from all Participants, the protocol for that transaction is complete.

This sequence ensures that either all participants reach the committed state or all reach the aborted state.

To see a clear, step-by-step walkthrough of the messaging in both success and abort scenarios, please review the following diagrams from CMU's database course slides.

Distributed Databases Part 2

These slides from CMU provide simple, clear diagrams of the 2PC message flow for both a successful commit and an abort case. They perfectly illustrate the two phases we've just discussed.

Please examine slides 11 and 12 (labeled 'TWO-PHASE COMMIT (SUCCESS)' and 'TWO-PHASE COMMIT (ABORT)'). Trace the 'Prepare' and 'Commit'/'Abort' messages between the Coordinator and Participants.


An Engineering Perspective: 2PC in Practice

The abstract protocol of messages and logs is essential, but seeing how it maps to a real system makes it much more concrete. Given your background in C++, Java, and data structures, you'll appreciate how these concepts translate into service endpoints, database schemas, and SQL transactions.

The following video provides an excellent, applied walkthrough of 2PC using a food delivery service example. It demonstrates how RESERVE operations map to the prepare phase and ASSIGN operations map to the commit phase.

Implementing Distributed Transactions using Two Phase Commit Protocol

This video by Arpit Bhayani implements a 2PC protocol in Go for a practical use case. It's a fantastic demonstration of the engineering principles behind the protocol.

Please watch the following segments: Phases Explained (04:11 - 07:25): Understand how the abstract 'prepare' and 'commit' phases map to concrete 'reservation' and 'assignment' actions. Schema and Architecture (07:25 - 19:43): Pay close attention to the database schema design, especially the is_reserved flag, and how the Order Service acts as the coordinator making HTTP calls. Code Walkthrough (23:56 - 35:28): Focus on how the participant services (Delivery and Store) use SQL transactions and exclusive locks (SELECT ... FOR UPDATE) to atomically implement the reserve (prepare) step. This is a direct implementation of the 'promise' we discussed earlier.

This video nicely demonstrates how the is_reserved flag in the database is the physical manifestation of a resource being locked during the prepare phase, awaiting the final decision from the coordinator.

Test your understanding!

Imagine a participant in a 2PC transaction successfully completes its local work, writes a READY record to its log, and sends VOTE-COMMIT to the coordinator. Immediately after sending the vote, the participant node crashes and reboots. It has not yet received the final decision from the coordinator.

What should the participant do upon recovery? Can it decide to abort the transaction to free up its local resources? Why or why not?

Show answer

Upon recovery, the participant must check its log. It will find the READY record for the transaction. This means it made a promise to the coordinator that it is able to commit.

Therefore, it cannot unilaterally decide to abort. Aborting would break its promise and could lead to an inconsistent state if the coordinator had already decided to commit based on all participants' votes.

The participant is blocked. It must wait until it can contact the coordinator (or another participant, in some variants) to find out the final outcome of the transaction. The log is crucial because it reminds the recovered node of the promise it made before crashing.


The Achilles' Heel of 2PC: Blocking

The test-your-understanding question hints at the biggest weakness of the standard Two-Phase Commit protocol: it is a blocking protocol.

What happens if the Coordinator crashes?

  • Case 1: Coordinator crashes before sending PREPARE messages. No problem, the transaction hasn't started. It can be aborted.
  • Case 2: Coordinator crashes after sending its final decision. Also no problem. When the coordinator recovers, it can read its decision from its log and re-send it to any participants who didn't ACK.
  • Case 3 (The Bad One): Coordinator crashes after receiving votes but before writing and sending its final decision. In this scenario, all the participants who voted VOTE-COMMIT are now stuck. They have promised to commit and have locked resources, but they have no way of knowing the final decision. They are blocked until the coordinator recovers. If the coordinator is gone for good, these resources could be locked indefinitely, requiring manual intervention from a database administrator.

This blocking problem is a major trade-off for the consistency that 2PC guarantees.


Conclusion

In this lesson, we dissected the Two-Phase Commit protocol, the classic algorithm for ensuring atomic commitment in distributed systems.

Key Takeaways:

  • Goal: To ensure a transaction spanning multiple nodes is atomic—all nodes either commit or all nodes abort.
  • Roles: A central Coordinator drives the protocol, and multiple Participants carry out the work.
  • Phase 1 (Prepare): The Coordinator asks participants to prepare. Participants do the work, write their changes to a durable log, and vote COMMIT or ABORT. A COMMIT vote is a binding promise.
  • Phase 2 (Commit): The Coordinator makes a decision (commit only if all voted to commit). It logs the decision and broadcasts it. Participants then execute the decision.
  • Implementation: In practice, the "prepare" phase often translates to reserving resources with locks (e.g., SELECT ... FOR UPDATE), while the "commit" phase involves finalizing the state changes.

Preview of the Next Lesson

We've just introduced the critical flaw in 2PC: the potential for blocking when the coordinator fails. In our next lesson, we will analyze these failure and blocking scenarios in the 2PC protocol in more detail. We'll explore the real-world impact of blocking on system availability and briefly touch upon alternative protocols, like Three-Phase Commit, that were designed to address this very issue.