DISTRIBUTED SYSTEMS / SYSTEM CONCEPT BRIEF

Distributed transactions

A distributed transaction updates data in multiple databases or services atomically: all changes commit or none do.

AdvancedPhase 05 / Topic 17 of 17RequirementsTrade-offsFailure modes
01

Overview

A distributed transaction updates data in multiple databases or services atomically: all changes commit or none do. The classic protocol is two-phase commit (2PC): a coordinator asks all participants to prepare (lock and promise to commit), and if all vote yes, tells them to commit. 2PC is correct but blocking: if the coordinator fails after prepare, participants hold locks until it recovers.

In microservices, 2PC is usually avoided in favor of sagas (a sequence of local transactions with compensating actions), the transactional outbox pattern (publish events reliably with the local transaction), and idempotent consumers. Distributed SQL databases (Spanner, CockroachDB) implement distributed transactions internally with consensus to avoid 2PC's blocking problem.

A wedding ceremony

The officiant (coordinator) asks each partner 'do you?' (prepare). Only if both say yes are they pronounced married (commit). If one says no, nobody is married (abort). If the officiant faints after both said yes, everyone waits.

02

When to use it

  • Operations spanning multiple databases or services.
  • Choosing between 2PC, sagas, and outbox patterns.
  • Keeping a database write and an event publish consistent.
  • Interview questions about order, payment, and inventory flows.
03

Where it shows up in interviews

Cross-service consistency

Recognize it when: place order, charge payment, reserve stock.

  • Design an e-commerce checkout
  • Design a travel booking system
Dual write problem

Recognize it when: update the DB and publish an event.

  • Design an event-driven order service
  • Design a CDC pipeline
04

Where it is used in real software

XA transactions

Java EE and many databases support XA 2PC across resources, but it is rarely used in cloud-native systems.

Google Spanner

Uses 2PC across Paxos groups, so participants are themselves replicated and the protocol does not block on single failures.

Transactional outbox with Debezium

Services write events to an outbox table in the same transaction; Debezium streams them to Kafka.

05

Key terms

Two-phase commit
Prepare phase, then commit or abort phase, driven by a coordinator.
Saga
Local transactions with compensating actions on failure.
Transactional outbox
Store events in the same DB transaction and publish them asynchronously.
Compensation
An action that semantically undoes a previous step (refund).
In-doubt transaction
A participant that prepared but does not know the outcome.
06

How it works, step by step

  1. 1
    Coordinator sends PREPARE

    Participants lock resources and write a prepare record.

  2. 2
    Participants vote

    YES (can commit) or NO.

  3. 3
    Coordinator decides

    All YES: commit; any NO or timeout: abort. Decision is logged durably.

  4. 4
    Coordinator sends the decision

    Participants commit or roll back and release locks.

  5. 5
    Recovery

    In-doubt participants ask the coordinator for the outcome after failures.

Two-phase commit
Step 1 / 4
Coordinator
Orders DB
Payments DB
Inventory DB

STEP 1Phase 1: the coordinator sends PREPARE to all three participants.

07

2PC vs saga vs outbox

Choosing a cross-service consistency pattern

Step 1 / 4
PatternConsistencyAvailabilityBest for
2PCAtomicBlocks on coordinator failureFew databases in one trusted environment
SagaEventual, with compensationsHighLong-running business flows across services
Transactional outboxDB write and event are atomicHighReliable event publishing
Distributed SQLAtomic (internally)High (consensus)Single logical database at scale

NOWPattern: 2PC | Consistency: Atomic | Availability: Blocks on coordinator failure | Best for: Few databases in one trusted environment

Microservices usually combine sagas for business flows with the outbox pattern for each local step.

08

Implementation

// Write business data and the event atomically in one local transactionexport async function placeOrder(order: NewOrder) {  await db.transaction(async (tx) => {    const { id } = await tx.one("INSERT INTO orders (user_id, total_cents, status) VALUES ($1, $2, 'pending') RETURNING id",      [order.userId, order.totalCents]);    await tx.none("INSERT INTO outbox (aggregate_id, type, payload) VALUES ($1, 'OrderPlaced', $2)",      [id, JSON.stringify({ orderId: id, ...order })]);  });} // Relay (or Debezium CDC) publishes outbox rows to Kafka, then marks them sentexport async function relayOutbox() {  const rows = await db.any("SELECT * FROM outbox WHERE sent_at IS NULL ORDER BY id LIMIT 100 FOR UPDATE SKIP LOCKED");  for (const row of rows) {    await kafka.send({ topic: "orders", messages: [{ key: row.aggregate_id, value: row.payload, headers: { eventId: String(row.id) } }] });    await db.none("UPDATE outbox SET sent_at = now() WHERE id = $1", [row.id]);  }}
09

Complexity and performance

2PC messages~4n

prepare, vote, commit, ack per participant.

2PC latency2 RTTs + fsyncs

Locks held throughout.

10

Trade-offs

Atomicity vs availability

2PC gives atomic commits but blocks and reduces availability; sagas stay available but expose intermediate states.

Complexity location

Sagas move consistency logic (compensations, idempotency) into application code.

11

Variants and related techniques

Three-phase commit

Adds a pre-commit phase to reduce blocking; rarely used due to partition issues.

TCC (Try-Confirm-Cancel)

Reserve resources, then confirm or cancel; a business-level 2PC.

12

Common mistakes

  • Dual writes: update the DB then publish to Kafka separately.

    Fix: One can fail; use the outbox pattern or CDC.

  • Using 2PC across microservices owned by different teams.

    Fix: Tight coupling and blocking; prefer sagas.

  • Compensations that are not idempotent.

    Fix: Compensations are retried too; make them idempotent.

13

Interview questions

Why avoid 2PC in microservices?

It couples services' availability together, holds locks across network calls, and blocks if the coordinator fails after prepare. Sagas with compensations and the outbox pattern give eventual consistency with higher availability.

How do you reliably update a database and publish an event?

Transactional outbox: insert the event into an outbox table in the same local transaction as the business change, then a relay or CDC process publishes it to the broker and consumers deduplicate by event ID.

14

Practice problems

ProblemDifficultyWhat it trains
Implement the outbox patternMediumAtomic event publish.
Design checkout across order, payment, inventory servicesHardSaga vs 2PC.