Hotshard
Replica convergence

Anti-Entropy & Read Repair

Leaderless replicas drift apart — the two mechanisms that pull them back together.

A leaderless, eventually-consistent store keeps N copies of every key on N different nodes. Because writes are acknowledged before every copy is updated, and because nodes crash and networks drop packets, those copies drift apart: the same key holds different values on different replicas. Left alone, that drift only grows. Two mechanisms fight it back. Read repair fixes divergence on the read path — when a quorum read notices some replicas are stale, the coordinator writes the newest value back to them. Anti-entropy is a background job that compares whole replica datasets with Merkle trees and repairs everything that differs, including keys nobody has read in months. This page is about those two mechanisms and how they complement each other. It leans on two topics you should read first: quorum replication for how a read gathers responses, and the Merkle tree for how a background sync finds the few differing keys cheaply.

~15 min read

Start here: why replicas drift#

TL;DRthe 30-second version
  • In a leaderless store, each key lives on N replicas. A write is acked after only W of them confirm it, so at that instant up to N − W replicas are already behind. Add crashes, dropped packets, and hinted handoff, and copies of the same key drift apart.
  • Without active repair the drift only grows — every missed write is a permanent difference until something copies the right value across.
  • Read repair fixes it on the read path: a quorum read sees that some replicas returned a stale (or missing) value and writes the newest value back to them. Cheap and immediate, but it only ever touches keys that are actually read.
  • Anti-entropy fixes it in the background: a periodic job compares two replicas' whole datasets using Merkle trees (see /merkle), finds the keys that differ without shipping everything, and repairs them — covering cold keys read repair never reaches.
  • Hinted handoff is the third leg: while a replica is down, a stand-in node holds its writes and replays them when it returns, shrinking how much the other two have to repair later.
  • They compose: read repair keeps hot keys fresh for free; anti-entropy guarantees full coverage on a schedule; hinted handoff limits the damage of a temporary outage. Dynamo and Cassandra run all three.

Quorum replication (see /quorum) buys availability by never waiting for every replica. A write is copied to N nodes but the coordinator returns success as soon as W of them acknowledge — it does not wait for the slow or unreachable ones. So the moment a write is reported committed, as many as N − W replicas may still be holding the old value. That is not a bug; it is the trade that keeps the system writable when a node is rebooting or across a slow link. But it means replicas of the same key are routinely out of step.

Several forces widen that gap over time. A replica can be down for a maintenance window and miss every write that lands during it. A network blip can drop the copy headed for one node while the others succeed. A sloppy quorum can accept a write on a stand-in node that is not even one of the key's real replicas. Each of these leaves at least one replica behind, and nothing in the write path ever circles back to fix it. Divergence accumulates.

Why drift can't just be ignoredEventual consistency promises that if writes stop, all replicas converge to the same value. That promise is empty unless something actively does the converging. Every stale replica is a landmine: a later read served by that replica returns old data, and a replica that dies while still stale can turn a temporary lag into permanent data loss. The overlap rule R + W > N only guarantees a read can SEE the freshest value among its responses — it does nothing to fix the replicas that missed the write. Repair is the machinery that makes 'eventual' actually arrive.

The two mechanisms: on the read, and in the background#

Convergence comes from two repair paths that cover different keys. Read repair rides along on ordinary reads and fixes whatever those reads happen to touch. Anti-entropy is a scheduled sweep that fixes everything, read or not. Neither replaces the other — read repair is fast but only sees hot keys, anti-entropy is thorough but runs on a slow cycle.

