Read Repair, Anti-Entropy, and Merkle Divergence Checks

LESSON

Consistency and Replication

008 30 min intermediate

Read Repair, Anti-Entropy, and Merkle Divergence Checks

By the end of this lesson, you will be able to...

  • Trace how a foreground read can find and repair a stale replica.

  • Explain why background anti-entropy is still required for cold data.

  • Use a Merkle-tree comparison to locate a mismatched range without copying every key.

Idea in one sentence: Replicas converge because repair mechanisms find disagreement and move resolved versions, not because time passing somehow makes copies agree.

Core Insight

After Harbor Point used sloppy quorum during a zone outage, watchlist:trader-17 reached fallback nodes. Hinted handoff returned most copies home. One replica is still stale, however, and an old dashboard preference has not been read at all since the outage.

It is tempting to call both records “eventually consistent” and wait. But waiting does not compare versions, send data, or resolve a conflict. The system needs work that detects divergence and a rule for what version can be copied.

Two repair paths divide that work. Read repair uses a foreground read that already found disagreement. Anti-entropy compares replica state in the background, so cold records do not wait forever for a user to visit them. They complement each other; neither is a generic replacement for the other.

The Situation

The home replicas for one hot watchlist should contain the same resolved version:

A: v7
B: v7
C: v5

The value v7 is known to supersede v5 in this teaching trace. That assumption matters. If v7 and another version were concurrent, the system would need a conflict policy before copying either one. Repair is not permission to erase an unknown user intent.

Harbor Point also has a cold key:

layout:trader-17:legacy-view

A: v12
B: v12
C: v9

No client has opened that layout for a month. The hot and cold keys need different ways to become visible to the repair system.

The Initial Model

The first reasonable idea is: “A quorum read already sees several replicas, so it will keep all replicas current.” It works for a key that is read often and whose read path compares versions.

It fails for the cold layout. No read means no comparison. Even for the hot key, a read might return the newest resolved value without writing it back, depending on the system's policy. The client gets a correct answer, while the stale replica remains a future risk.

The missing model is that detecting, deciding, and copying are distinct steps. A repaired cluster needs all three.

The Mechanism Step by Step

Plain meaning:

When a read discovers a replica is behind, return the resolved answer and ask that replica to catch up.

In this scenario:

A client reading the hot watchlist receives v7 from A and B, and v5 from C. The coordinator can identify C as stale and send it v7.

Technical name:

This foreground path is read repair. It heals disagreement revealed by useful client traffic.

1. client -> coordinator: GET watchlist:trader-17
2. coordinator -> A, B, C: read version and value

   A returns v7
   B returns v7
   C returns v5

3. coordinator compares version ancestry
   v7 supersedes v5 in this example

4. coordinator -> client: return resolved v7
5. coordinator -> C: repair with v7
6. C confirms the repair

The client should not have to wait for every repair action if the service contract permits asynchronous repair. The system must still make the result explicit: it returned v7 because version comparison established that v5 was older, not because a majority vote makes arbitrary data true.

If the repair write fails, the read can remain correct while C stays stale. That is why repair outcomes and retries belong in operational monitoring, rather than being hidden behind a successful client response.

The service also chooses whether a read waits for the repair acknowledgement. Waiting gives stronger evidence that C caught up, but adds tail latency to the user request and can make a slow replica dominate a healthy read. Sending repair asynchronously protects the foreground path, but leaves a measured convergence window. The right choice depends on the endpoint's freshness contract; neither mode turns a failed repair into success by wording alone. The API should state that boundary.

A Worked Anti-Entropy Trace

Read repair does nothing for the cold layout until someone reads it. Anti-entropy creates a background comparison schedule instead. Comparing every value in every replica range would be expensive, so a common approach summarizes ranges with a Merkle tree: leaves hash individual key values and each parent hashes its children.

The following hashes are illustrative.

Replica A, range layout:0000..9999
root=aa9
├── 0000..4999 = 51c
└── 5000..9999 = 7d2
    ├── 5000..7499 = 2f1
    └── 7500..9999 = 0c9

Replica C, same range
root=be4
├── 0000..4999 = 51c
└── 5000..9999 = 46e
    ├── 5000..7499 = 2f1
    └── 7500..9999 = 913

The comparison follows only the evidence of mismatch:

1. Compare roots: aa9 != be4, so the whole range is not identical.
2. Compare left children: 51c == 51c, so skip keys 0000..4999.
3. Compare right children: 7d2 != 46e, so descend.
4. Compare 5000..7499: 2f1 == 2f1, so skip it.
5. Compare 7500..9999: 0c9 != 913, so exchange narrower summaries or keys.
6. Find layout:trader-17:legacy-view and resolve v12 over v9.
7. Copy the resolved v12 to C and record success or retry work.

