Partitioning & Sharding

Rebalancing: A Load Spike You Schedule for Yourself

Moving partitions between nodes is not a background chore. It is a sustained, self-inflicted load spike: terabytes across the network, doubled disk I/O at both ends, and a destination whose caches are empty for every key that arrives. Rebalancing has to run while the system keeps serving — and it competes with that serving for exactly the same resources.

▶ Run the lab

The question this answers

The question

Data has to move between nodes while the system stays up. What does that actually cost, and what breaks at the moment ownership changes?

The guarantee — the property claimed, and its scope

No key is unavailable for longer than its own cutover window, and no acknowledged write is lost — *provided* the mover throttles itself and the cutover is a single ordered event. It guarantees nothing about latency: the move contends for the same disks, network and page cache as live traffic, and the correct expectation is a measurable, sustained degradation for the duration.

Everything below is bought to hold this sentence. "Strongly consistent" with no scope attached is a slogan, not a guarantee — read what it actually covers, and what it explicitly does not.

What a node knows — observation versus inference

A moving node knows how many bytes it has sent and received. It does not know the cluster’s total spare capacity, whether its throttle is the binding constraint, or whether the latency it is causing elsewhere is acceptable. Rebalance throttling is therefore a global policy enforced by local actors with no global view — which is why it is usually set too high.

A node knows its own state and the messages that arrived. Everything else is inference from evidence that was already stale. "B has not replied in five seconds" is knowledge; "B is down" is a decision — and usually the bug.

What guarantee?What does a node know?How does it work?What can fail?How does it fail?Where is coordination?What holds under failure?How does it recover?How would you know?What is the simpler thing?
rebalancingdata movementcutoveroperations

The four costs, none of which is "network bandwidth"

Ask an engineer what a rebalance costs and you get "network". That is the smallest of the four, and the easiest to throttle.

Network. Moving a 2 TB node’s worth of data is 2 TB across the fabric, plus checksums and protocol overhead. At 1 Gb/s effective, that is about five hours; at 10 Gb/s, thirty minutes. Real and bounded, and the only cost with an obvious knob.

Disk I/O at both ends, and more than you moved. The source reads the data — usually a large sequential read that evicts its own page cache. The destination writes it, and in an LSM engine that write is then compacted, often several times, so writing 2 TB costs 4–10 TB of physical writes. Write amplification means the destination does more I/O than the mover accounts for, which is why a rebalance can saturate a destination whose network is at 20%.

Cache coldness at the destination. This is the one nobody budgets for. A partition arrives with an empty page cache, an empty block cache and an empty in-memory index. Every read for a moved key now goes to disk. The moved keys’ p99 jumps by an order of magnitude and stays there until the working set is re-warmed — minutes to hours, depending on the access distribution. **Latency gets worse *after* the move completes, not during it**, which is what makes it so consistently surprising.

Ongoing serving, degraded. Every one of the above competes with live traffic for the same resources. A cluster at 60% utilisation running a rebalance is a cluster at 90%+ utilisation, and queueing is nonlinear near saturation (Coordination Couples Availability is not the issue here; simple utilisation is).

CostFalls onVisible asKnob
Network transfertypicalSource and destination NICs, and the fabric betweenInterface utilisation, transfer durationRate limit per stream and concurrent stream cap
Read amplification at sourcetypicalSource disk, source page cacheRead IOPS up, source cache hit rate downThrottle; read from a follower instead of the leader
Write amplification at destinationtypicalDestination disk, compaction threads, CPUWrite IOPS several× the transfer rate, compaction backlogCompaction throttle; bulk/ingest write path
Cache coldness after cutovertypicalEvery read for a moved keyp99 step-change *after* the move finishesWarm before cutover; move during low traffic; move in small units
Metadata churntypicalThe routing/membership store and every routerMetadata write rate, `NotOwner` responsesMove whole partitions, not keys; batch reassignments
Where the cost actually lands

The cutover is the only part that can lose data

