system-design · intermediate

Rebalancing Shards Under Skewed Traffic

The Central Question

Consider a sharded multi-tenant database platform running on the DataLab platform (datalab.com) processing 100,000,000 queries per day:


This condition is Shard Traffic Skew (Data Imbalance).

If the engineering team attempts to rebalance Shard 1 by taking the database offline for 8 hours to manually copy tables, the company violates its 99.99% SLA, incurring massive downtime losses.

Shard Rebalancing is the automated operational process of migrating partition subsets or hash slots from over-burdened database nodes to under-utilized or newly added server instances in real time with Zero Application Downtime.

This lesson answers one central question: How do distributed databases execute zero-downtime live shard rebalancing using 4-stage migration pipelines (Copy $\rightarrow$ Dual-Write $\rightarrow$ Catch Up $\rightarrow$ Shadow Cutover) and Range Splitting algorithms while maintaining ACID consistency?


Causes of Traffic Skew and Shard Imbalance

Shard imbalance occurs when data size or QPS distributes non-uniformly across physical database nodes:

flowchart TD
  SkewCauses[Root Causes of Shard Skew] --> HotTenant[1. Hot Tenant Concentration]
  SkewCauses --> AutoInc[2. Sequential Range Monotonic Writing]
  SkewCauses --> Hardware[3. Heterogeneous Server Hardware]
  
  HotTenant --> HTDesc["One massive customer (Tenant 901) produces 60% of QPS.<br/>All data lands on Shard 1."]
  AutoInc --> AIDesc["Range sharding by date or auto-increment ID.<br/>100% of today's writes hit the newest Shard."]
  Hardware --> HWDesc["Mixing older 16-core servers with new 64-core servers.<br/>Identical slot allocations cause older nodes to collapse."]

Figure 1: Taxonomy of root causes triggering shard traffic skew and data imbalance.


The 4-Stage Zero-Downtime Live Migration Workflow

To move a partition or tenant dataset from a source node (Shard 1) to a target destination node (Shard 4) without taking the application offline, databases execute a 4-Stage Live Migration Pipeline:

sequenceDiagram
    autonumber
    actor Router as Database Proxy / Router
    participant Src as Source Shard 1 (Overloaded)
    participant Dst as Target Shard 4 (New)
    participant CDC as Change Data Capture (CDC Stream)
    
    Note over Src,Dst: Stage 1: Bulk Snapshot Copy
    Src->>Dst: 1. Stream Initial Table Snapshot (Bulk Copy)
    
    Note over Src,Dst: Stage 2: Dual-Writing & CDC Replication
    Router->>Src: 2. Live Writes (INSERT/UPDATE)
    Src->>CDC: 3. Log WAL Mutation to CDC Stream
    CDC->>Dst: 4. Replicate Real-Time Delta Writes
    
    Note over Src,Dst: Stage 3: Catch-Up Verification
    Dst-->>Router: 5. Replication Lag Drops to < 1ms (Caught Up!)
    
    Note over Src,Dst: Stage 4: Atomic Cutover
    Router->>Router: 6. Switch Router Slot Table to Point to Target Shard 4!
    Router->>Src: 7. Delete Migrated Data (Purge Old Range)

Figure 2: Sequence diagram detailing the 4-stage zero-downtime live shard migration workflow.


Rebalancing Strategies: Dynamic Range Splitting vs. Hash Slot Migration

How data partitions are divided determines how cleanly shards rebalance:

flowchart TD
  RebalanceStrategies[Shard Rebalancing Algorithms] --> RangeSplit[1. Dynamic Range Splitting]
  RebalanceStrategies --> SlotMigrate[2. Fixed Hash Slot Migration (Redis/Citus)]
  
  RangeSplit --> RSDesc["Key Range [1 .. 100,000] becomes too large.<br/>Database splits range into [1 .. 50,000] and [50,001 .. 100,000].<br/>Move second range to new node."]
  SlotMigrate --> SMDesc["Fixed 16,384 slots.<br/>Node A transfers slots 4,000 to 5,460 to Node D.<br/>Zero key re-hashing required!"]

Figure 3: Comparison of Dynamic Range Splitting vs Fixed Hash Slot Migration.

1. Dynamic Range Splitting (CockroachDB / HBase)

Hot Range Load Shedding

When a single 64 MB key range experiences an extreme spike in QPS (even if total disk usage is under 64 MB), CockroachDB storage engines trigger **Load-Based Range Splitting**. The engine forces a split of the hot range at the specific key boundary receiving the highest QPS, creating two smaller 16 MB ranges and immediately offloading one half to a separate physical node to shed traffic load.