Read repair works because a quorum read is already talking to several replicas. When a read for a key arrives, the coordinator asks R replicas for that key (in practice it asks one for the full value and the others for a lightweight digest — a hash of their value — to save bandwidth). It compares what comes back. If every response agrees, it returns the value and does nothing else. If the responses disagree — a digest mismatch — the coordinator picks the newest version (by the write's version stamp), returns that to the client, and pushes it back to every replica that was stale or missing it. The read has healed the replicas it touched, for free, as a side effect of serving the read.

Coordinatorreads key K
fan out to R replicas
Replica A → v5full value
compare responses
Replica B → v3 (stale)digest mismatch
pick newest version
Return v5 to clientnewest wins
repair the laggard
Write v5 back to Bread repair
Read repair on a quorum read (N=3, R=2)

Anti-entropy takes the opposite approach: it ignores what is being read and instead compares two replicas' entire datasets directly. Shipping every key across to compare would cost as much as the data itself, so it uses a Merkle tree (covered in depth at /merkle). Each replica builds a tree of hashes over its key ranges: leaves hash buckets of keys, parents hash their children, and the single root hash fingerprints the whole range. Two replicas exchange their root hashes; if the roots match, the ranges are identical and nothing more is sent. If the roots differ, they walk down the tree, descending only into the subtrees whose hashes disagree and pruning the ones that match. In a few round-trips they have pinpointed the exact buckets that differ — without transferring the keys that already agree — and they stream only those across to reconcile. You can watch that tree-diff run step by step in the Merkle simulator (/merkle/sim).

The division of labourRead repair only ever fixes keys someone reads, so a key written once and never read again can stay diverged forever under read repair alone. Anti-entropy is exactly the backstop for those cold keys: it sweeps the full dataset on a schedule and repairs everything, read or not. Conversely, anti-entropy runs infrequently (it is expensive), so between sweeps read repair is what keeps the hot, frequently-read keys fresh in near real time. One covers latency, the other covers completeness.
Go deeperUnder the hood: blocking vs asynchronous read repair

Read repair comes in two flavours that differ in whether the client's read waits for the repair. In blocking read repair, when a quorum read finds a mismatch the coordinator repairs the involved replicas and waits for the required acks BEFORE returning to the client. This is what makes quorum reads monotonic: once one client reads the new value, the replicas that answered are updated, so a later quorum read cannot slip backwards to the old value. Modern Cassandra (4.0+) makes BLOCKING the default per-table behaviour precisely for that guarantee.

The older style was asynchronous and probabilistic: Cassandra's read_repair_chance / dclocal_read_repair_chance would, on some fraction of reads, fire a background repair across ALL replicas (not just the R that answered) without blocking the response. It improved convergence of frequently-read keys but gave no per-read guarantee and was hard to reason about, so CASSANDRA-13910 removed those knobs in 4.0. The lesson: 'repair on read' is only a real consistency tool when the read waits for it; fire-and-forget read repair is a best-effort nudge, not a guarantee.

PredictA key is written once, acknowledged by W of N replicas, and then never read again. One replica missed the write. Which mechanism eventually fixes that replica, and why can't the other?

Hint: Which mechanism is triggered by a read, and which one sweeps the whole dataset regardless of reads?

Anti-entropy fixes it; read repair cannot. Read repair only runs as a side effect of a read, and this key is never read, so read repair is never triggered for it — the stale replica stays stale indefinitely under read repair alone. Anti-entropy is the backstop: its background Merkle-tree comparison walks the entire key range on a schedule, sees that this key's bucket hashes differ between the replicas, and streams the correct value across regardless of whether anyone ever read it. This is exactly why a leaderless store needs both — read repair covers the hot path cheaply and immediately, anti-entropy guarantees eventual coverage of the cold keys read repair never touches. Hinted handoff would also have helped upstream, but only if the replica's downtime was transient and a stand-in captured the write in the first place.

What each mechanism costs#

The two mechanisms sit at opposite ends of a cost-versus-coverage curve, and the numbers explain why you run both rather than picking one.

  • Read repair cost is per read and small: the coordinator was already contacting R replicas, so detection is nearly free (compare R digests). The only extra work is the repair write itself, and only when a mismatch is found. Its coverage, though, is exactly the set of keys being read — nothing more.
  • Anti-entropy cost is per sweep and large: each replica must build a Merkle tree over its key range, which means reading and hashing the data on disk (an O(n) scan over that range). Exchanging trees and streaming the diffs is cheap relative to the build — the Merkle tree keeps transfer proportional to the number of differing keys, not the dataset — but the tree build itself is the dominant cost, which is why anti-entropy runs on a schedule (often daily or weekly), not continuously.
  • The staleness you tolerate is the dial. Sweep more often and cold keys converge faster, but you pay the tree-build scan more often (more disk and CPU competing with live traffic). Sweep less often and you save the scan but let cold divergence live longer. Read repair has no such dial — it is as fresh as your read rate for a given key.
Read repairAnti-entropy
When it runsOn every applicable read (foreground)On a schedule / on demand (background)
Keys it coversOnly keys that are readThe entire dataset in the range
Detection costCompare R digests — nearly freeBuild a Merkle tree per replica — O(n) scan
Repair costWrite the newest value to stale replicasStream only the diverged buckets (Merkle-bounded)
Freshness for a keyAs fresh as that key's read rateAs fresh as the sweep interval
Main downsideCold, unread keys never convergeExpensive tree build; runs infrequently
Merkle granularity is the hidden knobAnti-entropy's cost and precision both hinge on how many leaves the Merkle tree has. A leaf covers a bucket of many keys, so if any one key in a bucket differs, the whole bucket is marked diverged and streamed across — even the keys that already matched. More leaves means finer localization (less wasted transfer) but a bigger tree that costs more to build and hold. Cassandra caps its repair tree depth (leaves number in the tens of thousands per range) to keep the tree affordable, deliberately accepting some over-repair. This is the same leaf-granularity trade the Merkle topic covers — here it decides how much extra data a sweep ships.

How they combine — and hinted handoff, the third leg#

The reason a real system runs both mechanisms is that each fails exactly where the other succeeds. Read repair is immediate and cheap but blind to anything unread; anti-entropy is complete but slow and expensive. Run only read repair and your cold data rots. Run only anti-entropy and every read between sweeps risks stale data on hot keys that a read could have fixed instantly. Together they give you fresh hot keys and guaranteed-eventual cold keys, which is the whole promise of eventual consistency made real.

Hinted handoff is the third mechanism, and it works upstream of both — it prevents divergence rather than repairing it. When the coordinator can't reach one of a key's replicas at write time, instead of just leaving that replica behind, it stores the write on a stand-in node along with a hint recording who the write really belongs to. When the down replica comes back, the stand-in replays the buffered writes to it. If the outage was short, the replica is caught up the moment it returns, and read repair and anti-entropy have almost nothing left to fix. Hinted handoff only helps for transient failures, though — hints are held for a bounded window and then dropped, so a node down for longer than that window falls back to being repaired by anti-entropy.

  • Hinted handoff (write time): a stand-in buffers writes for a briefly-unreachable replica and replays them on its return. Prevents divergence from short outages; useless for long ones (hints expire).
  • Read repair (read time): heals whatever keys are read, immediately and cheaply. Keeps hot keys converged between anti-entropy sweeps; blind to unread keys.
  • Anti-entropy (background): the Merkle-tree sweep that guarantees full coverage on a schedule. The backstop for cold keys and for outages that outlived their hints.
Three layers, one goalThink of them as defense in depth against drift. Hinted handoff tries to stop a write from being missed at all. Read repair mops up the misses that touch the read path. Anti-entropy periodically re-checks everything to catch whatever slipped past the first two. You want all three because each is cheap exactly where it applies and useless outside its niche — and dropping any one leaves a class of divergence with nothing to fix it.

The three mechanisms side by side#

Hinted handoffRead repairAnti-entropy
RolePrevent driftRepair on the read pathRepair in the background
Triggered byA write to an unreachable replicaA read that finds a mismatchA schedule or an operator
CoverageWrites during a short outageOnly keys that get readThe entire dataset
Latency to convergeImmediate on the node's returnImmediate for read keysUp to one sweep interval
CostBuffer + replay the hintsA digest compare + a repair writeA full Merkle-tree build (O(n) scan)
Fails whenOutage outlives the hint windowThe key is never readYou need it faster than a sweep

Read them left to right as a timeline. Hinted handoff acts at write time to avoid the miss. Read repair acts at read time to fix a miss the moment it surfaces. Anti-entropy acts on its own clock to catch everything the first two didn't. They are not competing designs to choose between — a Dynamo-style store wires up all three at once, and each earns its place by covering a gap the others leave open.

Where this runs in the wild

Every mechanism here traces back to the Amazon Dynamo paper, and the Dynamo-family databases implement the same three-part scheme with local variations.

  • Amazon Dynamo (2007) — defined the combination: hinted handoff for transient failures, read repair to heal replicas on the read path, and a Merkle-tree anti-entropy protocol so replicas synchronize by exchanging trees and comparing branches independently, transferring only the diverged ranges.
  • Apache Cassandra — read repair is per-table (read_repair = BLOCKING by default since 4.0; the old probabilistic read_repair_chance knobs were removed). Anti-entropy is `nodetool repair`, which builds Merkle trees over token ranges and streams the differences; because it is expensive and manual, operators schedule it with a tool like Cassandra Reaper to run continuously in small slices.
  • Amazon DynamoDB — the managed descendant hides these mechanisms behind its API, but the same continuous background replica synchronization and read-time repair keep its replicas converged; the user only sees 'eventually consistent' vs 'strongly consistent' reads.
  • Riak — stayed close to classic Dynamo with read repair, active anti-entropy (a background Merkle-tree process it calls AAE), and hinted handoff, plus version vectors and CRDTs to resolve the conflicts repair surfaces.
Repair finds divergence; something else resolves itThese mechanisms locate replicas that disagree and copy a value across — but 'which value is newest?' is a separate question. Cassandra answers it with last-write-wins by timestamp; Riak and classic Dynamo use version vectors to detect concurrent writes and either keep both as siblings or merge them with CRDTs. Repair is the transport; conflict resolution is the policy it carries. See /quorum for how a read picks the winning version, and /gossip/sim for how replicas exchange the membership and state that drives all of this.
Common misconceptions & gotchas
Doesn't read repair alone keep replicas consistent?

Only for keys that are read. Read repair is triggered by a read, so a key that is written and then never read again gets no read repair at all — its stale replica stays stale indefinitely. That is the whole reason anti-entropy exists: it sweeps the entire dataset on a schedule and repairs cold, unread keys that read repair can never see. Skipping anti-entropy is a classic operational mistake that lets silent divergence pile up on rarely-read data.

Why not just run anti-entropy constantly and drop read repair?

Because anti-entropy is expensive: each sweep rebuilds Merkle trees by scanning and hashing the data on disk, competing with live traffic, so it runs on a slow cycle (daily or weekly), not per-request. Between sweeps, a hot key could serve stale reads for a long time. Read repair fills that gap for nearly nothing — it piggybacks on reads you were already doing — keeping frequently-read keys fresh in real time while anti-entropy handles completeness.

How can repair bring DELETED data back to life?

A delete is a tombstone — a versioned 'this key is gone' marker — and tombstones are eventually garbage-collected to reclaim space. If a replica missed both the original delete and its tombstone, and anti-entropy or read repair runs after the tombstone has been GC'd elsewhere, there is now no tombstone to copy across — only the old value survives on the replica that missed the delete, and repair can propagate THAT back out. The deleted data resurrects. The fix is to run anti-entropy repair on a cycle shorter than the tombstone GC window (gc_grace_seconds in Cassandra), so every replica sees the tombstone before it is collected.

What is 'over-repair' and why does it happen?

A Merkle leaf covers a bucket of many keys, so if a single key in that bucket differs, the whole bucket is flagged as diverged and streamed across — including the keys that already matched. Coarser trees (fewer leaves) over-repair more; finer trees waste less but cost more to build. It is the leaf-granularity trade from the Merkle topic, showing up here as extra bytes shipped per sweep.

Is read repair a substitute for the R + W > N overlap rule?

No — they solve different halves. The overlap rule (see /quorum) guarantees a read's responses CONTAIN the freshest value; read repair then propagates that value back to the stale replicas that answered. Overlap makes the read correct; read repair makes the replicas converge. You need both: overlap without repair means replicas never heal, and repair without overlap means the read might not have seen the fresh value to propagate in the first place.

QuizAn operator disables scheduled anti-entropy repair to save I/O, keeping only read repair. Months later they find rarely-accessed records differ across replicas, and some deleted records have reappeared. What went wrong?

  1. Read repair is buggy and should be disabled too.
  2. Read repair only fixes keys that are read, so cold keys never converged; and with no repair inside the tombstone-GC window, missed deletes resurrected.
  3. The overlap rule R + W > N was violated by turning off repair.
  4. Merkle trees are unreliable and caused the divergence.
Show answer

Read repair only fixes keys that are read, so cold keys never converged; and with no repair inside the tombstone-GC window, missed deletes resurrected.Read repair is triggered only by reads, so rarely-accessed keys got no repair and stayed diverged — exactly the cold-key gap anti-entropy is meant to close. Worse, anti-entropy is what guarantees every replica sees a tombstone before it is garbage-collected; without a sweep inside the tombstone-GC window, a replica that missed a delete never learned about it, the tombstone was collected elsewhere, and the stale old value resurrected. The fix is to re-enable scheduled anti-entropy (e.g. nodetool repair via Reaper) on a cycle shorter than gc_grace_seconds. The overlap rule and Merkle trees are working correctly; the missing piece was the background sweep.

In an interview

Frame it as the answer to one question: in a leaderless store, replicas of a key drift apart, so what pulls them back together? Then name the two mechanisms and the axis that separates them — read repair fixes divergence on the read path (immediate, cheap, but only for keys that are read), and anti-entropy fixes it in the background with Merkle trees (complete, but expensive and periodic). Saying that they cover different keys — hot-and-read versus cold-and-unread — is the insight that shows you understand why a system needs both.

  • Start from the cause: W < N means a write is acked before every replica has it, so replicas are routinely behind — and crashes, dropped packets, and sloppy quorums widen the gap.
  • Read repair: a quorum read compares R responses (one full value, the rest digests), returns the newest, and writes it back to the stale replicas. Note the blocking variant is what makes quorum reads monotonic.
  • Anti-entropy: a background Merkle-tree comparison (reference the tree-diff — exchange roots, descend only where hashes differ) that finds and streams just the diverged keys, covering cold keys read repair never touches.
  • Add hinted handoff as the third leg: a stand-in buffers writes for a briefly-down replica and replays them on its return, preventing drift from short outages.
  • Close with the sharpest gotcha: skipping anti-entropy lets cold keys silently diverge and can resurrect deleted data if a sweep doesn't run inside the tombstone-GC window. Ground it in Dynamo and Cassandra (`nodetool repair`, Reaper).

If asked to go deeper, reach into the built simulators rather than hand-waving: /quorum/sim shows the R + W > N read that triggers read repair and heals a laggard, and /merkle/sim shows the tree-diff that makes anti-entropy ship only the differing keys instead of the whole dataset. Being able to point at the exact sub-mechanism — the overlap that surfaces the fresh value, the pruned subtree that keeps the sync cheap — is what separates reciting the names from understanding the machine.

References & further reading
References

Feedback on this topic →