Heal on read, heal on a schedule — read repair and Merkle-tree anti-entropy

The cluster heals divergence two ways: read repair fixes hot keys inline as they're read, and anti-entropy uses Merkle-tree diffs to bulk-heal cold keys on a schedule.

Previously

Hints expire and replicas drift cold; the cluster needs a way to find and fix divergence on its own — one mechanism for hot data on every read, one for cold data on a schedule.

Scene 11

Heal on read, heal on a schedule

  1. Watch
  2. Try it
  3. Predict
  4. Capture
mode: with-merkleS0S1S2S3S4S5S6S7user-42R1R2R3READ REPAIR (per-key inline)n3 merkleRn5 merkleRrepairdiffering leaf #2 → ship only that subtreeRead repair: a single key was read, replicas disagreed, the coordinator pushed the LWW winner to the stale re…
RF=3 — three replicas of user-42, but they don't agree
read repair → coordinator pushes LWW winner to stale replica (only this key)
Merkle tree: hash of subtree → spot mismatches in O(log n)
What to watch for

Watch the read on user-42. The three replicas reply with different values; the coordinator picks the latest by timestamp and pushes the winner back to the stale replica. The Merkle inset on the right shows where the two replicas disagreed — one glowing leaf.

Implementation

Highlighted lines are the ones running in the diagram right now.

Coordinator.read_repair
heal the just-read key, then return to the client
def read_repair(key, replicas):
responses = [r.read(key) for r in replicas]
winner = max(responses, key=lambda x: x.ts)
# only the keys actually read get healed
for resp in responses:
if resp.ts < winner.ts:
send_async(resp.replica,
Write(key, winner.value, winner.ts))
return winner.value
AntiEntropy.merkle_repair
diff two replicas' trees; bulk-heal every mismatched leaf
def merkle_repair(a, b, range):
tree_a = a.build_merkle(range)
tree_b = b.build_merkle(range)
diffs = diff_subtree(tree_a.root, tree_b.root)
# diffs is the set of mismatched leaf ranges
for leaf_range in diffs:
rows_a = a.fetch(leaf_range)
rows_b = b.fetch(leaf_range)
merged = lww_merge(rows_a, rows_b)
a.bulk_write(merged)
b.bulk_write(merged)
AntiEntropy.diff_subtree
recursive descent — skip whole subtrees on hash match
def diff_subtree(node_a, node_b):
if node_a.hash == node_b.hash:
return [] # entire subtree matches; skip
if node_a.is_leaf:
return [node_a.range]
out = []
out += diff_subtree(node_a.left, node_b.left)
out += diff_subtree(node_a.right, node_b.right)
return out

Where this sits in Build a wide-column store (Cassandra / DynamoDB family)

Scene 11 of 13, in the Healing act — Hinted handoff + read repair + anti-entropy + gossip keep the cluster honest.. When a read sees disagreement, push the winner to stale replicas; on a cron, compare every key.

Up next. Reads, hints, and repairs all need the cluster to know who's alive — that membership picture is itself maintained by a quiet background protocol.

All 13 scenes in Build a wide-column store (Cassandra / DynamoDB family) · Every curriculum

Built with Arqly
Every scene in Build a wide-column store (Cassandra / DynamoDB family) builds on the one before it.All 13 Build a wide-column store (Cassandra / DynamoDB family) scenes