Rate-Limiting Live Migration I/O

Streaming multi-gigabyte table snapshots during live migration risks consuming 100% of underlying server disk I/O and network bandwidth, starving production database queries. Production storage engines implement **I/O Throttling Controllers**:

Automated Rollback Procedures for Failed Migration Runs

If a target database shard node suffers a hardware crash during Stage 2 CDC catch-up, live rebalancing engines execute an **Automated Fail-Safe Rollback**:
  1. The orchestrator immediately cancels the bulk copy worker threads and drops the incomplete table partitions on the target node.
  2. The database router routing table remains unchanged, continuing $100\%$ of production traffic to the source shard without data loss or user disruption.
  3. Operating teams receive an alert detailing the migration failure cause before initiating a second rebalance attempt.

Post-Cutover Garbage Collection & Disk Compaction

Following successful Stage 4 atomic cutover, the old migrated key records remain on the source shard disk space. The rebalancing engine schedules an asynchronous **Garbage Collection (GC) Job** during off-peak hours to `DELETE` old records in batches of 1,000 and run PostgreSQL `VACUUM FULL` or RocksDB compaction to reclaim disk space.

2. Fixed Hash Slot Migration (Redis Cluster / Vitess)

Virtual Node Migration in Consistent Hashing

In client-side sharded architectures utilizing Consistent Hashing rings, physical server nodes are represented by 100 to 200 **Virtual Nodes** (e.g. `NodeA_1` through `NodeA_200`) distributed randomly across the $2^{32}-1$ integer ring. When `Node A` becomes overloaded:
  1. The cluster orchestrator selects a subset of Node A's virtual nodes (e.g. 50 virtual nodes).
  2. The orchestrator re-assigns those 50 virtual nodes to a newly provisioned physical Node E.
  3. Because virtual nodes are scattered randomly across the ring, rebalancing transfers fractions of data from multiple key ranges uniformly, avoiding large contiguous data moves.

Shadow Cutover and Data Reconciliation Verification

Before flipping the database router switch to direct production traffic to the newly rebalanced target shard (Stage 4 Cutover), systems execute **Shadow Validation**:

Complete Worked Example: Production Go Live Shard Rebalancing Migration Engine

Let's inspect a complete Go implementation of a Live Shard Rebalancing Migration Engine for the DataLab platform (datalab.com).

package main

import (
"context"
"fmt"
"sync"
"time"
)

type MigrationState int

const (
StateIdle MigrationState = iota
StateBulkCopy
StateDualWriteCatchUp
StateCutoverComplete
)

type ShardMigrationEngine struct {
mu sync.Mutex
sourceShard string
targetShard string
state MigrationState
cdcStream chan string
migratedKeys map[string]string
}

func NewShardMigrationEngine(source, target string) *ShardMigrationEngine {
return &ShardMigrationEngine{
sourceShard: source,
targetShard: target,
state: StateIdle,
cdcStream: make(chan string, 1000),
migratedKeys: make(map[string]string),
}
}

func (e *ShardMigrationEngine) ExecuteLiveMigration(ctx context.Context, keysToMigrate map[string]string) error {
e.mu.Lock()
e.state = StateBulkCopy
fmt.Printf("[STAGE 1: BULK COPY] Starting snapshot migration from %s to %s...\n", e.sourceShard, e.targetShard)
e.mu.Unlock()

// 1. Bulk Copy Snapshot Data
for k, v := range keysToMigrate {
e.mu.Lock()
e.migratedKeys[k] = v
e.mu.Unlock()
time.Sleep(10 * time.Millisecond) // Simulate network bulk transfer
}

e.mu.Lock()
e.state = StateDualWriteCatchUp
fmt.Printf("[STAGE 2: DUAL WRITE CATCH-UP] Bulk copy finished. Consuming CDC delta stream...\n")
e.mu.Unlock()

// 2. Consume CDC delta mutations until replication lag is 0
e.flushCDCStream()

// 3. Atomic Cutover
e.mu.Lock()
e.state = StateCutoverComplete
fmt.Printf("[STAGE 3: ATOMIC CUTOVER] Router updated! All traffic for migrated keys now routes to %s.\n", e.targetShard)
e.mu.Unlock()

return nil
}

