What is Two-Phase Commit? (2PC)

By Kaden Sungbin Cho
Picture of the author
Published on
two phase commit flow

Two-phase commit is an algorithm (or protocol) to achieve atomic transaction commit on multiple nodes. It is used in distributed databases to ensure that all nodes commit equally or fail for a transaction. 2PC is used inside some databases or is also provided in the form of applications such as XA transaction (supported by Java transaction API) and WS-AtomicTransaction for SOAP.

In this article, we will look into the following points regarding 2PC.

  • Background of the birth of 2PC
  • What is 2PC?
  • Vulnerability of 2PC

Birthplace of 2PC

The database provides atomic transactions to prevent situations where only part of a multi-object transaction is reflected in the database and part of it fails.

In databases running on a single node, this atomicity is usually implemented by the storage engine. When a client requests a commit to a database node, the database ensures that the transaction can be durably written (usually via a write-ahead log). If a failure occurs during this process, the transaction is recovered from the log when the node restarts, and if the commit record was successfully written to disk before the failure, it is considered a commit success, otherwise it is considered a commit failure and a rollback is performed.

So on a single node, transaction commit depends on the order in which data was written to disk. The success of the transaction is determined at the moment the commit record is written to disk.

So how are these atomic transactions supported in a distributed data store rather than a single node? Most NoSQL does not support such distributed transactions, but various cluster systems do. If distributed transactions are supported only in a single node manner in this cluster system, the following problems may occur:

  • In some nodes, a constraint violation is detected and a commit failure occurs, but in some nodes, the commit is successful.
  • The commit request is lost on the network, and commit failure occurs due to a timeout, but is successfully delivered to some nodes and successfully committed.
  • On some nodes, the commit record goes down before it is completely written, but on some nodes it is successfully committed.

If only some commit transactions and others do not, the nodes become inconsistent with each other. Once a commit has occurred, a transaction cannot make any further changes to that commit. That is, a node must commit only once, at the moment when it is certain that all nodes in the transaction have committed**. Otherwise, since other transactions that occur later are at the 'read committed' isolation level, incorrectly written data may be read momentarily.

What is 2PC?

2PC is basically divided into two phases as follows:

Image from Author inspired by [1]
Image from Author inspired by [1]

2PC uses a new component, the Coordinator (or Transaction Manager), which does not exist in a typical single node transaction. The coordinator is implemented in a library within the same application process that requests the transaction, but may also be a separate service or process.

A 2PC transaction begins when an application reads or writes to multiple database nodes. When the application is ready to commit, the coordinator starts phase 1 and sends a prepare request to each node to ask if it can commit. The coordinator then tracks the response of each node (called a participant):

  • If all participants respond yes, the coordinator moves to phase 2 and sends a commit request to perform the commit.
  • If any one responds no, the coordinator moves to phase 2 and sends an abort request to all nodes. In more detail, if we look at how 2PC works,
  1. When an application wants to start a distributed transaction, it asks the coordinator for the transaction ID. This transaction ID is globally unique.
  2. The application initiates a single-node transaction with each participant and passes the globally unique ID above. All reads and writes will be handled by one of these single-node transactions. If some work goes wrong at this stage, the coordinator or any of the participants can abort.
  3. When the application is ready to commit, the coordinator sends a prepare request containing the global transaction ID to all participants. If any of these requests fail or time out, the coordinator sends a transaction abort request with that transaction ID to all participants.
  4. When a participant receives a prepare request, the participant is allowed to commit the transaction under certain circumstances. This includes writing all transaction data to disk, checking for constraint violations, etc. By replying 'yes' to the coordinator, the node promises that if the transaction commits, it can be committed without any errors.
  5. Once the coordinator receives responses to all prepare requests, it decides whether to commit or abort the transaction. The coordinator records such decisions in the coordinator transaction log on the disk to prepare for a possible crash (commit point).
  6. The coordinator's decision is written to disk and a commit or abort request is sent to all participants. If a request fails or times out, the coordinator must retry until success. There are no subsequent cancellations or rejections, and if the decision was to commit, that decision must be enforced regardless of how many retries are required. If a participant goes down in the meantime, the transaction will be committed when the participant recovers.

Therefore, the 2PC protocol has two important “no-break points”: One is the part that must be committed after the participant responds yes, and the other is the part that must be enforced when the coordinator makes a decision. This promise guarantees the atomicity of 2PC.

2PC Vulnerability

We previously told you that participant downtime and network failure are overcome by forcing the coordinator to make a decision after the decision has been made. But what happens if the coordinator goes down? Participants can safely abort a transaction before sending a prepare request. However, if the coordinator goes down after the participant receives the prepare request and responds yes, the participant can no longer perform the abort alone.

Image from Author inspired by [1]
Image from Author inspired by [1]

Without additional help from the coordinator, participants have no way of knowing whether they have committed or aborted. In principle, the participants should communicate with each other to find out how each participant responded to the prepare and reach an agreement, but this part is not included in the 2PC protocol.

The only way for 2PC to be completed is to wait for the coordinator to recover. The coordinator records commit or abort decisions in a transaction log that exists on the disk. When the coordinator recovers, it reads the transaction log to restore the state.

Reference

[1] Designing Data-Intensive Applications

[2] https://en.wikipedia.org/wiki/Two-phase_commit_protocol

Join our newsletter

Stay tuned with 100+ Software engineers
Latest backend & growth trends in your mail box on every Wednesday