Copying is safe: it is a read on one side and a write on the other, and if it fails you retry it. The hazard is the instant ownership transfers, because for a moment the answer to "who owns this key" must change atomically for everyone, and it cannot.

The standard safe shape has four phases, and each exists to close a specific hole:

1. Copy while the source keeps serving. The destination pulls a snapshot. The source is authoritative throughout; the destination answers nothing.

2. Catch up on the delta. Writes accepted by the source during the copy are streamed to the destination — from a change log, or by a second pass. Repeat until the delta is small enough to close in one short step.

3. Freeze, drain, flip. The source stops accepting writes for that partition, drains what is in flight, ships the final delta, and only then does the ownership record change. The unavailability window is the length of this step, and it is why the delta must be small before you enter it.

4. Redirect stragglers. Routers with stale maps still arrive at the source. It must answer NotOwner, never serve and never 404 — the same discipline as The Ring: Keeping the Mapping Stable When Membership Changes and Range Partitioning: Scans You Keep, Hotspots You Inherit. Optionally the source forwards to the destination for a grace period.

The single most common bug: skipping step 3’s freeze and hoping the delta stream catches everything. It does not, because there is always a write in flight when you decided the delta was empty, and that write is accepted by a node that is about to stop being the owner. It is then lost with no error anywhere.

Copy, catch up, freeze, flip — and where a straggler landstypical
Client (stale map)Source nodeDestination nodeOwnership recordsnapshot stream: deliveredsnapshot streamdelta since snapshot: delivereddelta since snapshotfinal delta after freeze: deliveredfinal delta after freezePUT k (stale map): deliveredPUT k (stale map)NotOwner (epoch 7 < 8) — refresh and retry: deliveredNotOwner (epoch 7 < 8) — refresh and retrybegin snapshot copy (recover) at t=0begin snapshot copyfreeze partition; drain in-flight (decide) at t=5freeze partition; drain in-flightownership → destination (epoch+1) (write) at t=8ownership → destination (epoch+1)serving; caches empty (recover) at t=9serving; caches emptyt=0time →t=12
delivereddelayed (dashed, long)duplicated (×2)dropped — stops short, never arriveswriterecoverdecide
The freeze at t=5 is what makes the final delta complete. Without it there is always an in-flight write that arrives after the last delta and before the ownership flip — accepted, then discarded, with no error. The epoch on the ownership record is what lets the source recognise a stale request instead of serving it.

Automatic rebalancing on top of an unreliable failure detector

Here is the coupling that produces the worst incidents in this module, and it is worth stating as a rule.

A failure detector is imperfect: it will occasionally declare a live-but-slow node dead (Crashed or Just Slow: The Distinction You Cannot Make, No Heartbeat Does Not Mean Dead). An automatic rebalancer reacts to membership changes by moving data. Compose the two and you get: a node that is slow because the cluster is loaded gets declared dead; the rebalancer starts moving its data; the move adds load; more nodes become slow; more nodes are declared dead; more data moves. The cluster converts an overload into a stampede, and the harder it works the worse it gets. This is Cascading Failure: When the Response to Failure Causes More Failure with the rebalancer as the amplifier.

Three defences, in order of how much they help:

Do not rebalance on suspected failure — only on confirmed departure. Distinguish "we have not heard from it in 30 seconds" from "it has been gone for 15 minutes" from "an operator decommissioned it". Only the last two should move data. Cassandra and Elasticsearch both learned this and both expose a delay before reallocation begins.

Cap concurrency cluster-wide, not per node. Each node throttling itself to a modest rate still adds up to a saturated fabric when fifty of them do it at once. The cap that matters is the number of concurrent moves in the whole cluster.

Keep a human in the loop for large moves. Automatic recovery from a single node loss is worth having. Automatic rebalancing of a whole cluster because the failure detector had a bad minute is not. The asymmetry — small automatic, large manual — is a good default.

