Two-phase commit and why services avoid it

Transactions, isolation and locking Senior Python 3.13+ PostgreSQL 17+ Reviewed By Best Latest Apps

Pick the fix

An order service must update its own Postgres and a separate inventory service's Postgres atomically. What do you recommend?

Options
Hint 1 of 1

Which option avoids holding locks across services?

Choose an answer, then open Show answer below to check it.

Show answer

Answer: A A saga: reserve inventory, create the order, compensate on failure, driven by events with an outbox

Correct. Each step is local and committed; failures run compensations instead of holding locks.

When two services own two databases, an atomic commit across both means blocking and tight coupling. A saga makes each step a local commit and undoes earlier steps if a later one fails.

Why not the others?
  • B XA two-phase commit across both databases. Couples the services and blocks if the coordinator fails.
  • C Write to both databases and hope. A crash between the writes leaves them inconsistent.
  • D Put both tables in one database owned by the order service. Breaks the inventory service's ownership of its data.

30-second answer

A coordinator asks every participant to prepare: each writes everything durably and promises it can commit. If all say yes, the coordinator tells them to commit; if any says no, all roll back. It gives atomicity across databases, but a participant that has prepared must hold its locks until it hears the decision, so a coordinator crash can block them indefinitely. Services usually prefer sagas with compensation, or an outbox plus idempotent consumers.

Practise saying it · 40 s. No recording, just a timer

Explain 2PC and its alternatives.

Say your answer out loud, then check which of these points you covered:

  • Prepare, then commit
  • Atomic across participants
  • Blocks if the coordinator dies
  • Sagas with compensation
  • Outbox plus idempotent consumers

Deep dive

PostgreSQL supports the participant side: PREPARE TRANSACTION 'id', then COMMIT PREPARED or ROLLBACK PREPARED; it's off by default (max_prepared_transactions = 0).

Forgotten prepared transactions hold locks and block VACUUM like long transactions.

2PC needs every participant to support it; HTTP APIs and most brokers don't.

Consensus protocols (Raft, Paxos) solve a different problem: agreeing on one log despite failures.

Common mistakes

  • Calling 2PC 'two-phase locking' (2PL is a concurrency-control scheme).
  • Assuming 2PC is non-blocking.
  • Using it across services owned by different teams.

Senior take & follow-ups

Explain prepare and commit, the blocking failure mode, and the saga alternative with its isolation trade-off.

Follow-up questions

Think of your answer first, then open the hint.

  1. What happens if the coordinator dies after prepare?

    Show hint

    Participants wait, holding locks, until it recovers or someone resolves them manually.

  2. What do sagas give up compared with 2PC?

    Show hint

    Isolation: other transactions can see intermediate states.

Suggest a fix if something here is wrong or out of date.