The tree does not tell the replicas which user value is correct. It tells them where their state differs. Version ancestry or a conflict resolver must still decide what to synchronize. Dynamo's anti-entropy design uses Merkle trees to reduce the data and disk work needed to find inconsistent ranges.

There is one more practical detail. The range can change while the replicas compare it. A repair pass therefore needs a stable comparison boundary: for example, a range generation, a snapshot position, or a repeated comparison after a changed root. Otherwise A might hash one view of the range while C is already repairing a newer view, and the pass could wrongly report that the work is complete.

comparison start: root(A)=aa9, root(C)=be4, generation=104
repair writes v12 to C
recheck: root(A)=aa9, root(C)=aa9, generation=104 -> range converged

if generation changes during the pass:
  record incomplete comparison
  schedule the changed range again

The exact implementation varies. The teaching point is stable: a Merkle comparison is evidence about a particular replica state, not a permanent certificate. Good background repair records what range and version it checked, retries incomplete work, and limits concurrency so several large scans do not consume the same disk and network budget as foreground traffic.

So far: read repair gives hot keys quick, opportunistic healing. Merkle-based anti-entropy gives cold data a deliberate route back to convergence.

What This Changes

Before this model, a team may say “the store repairs itself.” After it, the team can state two separate obligations:

Data pattern Detection path Repair consequence
Hot watchlist Quorum or multi-replica read Low-latency foreground detection; repair may add read or write work
Cold preference Scheduled range comparison Background coverage even with no client traffic
Concurrent updates Version ancestry detects no ancestor relation Conflict policy must resolve, preserve siblings, or request retry before copying

The important decision is not merely enabling a feature. It is setting a convergence objective that matches the data's contract. A watchlist might need to converge minutes after a replica returns. An archival preference may tolerate hours. An audit export may require a complete, verified view rather than a best-effort background repair window.

The Trade-off, Limits, and Signals

Repair improves replica agreement. The trade-off is where it spends work. Read repair adds comparison and sometimes writes to a user-facing read path. Anti-entropy adds scheduled CPU, disk reads, hashes, network transfer, and repair traffic even when no user asks for the data.

Signal What it reveals
Read-repair discovery and failure rate How often foreground reads find drift and whether the write-back succeeds
Anti-entropy backlog and scan age Whether cold ranges receive the promised coverage
Oldest unresolved range or hint The longest current convergence delay
Repair bytes and disk utilization The resource cost of making copies agree
Conflict or sibling count Whether divergence is stale data or unresolved user intent

Aggressive repair shortens divergence windows but can compete with client traffic. Conservative repair protects foreground latency but lets stale replicas survive longer. Neither mechanism protects a business invariant that cannot tolerate a temporary conflicting write in the first place; that invariant needs a stronger write contract or a domain-specific conflict policy.

Common Confusions

Confusion: Eventual consistency means replicas converge without an active mechanism.

Why it is tempting: “Eventually” sounds like a time-based promise.

Better model: Time only provides an opportunity. A system must discover divergence, choose a resolved version, and transfer it.

Confusion: Read repair covers the entire dataset.

Why it is tempting: A successful hot-key read can visibly heal a replica.

Better model: Read repair follows reads. Cold data needs scheduled anti-entropy or another background reconciliation path.

Confusion: Matching a Merkle root proves a system has no data problem.

Why it is tempting: The compared range matches at that time.

Better model: It only establishes agreement for the compared representation and range. It does not validate a bad conflict policy, an omitted range, or a value that was consistently wrong everywhere.

Check Your Understanding

Check: A client read returns v7 from two replicas and v5 from one replica. The version metadata proves v7 descends from v5. What is the correct repair action, and what uncertainty remains if the repair write times out?

Think first, then reveal.

Answer: Return resolved v7 and attempt to copy it to the v5 replica. If the repair write times out, the client can still have received the correct resolved value, but the system cannot assume the stale replica caught up; it must retry or let later repair find it again.

Check: Two Merkle roots differ, but the left child hashes match. What should the replicas avoid doing?

Answer: They should avoid transferring the entire range or rechecking all left-side keys. The matching child proves that subtree can be skipped for this comparison.

Practice: Design a Repair Objective

An analyst dashboard uses a replicated daily-exposure record. It is read every few minutes during trading hours, but an end-of-day compliance export must not omit an acknowledged correction. A zone recovery leaves one replica stale.

Define the read-repair behavior, anti-entropy objective, and export behavior. Include one signal and one conflict boundary.

A good answer should mention:

Connections

Resources

Key Takeaways

PREVIOUS Leaderless Replication, Sloppy Quorums, and Hinted Handoff NEXT Conflict Resolution and Convergence Policies