And a fourth that is really a design choice: move whole partitions, never individual keys. A partition-granular move is discrete, interruptible, resumable and countable. A key-granular reshuffle has no natural checkpoint and cannot be paused halfway.

Making the move boring

The operational goal is that a rebalance is uninteresting. That is achievable, and the techniques are unglamorous.

Move small units. Many small partitions mean each cutover window is short and each failed transfer is cheap to retry. This is a strong argument for a higher partition count independent of balance.

Warm before you flip. If the destination has the data but not the ownership, it can serve shadow reads to populate its caches. Then the cutover does not cause a cold-cache cliff. Few systems do this automatically and it is the highest-value custom work available here.

Throttle by observed impact, not by a fixed rate. A fixed 100 MB/s is too much at peak and too little at 3am. Systems that adapt the move rate to observed p99 on the affected nodes finish faster *and* hurt less.

Rehearse the abort. Every rebalance should be pausable and reversible before cutover. The plan that has no abort path is the plan that gets completed during an incident because nobody knew how to stop it.

Prefer cheap sources. Stream from a follower rather than the leader where the engine allows it, so the move does not evict the cache of the node serving reads.

Key points

  • A rebalance is a sustained load spike you chose to inflict, competing with live traffic for the same disks and links.
  • Write amplification means the destination does several times more physical I/O than the bytes you moved.
  • Cache coldness makes latency worse *after* the move completes, which is why teams misattribute the impact.
  • Only the cutover can lose data, and only if the source is not frozen before the final delta is shipped.
  • Automatic rebalancing driven by an imperfect failure detector converts an overload into a self-amplifying stampede.

The chain, answered

Every field here is required, which is why no lesson in this domain can recommend a design without naming what an operator sees when it fails, what survives the partition, what repairs it afterwards, and the simpler thing to consider first.

How it works
  • Decide a target assignment: which partitions should live where. This is a planning step and should be inspectable before anything moves.
  • For each partition to move: snapshot at the source and stream to the destination while the source keeps serving.
  • Stream the delta of writes accepted during the copy, repeatedly, until the remaining delta is small.
  • Freeze the partition at the source, drain in-flight writes, ship the final delta.
  • Commit the ownership change with a new epoch — the single ordered event that transfers authority.
  • Answer NotOwner at the source for stale requests; optionally forward for a grace period.
  • Drop the source copy only after the destination confirms durability, and only after the grace period.
What can fail at the boundary
  • A write is accepted by the source after the final delta and before the ownership flip, and is silently discarded.
  • The transfer is interrupted and resumes from the start because no checkpoint was kept.
  • The destination runs out of disk mid-move, having accepted ownership of some partitions already.
  • A flapping node causes partitions to move away and back repeatedly, so the cluster never converges.
  • The ownership record is updated but the routers are not, extending the NotOwner window far beyond the cutover.
  • The move saturates the network, making the failure detector suspect more nodes, triggering more moves.
How it fails — what an operator sees
  • Rebalance brownout: p99 roughly doubles for the whole duration of the move, with error rate unchanged. The operator’s clearest signal is source-node disk read throughput and destination compaction backlog, neither of which is on a normal dashboard.
  • Post-cutover cold-cache cliff: the move completes, everyone declares success, and latency for the moved keys is 10× worse for the next hour while the cache re-warms. The database or disk behind the cache takes a step-change in load at the exact moment the transfer ended.
  • Runaway rebalance: a node flaps in and out, partitions move back and forth, and streaming sessions never return to zero. The operator sees continuous background load with no completion and a cluster that has been "rebalancing" for days.
  • Silent write loss at cutover: writes accepted by the old owner in the final seconds never reach the new one. There is no error; the loss is found later by reconciliation or by a user, and cannot be attributed to a moment.
  • Disk exhaustion mid-move: the destination fills up holding both incoming data and its own compaction temporaries, and it fails while owning partitions whose only other copy is on a node being decommissioned.
  • Detector-driven stampede: a load spike makes several nodes slow, the detector marks them dead, and the rebalancer starts moving their data. Request rate is flat while cluster load doubles — the signature that the cluster is fighting itself.
