Patterns of Distributed Systems | Distributed Transactions

Patterns of Distributed Systems

Based on: Catalog of Patterns of Distributed Systems by Unmesh Joshi (published on martinfowler.com, Nov 2023)


Table of Contents

  1. Group H — Distributed Transactions

Group H — Distributed Transactions

Sometimes you need to update data on multiple nodes atomically — all updates succeed or all fail.


H1. Two-Phase Commit

The classic protocol for atomic distributed transactions.

Problem

A transaction updates data on two or more nodes (e.g., debit $100 from Account A on Node 1, credit $100 to Account B on Node 2). If Node 2 crashes after Node 1 debits, you have inconsistent state: money vanished.

You need atomicity across nodes: either both updates commit, or neither does.

Solution

Divide the commit into two phases, coordinated by a transaction coordinator:

Phase 1 — Prepare (Voting): The coordinator asks each participant: "Are you ready to commit?" Each participant:

  • Acquires locks on affected data
  • Writes the proposed changes to its WAL (but does NOT commit yet)
  • Votes YES (ready) or NO (cannot commit)

Phase 2 — Commit or Abort:

  • If all participants voted YES → coordinator sends COMMIT to all; participants commit and release locks
  • If any participant voted NO (or timed out) → coordinator sends ABORT to all; participants roll back

How it works

Coordinator: "Prepare tx-123: debit A=$100, credit B=$100"
  Node 1 (has Account A): locks A, writes to WAL, votes YES
  Node 2 (has Account B): locks B, writes to WAL, votes YES

Coordinator: all YES → "Commit tx-123"
  Node 1: commits → Account A -= $100, releases lock
  Node 2: commits → Account B += $100, releases lock
  Transaction coordinator: writes commit record

--- If Node 2 votes NO ---
Coordinator: "Abort tx-123"
  Node 1: rolls back WAL changes, releases lock
  Node 2: rolls back WAL changes, releases lock

The blocking problem: If the coordinator crashes after Phase 1 but before Phase 2, participants are stuck holding locks — they voted YES but don't know if the coordinator decided to commit or abort. This is the fundamental limitation of 2PC.

Real-world analogy

A marriage ceremony. The officiant (coordinator) asks each party "Do you take...?" (Phase 1 — prepare). Only after both say "I do" does the officiant declare "I now pronounce you married" (Phase 2 — commit). If anyone says "No," the ceremony is cancelled.

Example systems

XA transactions (JTA in Java EE), distributed SQL databases (CockroachDB, Spanner use 2PC variants), some message queues

Trade-offs

Pro Con
Atomic updates across multiple nodes Blocking protocol — coordinator failure leaves participants locked
Well-understood, widely implemented Latency: 2 round trips minimum
Works with heterogeneous systems (via XA) Locks held for the entire 2PC duration — poor for high contention
Not fault-tolerant on its own — needs a coordinator that can recover

Mitigations for blocking:

  • Use a persistent coordinator that can recover from WAL
  • Use Consistent Core + Replicated Log as the coordinator for fault tolerance
  • Use saga patterns or eventual consistency instead for long-running transactions

→ Write-Ahead Log (both coordinator and participants log their state) → Consistent Core (often hosts the coordinator for fault tolerance) → Clock-Bound Wait (Spanner uses this to order 2PC transactions)


Post a Comment

0 Comments