Two-Phase Commit

Two-phase commit is a protocol that makes several independent resources commit or abort together. A coordinator asks each participant to prepare; each answers only after it can guarantee it is able to commit; and if all of them agree, the coordinator tells them all to commit. The two phases are a promise and its fulfilment, and the split is what makes the outcome the same everywhere.

It is not a theoretical construct. Databases implement it, and PostgreSQL’s description of the prepare step is a precise statement of what “prepare” means: the transaction “is no longer associated with the current session; instead, its state is fully stored on disk, and there is a very high probability that it can be committed successfully, even if a database crash occurs before the commit is requested.”

The phrase “very high probability” rather than certainty is worth noticing. Preparing is a strong promise, not an absolute one.

What the prepared state costs

Between prepare and commit, a participant is in an unusual condition: it has given up its own right to decide. From the issuing session’s point of view, preparing “is not unlike a ROLLBACK command” — the effects are no longer visible, though they “will become visible again if the transaction is committed.” Meanwhile the decision belongs to whoever holds the coordinator’s role, and “COMMIT PREPARED or ROLLBACK PREPARED,” and those commands “can be issued from any session, not only the one that executed the original transaction.”

The documentation’s caution names the price in concrete terms.

It is unwise to leave transactions in the prepared state for a long time. This will interfere with the ability of VACUUM to reclaim storage, and in extreme cases could cause the database to shut down to prevent transaction ID wraparound… Keep in mind also that the transaction continues to hold whatever locks it held.

That last sentence is the cost: a prepared transaction keeps its locks until it is told the answer, and the thing that tells it is a coordinator that can itself fail. The intended behaviour, as documented, is that “a prepared transaction will normally be committed or rolled back as soon as an external transaction manager has verified that other databases are also prepared to commit”; the word doing the work there is normally.

Two clarifications matter here, because this is where descriptions of 2PC usually go wrong in opposite directions.

What the locks block is narrower than “nobody can touch the row.” In PostgreSQL, “row-level locks do not affect data querying; they block only writers and lockers to the same row.” An ordinary read still gets a visible version under MVCC. What waits is a conflicting write — or more, where the transaction also holds a table-level lock.

And a dead coordinator is not a call for human judgment. A transaction manager writes its decision durably before phase two and replays it on restart. Narayana’s description is representative: after a machine or network failure “the recovery will not take place until the system or network are restored, but the original application does not need to be restarted,” and “recovery responsibility is delegated to The Recovery Manager,” working from state that “is held in the ActionStore” — with the corollary that “if the ObjectStore is destroyed or modified, recovery may not be possible.”

Manual resolution is the exception, not the norm, and it is dangerous where it is not required. If the coordinator recorded commit and one participant committed before the crash, aborting another participant by hand breaks exactly the atomicity the protocol had already secured. Intervening is for cases where the log is genuinely unavailable or a heuristic outcome has already occurred.

What is unavoidable is the blocking window. Until the coordinator and its log return, “resources affected by a transaction that was in progress at the time of the failure may be inaccessible” — reported by databases as rows held by “in-doubt transactions.” The duration is set by how fast the coordinator recovers, which is why the real requirement is that the coordinator and its store have an owner, monitoring, and a tested recovery path.

Which is why the feature comes with an unusually direct warning about who should use it: “its purpose is to allow an external transaction manager to perform atomic global transactions across multiple databases or other transactional resources. Unless you’re writing a transaction manager, you probably shouldn’t be using PREPARE TRANSACTION.” And an operational default — if no such manager exists to close prepared transactions out promptly, “it is best to keep the prepared-transaction feature disabled by setting max_prepared_transactions to zero,” so that forgotten prepared transactions cannot accumulate.

There are also structural limits worth knowing before designing around it. A transaction that has touched temporary tables, created a cursor WITH HOLD, or used LISTEN, UNLISTEN, or NOTIFY cannot be prepared, because those features are tied to the session the prepared transaction is being detached from.

Why enterprise integration cannot use it

The protocol is sound and the implementations are real, so the reason it is absent from most cross-system designs is not that it fails to work. It is that three conditions rarely hold at once.

  • Every participant must support it and be willing to enlist. A payment provider, a partner’s order system, or a SaaS application will not join your transaction, and no amount of design makes it.
  • The window must be short. Locks are held for the duration of the protocol, so a participant that answers twice a day would hold them for hours. Any step involving human approval, batch processing, or a physical action is disqualifying.
  • Someone must own the coordinator and its log. Replaying the decision is automatic; making sure the manager restarts, its store survives, and in-doubt transactions are noticed is not. Without that owner the blocking window is unbounded in practice.

Where the conditions do hold — two databases you fully control, inside one boundary, with sub-second transactions and a transaction manager someone is responsible for — two-phase commit is the right answer, and reaching for a saga instead means taking on compensation logic to solve a problem the database was prepared to solve for you. The mistake runs in both directions.

Where they do not, what you give up is atomicity across systems, and what you accept in its place is a process whose intermediate states are visible and whose reversal is a compensating transaction rather than a rollback. That is the trade the rest of the design is about — including the fact that a stalled coordinator and a stalled saga are both a partial failure that has to be resolved, automatically where possible and by a person where not. And note what abandoning atomicity does not hand you: eventual consistency is a guarantee with preconditions — durable propagation, a conflict rule, and detection and repair of what was missed — so giving up 2PC creates the obligation to build those rather than satisfying it.

A name used for something else

One clarification, because the phrase travels. Stream processing engines describe a sink as doing “two-phase commit” when it writes output inside a transaction that is prepared as records are processed and committed only once the corresponding checkpoint succeeds — the two-phase commit sink. The mechanism borrows the prepare-then-commit shape, and the problem is different: there the goal is that a destination never holds output from an attempt that was later rolled back, with the engine’s checkpoint playing the coordinator’s part for one writer.

This entry is about the other thing — an atomic commit protocol across several independent resource managers, coordinated by something outside all of them. When you read “2PC,” it is worth checking which of the two is meant.

Reference: PostgreSQL documentation, PREPARE TRANSACTION (version 18, checked September 2026); PostgreSQL Documentation, Explicit Locking — Deadlocks.


Discover more from Insightful Data Lab

Subscribe to get the latest posts sent to your email.

Similar Posts

Questions, corrections, or additional insights?

This site uses Akismet to reduce spam. Learn how your comment data is processed.