Where coordination is required
  • The ownership flip must be a single totally-ordered event. Everything else in a rebalance can be eventual; this one thing cannot.
  • An epoch attached to the ownership record turns the propagation delay from a correctness hazard into an observable retry — the same technique as Fencing Tokens: Making the Stale Actor Safe, Not Just Unlikely.
  • Deciding the target assignment is a global computation, so it belongs in one place: a controller, a leader, or an operator. Nodes independently deciding to move data is how you get thrash.
  • Rate limits need cluster-wide coordination to be meaningful, because per-node limits compose into an unbounded aggregate.
What still holds under failure
  • If the destination fails mid-transfer, the source is still authoritative and nothing is lost — provided ownership had not yet flipped. This is why the flip must be last.
  • If the source fails mid-transfer, the partition falls back on its remaining replicas; the move restarts from a different source.
  • If the ownership store is unavailable, no cutover can happen and the cluster simply stays as it is — a safe, if inconvenient, failure.
  • During the move the partition is typically under-replicated in effect, because one replica’s resources are consumed by streaming rather than by serving.
How it recovers
  • Detect: an explicit rebalance state per partition, with a start time. A partition in moving for hours is the alert.
  • Contain: pause the rebalancer. It should be a single command, tested, and known to the on-call.
  • Recover: for a stalled transfer, keep the source authoritative and restart the stream. Never promote a partially-populated destination.
  • Reconcile: after any cutover with a suspected loss window, run Anti-Entropy: Repairing Divergence Nobody Reported over the affected ranges and compare against the source’s write log.
  • Verify: confirm every partition has exactly one owner and the target replication factor, and that streaming sessions have returned to zero.
How you would know
  • Bytes remaining to move, and estimated completion. Without it, "is the rebalance nearly done" is unanswerable and every conversation during an incident stalls on it.
  • Concurrent streaming sessions cluster-wide — the number your throttle should actually be limiting.
  • Destination compaction backlog and pending compactions. This is where write amplification becomes visible before it becomes an outage.
  • Cache hit rate for moved partitions specifically, from the moment of cutover. The re-warm curve tells you the true cost of the move.
  • Count of partitions in a transitional state, and how long the oldest has been there.
  • NotOwner rate by client — the width of the routing propagation window.
When it helps
  • Adding capacity, where the alternative is running a saturated cluster.
  • Recovering replication factor after a node loss, which is genuinely urgent and worth the disruption.
  • Correcting skew, when a partition has grown or heated disproportionately.
  • Draining a node for maintenance or hardware replacement, where a controlled move beats an abrupt failure.
When it hurts
  • During an ongoing incident. A cluster that is already struggling cannot absorb a self-inflicted load spike, and the rebalance will make the incident longer.
  • At peak traffic. The same move at 3am costs a fraction as much and hurts nobody.
  • In response to a transient failure signal, which is the stampede case.
  • When the imbalance being corrected is smaller than the cost of correcting it — a 15% skew is usually cheaper to tolerate than to fix.
Simpler alternatives
  • Do nothing. Tolerate imbalance until it crosses a threshold that justifies the move. Most clusters rebalance more often than they need to.
  • Move logical ownership without moving data, where the storage is shared — a disaggregated architecture makes rebalancing a metadata operation, which is the largest available win and the reason such architectures exist (A Distributed Database Is a Stack, Not a Box).
  • Add a replica instead of moving the primary: build the new copy in the background, promote when it is caught up, and drop the old one. Same bytes, but no unavailability window at all.
  • Grow by adding partitions rather than moving them, when the partition count is under your control and new data can be steered to new partitions.
  • Rebuild from an external source of truth — object storage, an event log — rather than streaming peer to peer, when that source is cheaper to read than a live node (Recovered State Is a Checkpoint Plus the Log After It).

Rebalancing: a load spike you schedule for yourself