func (e *ShardMigrationEngine) RecordCDCMutation(key string, value string) {
e.mu.Lock()
defer e.mu.Unlock()

if e.state == StateDualWriteCatchUp || e.state == StateBulkCopy {
e.cdcStream <- fmt.Sprintf("%s:%s", key, value)
}
}

func (e *ShardMigrationEngine) flushCDCStream() {
for len(e.cdcStream) > 0 {
mutation := <-e.cdcStream
fmt.Printf("[CDC REPLICATION] Applying real-time delta write to Target: %s\n", mutation)
}
}


Failure Modes and Engineering Mitigations

Failure ScenarioRoot CauseSystem SymptomDetection SignalMitigation Strategy
1. Migration Network SaturationBulk snapshot copy consumes 100% of network card bandwidth on source node.Source shard API response times spike from $5\text{ms}$ to $3,000\text{ms}$ during migration.High network interface throughput alerts on source database node.Apply Bandwidth Throttling Caps on bulk migration streams (e.g. max 50 MB/s).
2. CDC Catch-Up DeadlockHigh write velocity on source shard exceeds CDC replication consumer throughput.Live migration stays stuck in Stage 2 forever; replication lag never drops to 0.Continuous growing delta queue size metrics during live migration.Implement Write Throttling on Source or scale parallel CDC worker threads.
3. Premature Router Cutover Data LossRouter switches traffic to target shard before Stage 2 CDC catch-up completes.Target shard serves stale data or throws missing key errors on recent records.Primary key lookup exceptions immediately following cutover.Require Cryptographic Checksum Verification and lag $<1\text{ms}$ before cutover.
4. Dual-Write Out-of-Order CorruptionApplication writes to both source and target during migration; writes arrive out of order.Target shard holds old data value while source shard holds new value.Data mismatch reconciliation errors during shadow validation checks.Use Monotonic Version Timestamps or rely exclusively on single-source WAL CDC streams.

What You Should Remember

  1. Rebalancing redistributes skewed partitions: Move data subsets away from overloaded nodes to under-utilized or newly added server instances.
  2. Execute zero-downtime 4-stage migrations: Bulk Copy $\rightarrow$ Dual-Write CDC $\rightarrow$ Catch-Up $\rightarrow$ Atomic Cutover.
  3. Throttle migration network bandwidth: Limit bulk copy transfer speeds to prevent migration traffic from degrading live application user queries.
  4. Use Fixed Hash Slots or Dynamic Ranges: Fixed slots (16,384 in Redis) and 64 MB Range splits avoid global data re-hashing when adding nodes.
  5. Verify data integrity before atomic cutover: Ensure CDC replication lag is $<1\text{ms}$ and row checksums match before updating router tables.

Glossary of Terms

TermDefinition
Shard RebalancingThe operational process of redistributing data partitions across cluster nodes to eliminate load skew.
Live MigrationMoving database partitions from a source node to a target node while serving active production traffic.
Change Data Capture (CDC)Streaming real-time database mutations (INSERT/UPDATE/DELETE) from transaction logs to target systems.
Range SplittingDividing a 64 MB key range into two 32 MB halves when a partition reaches storage capacity.
Atomic CutoverUpdating router routing tables in a single atomic step to direct traffic to the new target node.
Traffic SkewNon-uniform distribution of read/write queries across database shards causing single-node hotspots.

Practice Scenario and Self-Assessment

Architecture Scenario

You are leading the database operations team for an e-commerce platform (`checkout.datalab.com`): **Questions**:
  1. Detail the 4-stage live migration plan to move 500 GB of merchant data from Shard 2 to Shard 5 with zero downtime.
  2. Formulate the bandwidth throttling and CDC catch-up verification strategy.

Interactive Self-Assessment

It streams real-time database mutations occurring during bulk copy, ensuring the target shard is up-to-date before cutover.

CDC automatically converts SQL integer primary keys into string UUIDs.

CDC replaces public DNS nameservers with local hosts file entries.

CDC doubles the physical hardware clock speed of target database CPUs.

The bulk migration copy consumes 100% of the source node's network and disk I/O, starving live user queries and causing outages.

Un-throttled migration automatically formats persistent NVMe SSD disk drives on target nodes.

Un-throttled migration revokes edge HTTPS TLS encryption certificates on load balancers.

Un-throttled migration reboots operating system hypervisors across all shard nodes.


What to Learn Next

Track: Data, Storage and Messaging

Previous: How to Scale a Database — The Progressive Scaling Ladder

Next: Search Index Freshness vs Ranking Quality

By Shubham Jain

All articles · Study paths

Shubham Jain · Learning Lab