Skip to content

Phase 6: Anti-Entropy Repair, Hinted Handoff & Distributed Safety

Audience: Principal Distributed Systems Architect & High-Availability Engineer
Source References: src/java/org/apache/cassandra/repair/, src/java/org/apache/cassandra/hints/, src/java/org/apache/cassandra/service/paxos/


1. Anti-Entropy Repair & Merkle Trees

Because writes in Cassandra can succeed with LOCAL_QUORUM without waiting for all replicas, network blips, disk timeouts, and transient down nodes cause replicas to drift over time.

Anti-Entropy Repair (nodetool repair) is the background distributed process that synchronizes all replicas to 100% data parity.

Anti-Entropy Merkle Tree Diffing:
               [ Root Hash: 0x8F9B ]
                  /              \
         [ Left: 0x4A11 ]      [ Right: 0x9B2C ]
           /         \            /         \
      [Leaf 1]     [Leaf 2]   [Leaf 3]    [Leaf 4]  <--- Hashes of Token Sub-ranges

1.1 The Merkle Tree Generation Protocol

  1. The repair coordinator assigns a token range to be repaired (e.g. [Token A, Token B)).
  2. All participating replicas scan their local SSTables and construct an in-memory cryptographic binary hash tree (Merkle Tree) of depth 15 (\(2^{15} = 32,768\) discrete sub-ranges).
  3. The root hashes are compared:
  4. If Root A == Root B: The datasets are mathematically identical! Zero network data transfer required.
  5. If Root A != Root B: The coordinator traverses down the tree branches in \(O(\log N)\) steps until it isolates the exact microscopic token range with mismatched hashes.
  6. The missing data for only that specific token range is streamed across the network using Netty zero-copy streaming.

1.2 Enterprise Repair: Full vs. Incremental

  • Full Repair (nodetool repair -full): Scans all SSTables on disk, constructs trees, and streams deltas. Heavy I/O; run weekly.
  • Incremental Repair (nodetool repair -inc): Segments SSTables into "repaired" vs "unrepaired" sets, scanning only newly created SSTables.
  • Cassandra Reaper: Open-source industry standard tool to orchestrate small sub-range repairs continuously, preventing I/O spikes.

2. Hinted Handoff Mechanics: Surviving Transient Outages

When a client writes data with a consistency level like ONE or LOCAL_QUORUM, what happens if one of the natural replicas is dead or unreachable?

┌────────────────────────────────────────────────────────┐
│ Coordinator Node Receives Write for 3 Replicas        │
├────────────────────────────────────────────────────────┤
│ Replica A: Active  ──► Write succeeds                  │
│ Replica B: Active  ──► Write succeeds                  │
│ Replica C: DEAD    ──► Coordinator saves a "Hint"!     │
└───────────────────────────────────┬────────────────────┘
                                    │
                                    ▼
┌────────────────────────────────────────────────────────┐
│ Local Hints Storage (/var/lib/cassandra/hints/)        │
├────────────────────────────────────────────────────────┤
│ Mutation payload serialized to local disk with:        │
│ - Target Node UUID (Replica C)                         │
│ - Original Timestamp                                   │
│ - TTL expiration window (default: 3 hours)             │
└────────────────────────────────────────────────────────┘

2.1 The Replay Lifecycle

  1. The coordinator detects that Replica C has returned to life (via Gossip HeartBeatState).
  2. A background thread pool (HintsDeliveryExecutor) opens the local hint file and streams the buffered mutations to Replica C.
  3. Replica C applies the mutations to its local Memtable and CommitLog using their original timestamps, maintaining chronological ordering.

2.2 Hint Failure Invariants

  • max_hint_window_in_ms (Default: 3 Hours): If Replica C stays down for longer than 3 hours, the coordinator stops creating hints to prevent local disk exhaustion.
  • Once Replica C revives after 3 hours, it is missing data and MUST be healed using nodetool repair.
  • Hints do NOT count toward satisfying the client's Consistency Level (except for ANY).

3. Paxos & Lightweight Transactions (LWT)

Standard CQL writes do not lock data and follow Last-Write-Wins (LWW). When an application requires Linearizable Consistency (e.g. INSERT ... IF NOT EXISTS), Cassandra invokes a 4-phase Paxos consensus protocol.

4-Phase Paxos Protocol over the Storage Engine:
Phase 1: PREPARE (Ballot Generation)
Coordinator ──► Sends Prepare(ballot) to all natural replicas.
Replicas    ──► Promise: "I will reject any ballots older than this ballot."
                Returns previously accepted proposal (if any).

Phase 2: PROPOSE (Condition Evaluation)
Coordinator ──► Evaluates condition (e.g., IF balance >= 100).
                If satisfied, sends Propose(ballot, new_mutation).

Phase 3: ACCEPT (Consensus Commitment)
Replicas    ──► Accept mutation and persist state in system.paxos table on disk.
                Returns Ack to Coordinator.

Phase 4: LEARN / COMMIT (Data Materialization)
Coordinator ──► Sends Commit to all replicas.
Replicas    ──► Writes mutation to normal table Memtable/CommitLog.

3.1 The Performance Tax of LWT

  • A normal Cassandra write requires 1 round-trip network hop to replicas.
  • A Paxos Lightweight Transaction requires 4 round-trip network hops and multiple disk writes to system.paxos.
  • Under high concurrent contention on the same partition key, Paxos ballots conflict, forcing exponential backoff retries. Use LWT judiciously for critical identity/fencing operations, not high-velocity streaming data!