Rebalancing: the bill for a resize
How many partitions move comes from the engine's churn count, so the strategy you chose months ago sets the size of this bill. The four costs are bytes, time, competing I/O, and a destination whose cache is empty.
strategy
partitions moved
124 of 1024
bytes on the wire
496 GB
at the throttle
1.2 h
origin during the move
sheds
the same move under modulo
920 partitions · 3,680 GB
extra origin reads from cold caches
4,602/s
origin utilisation during the move
1.10×
disk I/O
doubled at both ends — the source reads what it is sending, the destination writes what it receives, and both are still serving
Arrivals (6602/s) exceed capacity (6000/s). The queue pins at its bound of 20000, so waiting stays finite at 3333 ms and the excess 602/s is refused immediately. Shedding is the bound doing its job: a fast rejection is a better answer than a slow timeout.
The cutover is the only part of this that can lose data. Everything above is throughput; the moment ownership changes hands, a write accepted by the old owner after the snapshot and before the switch has nowhere correct to go. That is why real implementations move data first, keep the source authoritative, and make the ownership flip a single atomic decision with a fence — not why they buy more bandwidth.
assumptionTransfer time assumes the throttle is the only limit and that nothing fails mid-move. The cold-read estimate assumes every moved partition's traffic misses until refilled, which is the pessimistic end of a real refill curve.

What people believe, and what is true

Claim

Rebalancing is a background task, so it does not affect production.

Reality

It uses the same disks, the same network and the same page cache as production. "Background" describes its priority, not its resource consumption.

Claim

The impact ends when the transfer ends.

Reality

The destination’s caches are empty for every key that arrived. Latency for moved keys is worst immediately *after* the move and recovers over the re-warm period.

Claim

Automatic rebalancing makes the cluster self-healing.

Reality

It makes it self-healing for genuine failures and self-harming for false ones. Coupling data movement to an imperfect failure detector is how a slow node becomes a cluster outage.

Claim

Throttling each node to a safe rate makes the whole move safe.

Reality

Fifty nodes each moving at a safe rate saturate a shared fabric. The limit has to be cluster-wide concurrency, not per-node bandwidth.

Go deeper

Only the levels this lesson can honestly fill — a missing level is a claim nobody had.

Overview

Moving partitions between nodes competes with live traffic for the same resources. Expect measurable degradation for the duration, and a cold-cache penalty afterwards.

Practical

Move whole partitions, one bounded unit at a time, off-peak, with a cluster-wide concurrency cap and a tested pause. Never start a rebalance during an incident. Watch destination compaction backlog, not just network.

Advanced

Copy, catch up, freeze, flip. The freeze exists so the final delta is complete; without it there is always an in-flight write that is accepted by a node about to lose ownership and then silently dropped. Make the flip a single epoched event so stale routers get NotOwner instead of a wrong answer.

Internals

Do not couple data movement to failure suspicion. Introduce a delay and a distinct "confirmed gone" state between the detector and the rebalancer, and cap concurrent moves cluster-wide. Otherwise the composition of an imperfect detector with an eager mover is a positive feedback loop: load makes nodes look dead, dead nodes cause moves, moves cause load.

Apply it

Build it, then break it
  • 🔧 Write down your cluster’s rebalance abort procedure. If you cannot, that is the finding.
  • 🔧 Measure the cache hit rate for a partition before and after a move, and put a number on the re-warm period. That number is the part of the cost nobody has budgeted.
Reason about this
  • A node is replaced at 2pm on a Tuesday. Within ten minutes three more nodes are marked down and the cluster is moving data everywhere. Explain the mechanism and the two changes that would prevent it.
  • After a rebalance, reconciliation finds 40 writes missing, all timestamped within a two-second window. Identify the bug precisely.
Interview questions
  • 💬 Walk me through the phases of moving a partition from one node to another without losing a write.
  • 💬 A rebalance completed successfully and latency got worse afterwards. Why?
  • 💬 Your cluster automatically rebalances when a node is marked down. What could go wrong during a traffic spike?