Distributed Database Sharding Under the Hood: Consistent Hashing, 2PC, Range Partitioning, and Rebalancing Architecture

Database Internals & Distributed Systems

Single-node databases like PostgreSQL or MySQL inevitably hit a hard physical wall when transaction throughput scales past 100,000 writes per second or datasets exceed multi-terabyte memory-mapped storage boundaries. Even with read replicas and vertical hardware scaling, single-leader write bottlenecks restrict global application growth.

Distributed Database Sharding and Partitioning solves this bottleneck by horizontally partitioning rows across a cluster of independent storage nodes. In this deep dive, we explore how modern distributed engines—such as Vitess, CockroachDB, Google Spanner, Citus, Cassandra, and MongoDB—shard datasets using Consistent Hashing rings, Range Splits, Scatter-Gather Query Routers, Two-Phase Commit (2PC) coordinators, and active-active live rebalancing without downtime.


1. The Architecture of Horizontal Scaling: Replication vs. Sharding

In distributed database design, engineers often conflate Replication with Sharding. Replication duplicates the exact same dataset across multiple nodes to increase read availability and provide fault tolerance. However, write requests in single-leader replication models must still pass through a single primary node, leaving write bandwidth bottlenecked by the primary's CPU, RAM, and disk IOPS.

Sharding (Horizontal Partitioning) splits a dataset into distinct, non-overlapping subsets called Shards or Partitions. Each physical storage node in a sharded cluster owns and manages a specific subset of data. This distributes both read and write workloads proportionally across $N$ physical machines!

Dimension Vertical Scaling (Scale-Up) Read Replication (Scale-Out Reads) Horizontal Sharding (Scale-Out Writes)
Write Capacity Bounded by single machine RAM/IOPS Bounded by single Primary node Scales linearly with cluster size ($N imes ext{Node Capacity}$)
Storage Capacity Hard limit (e.g. 64TB NVMe limit) Bounded by single node disk size Petabyte-scale distributed disk pooling
Cross-Node Queries N/A (Local memory speed) N/A (Local replica execution) Requires Scatter-Gather or Distributed Joins ($O(K)$ network RPCs)
Transaction Guarantees Strict ACID (Single-node locks) Eventual or Read-After-Write Consistency Distributed 2PC / Consensus (Raft/Paxos per range)

2. Sharding Strategies: Hash-Based vs. Range-Based Partitioning

Choosing the correct sharding key strategy determines how evenly data distributes across nodes and how efficiently queries execute.

2.1 Hash-Based Partitioning

In hash-based partitioning, a deterministic cryptographic or fast non-cryptographic hash function $H(k)$ (such as MurmurHash3 or CityHash) is applied to the Shard Key $k$. The hash output is mapped modulo the number of active shards or across a 64-bit Consistent Hashing ring:

$$\text{Shard ID} = H(\text{shard\_key}) \pmod{N}$$
  • Pros: Uniform key distribution; eliminates write hotspots for sequential monotonically increasing primary keys (like auto-increment IDs or timestamps).
  • Cons: Range queries (e.g. WHERE created_at BETWEEN t1 AND t2) cannot locate contiguous key blocks and must scatter-gather across every single shard in the cluster!

2.2 Range-Based Partitioning

Range partitioning divides keys into contiguous ordered intervals (e.g., Range 1: [A - G), Range 2: [H - N), Range 3: [O - Z)). Systems like CockroachDB and Google Spanner use Range Partitioning with LSM-Tree SSTables.

  • Pros: Extremely efficient range scans! Adjacent keys reside on the exact same physical storage node.
  • Cons: Monotonically increasing primary keys cause severe write hotspots, sending 100% of new write traffic to the highest key range node!

3. Consistent Hashing Rings and Virtual Nodes (vnodes)

Traditional modulo sharding ($ ext{Hash}(k) \pmod N$) suffers from a catastrophic flaw: when adding a new physical node $N+1$, almost 100% of existing keys hash to a new node index, requiring a full cluster data rewrite! Consistent Hashing fixes this by placing nodes and keys on a circular 64-bit integer ring $[0, 2^{64}-1]$.

Virtual Nodes (vnodes) Solution:

Physical machines are assigned multiple Virtual Nodes (vnodes) randomly distributed around the ring. When a physical node joins or dies, only $ rac{1}{N}$ of total keys are migrated to adjacent vnodes, leaving the rest of the cluster completely untouched!

4. Step-by-Step Worked Numeric Trace: Consistent Hashing Key Placement

Let's trace key assignment across a Consistent Hashing ring $[0, 1000)$ with 3 physical nodes ($A, B, C$) mapped via 2 vnodes each:

  graph TD
      A["Client Request: key='user_42'"] --> B["Hash Function: MD5('user_42') -> 75"]
      B --> C["Consistent Hashing Ring [0 - 1000)"]
      C --> D["Next Clockwise vnode: A_v1 at pos 100"]
      D --> E["Assigned Physical Server: Node A"]
  

Figure 1: Consistent Hashing Key Resolution Workflow.

Virtual Node Ring Map:
  - Node A_v1 at position 100
  - Node B_v1 at position 350
  - Node C_v1 at position 550
  - Node A_v2 at position 700
  - Node B_v2 at position 850
  - Node C_v2 at position 950

Key Placement Execution (Traversing Clockwise):
  1. Key "user_42"    -> Hash = 75   -> Next vnode is A_v1 (100) -> Placed on Node A
  2. Key "order_99"   -> Hash = 420  -> Next vnode is C_v1 (550) -> Placed on Node C
  3. Key "payment_12" -> Hash = 780  -> Next vnode is B_v2 (850) -> Placed on Node B
  4. Key "session_88" -> Hash = 980  -> Wraps to A_v1 (100)      -> Placed on Node A

Adding Node D (vnode D_v1 placed at pos 400):
  - Keys in range (350, 400] that previously landed on C_v1 (550) now land on D_v1!
  - ONLY keys in (350, 400] move to Node D. 85% of cluster keys remain on their existing nodes!

5. Query Routing Layer & Scatter-Gather Architecture

In a sharded architecture, client applications do not manage shard mappings directly. A stateless Query Router (e.g. Vitess VTGate, Citus Coordinator, or MongoDB mongos) parses SQL/NoSQL AST queries, inspects the WHERE clause for the Shard Key, and routes traffic accordingly:

  graph TD
      Client["Client Application"] -->|SQL Query| Router["Query Router (VTGate / Mongos)"]
      Router -->|Targeted Single-Shard Hop| S1["Shard 1 (Node A) - FOUND"]
      Router -->|Scatter-Gather Parallel RPC| S2["Shard 2 (Node B)"]
      Router -->|Scatter-Gather Parallel RPC| S3["Shard 3 (Node C)"]
      S1 --> Router
      S2 --> Router
      S3 --> Router
      Router -->|Merged Result Set| Client
  

Figure 2: Single-Shard Targeted Routing vs. Multi-Shard Scatter-Gather Architecture.

  • Single-Shard Point Query: The router identifies shard_key = 42, routes to Shard 1 directly, and returns in single-network hop latency.
  • Scatter-Gather Query: The query lacks a shard key (WHERE status = 'ACTIVE'). The router dispatches $N$ parallel RPCs to all shards, merges result sets in memory, performs global sorting/pagination, and returns to client!

6. Distributed Transactions & Two-Phase Commit (2PC) Protocol

When an atomic transaction mutates rows residing on two distinct shards (e.g. transferring money from Account 101 on Shard A to Account 202 on Shard B), a local ACID transaction is insufficient. Distributed engines execute the Two-Phase Commit (2PC) protocol:

  sequenceDiagram
      autonumber
      participant C as 2PC Coordinator
      participant S1 as Shard 1 (Account 101)
      participant S2 as Shard 2 (Account 202)
      
      Note over C,S2: Phase 1: Prepare Phase
      C->>S1: PREPARE Transaction
      C->>S2: PREPARE Transaction
      S1-->>C: VOTE_COMMIT (Locks Acquired & WAL Flushed)
      S2-->>C: VOTE_COMMIT (Locks Acquired & WAL Flushed)
      
      Note over C,S2: Phase 2: Commit Phase
      C->>C: Write COMMIT Record to Durable Log
      C->>S1: GLOBAL_COMMIT
      C->>S2: GLOBAL_COMMIT
      S1-->>C: ACK (Locks Released)
      S2-->>C: ACK (Locks Released)
  

Figure 3: Two-Phase Commit (2PC) Protocol Sequence Diagram.

Phase 1: Prepare Phase
  1. Transaction Coordinator sends PREPARE message to Shard A and Shard B.
  2. Shard A and B write transaction actions to local WAL, acquire locks, and reply VOTE_COMMIT.

Phase 2: Commit Phase
  1. If ALL shards vote VOTE_COMMIT:
     Coordinator writes COMMIT record to its durable log and dispatches GLOBAL_COMMIT.
  2. Shards release locks, finalize local writes, and send ACK back to Coordinator.
  3. If ANY shard votes VOTE_ABORT (or times out):
     Coordinator sends GLOBAL_ABORT, reverting locks on all shards!
The 2PC Blocking Flaw:

If the Coordinator crashes mid-way after Phase 1, participant shards are left holding exclusive locks in an indefinite In-Doubt state! Modern systems solve this by replicating the 2PC Coordinator via Raft/Paxos consensus groups.

7. Subtree and Range Split Mechanics in CockroachDB / Spanner

In range-partitioned engines, single Range partitions grow dynamically as data is inserted. When a range exceeds a threshold (typically 64MB or 512MB), the engine executes an automatic Range Split:

  1. The storage engine locates the median key in the LSM-Tree or B-Tree index.
  2. A Raft command creates a new Range descriptor: Range 1 becomes [A - M) and Range 2 becomes [M - Z).
  3. The global Range Routing Table (a distributed 3-tier B-tree) is updated atomically to route queries for key $M+$ to Range 2!

8. Cross-Shard Distributed Joins & Broadcast Joins

Joining tables across shards is one of the most complex query engine tasks. Distributed databases use three join strategies:

8.1 Broadcast (Replication) Join

When joining a massive sharded table (Orders) with a small static reference table (Categories), the engine replicates the entire Categories table to every shard node. Joins execute 100% locally on each shard without cross-node data shuffling!

8.2 Hash Redistribute (Shuffle) Join

When joining two large sharded tables (Users and Orders) sharded on different keys, both tables are dynamically re-hashed by the join key and shuffled across the network to temporary worker nodes holding matching hash buckets.

9. Online Dynamic Shard Rebalancing without Downtime

When an existing shard becomes overloaded or disk space exceeds 80%, the control plane initiates Online Resharding:

  graph LR
      A["1. Snapshot Copy (SSTables)"] --> B["2. Real-Time CDC Buffer"]
      B --> C["3. Replication Catch-Up (<10ms Lag)"]
      C --> D["4. Atomic Router Cutover (<50ms)"]
      D --> E["5. Asynchronous GC Cleanup"]
  

Figure 4: Online Zero-Downtime Data Migration Pipeline.

Step-by-Step Online Data Rebalancing:
  1. Snapshot Copy: Storage engine streams background SSTable/RocksDB snapshot from Source Shard to Target Shard.
  2. Change Data Capture (CDC): Write transactions continue on Source Shard, appending to a CDC buffer.
  3. Catch-Up Replay: CDC log delta is applied to Target Shard until replication lag drops below 10ms.
  4. Atomic Router Flip: Router lock pauses writes for < 50ms, updates routing metadata, and redirects write traffic to Target Shard!
  5. GC Cleanup: Source Shard deletes migrated key ranges asynchronously.

10. Distributed Deadlock Detection & Lock Timeouts

In distributed transactions spanning multiple shards, cyclic lock dependencies cause Distributed Deadlocks (e.g. Transaction 1 locks Key $A$ on Shard 1 and waits for Key $B$ on Shard 2, while Transaction 2 locks Key $B$ on Shard 2 and waits for Key $A$ on Shard 1). Engines resolve this using:

  • Lock Wait Timeouts: Abort transactions that wait longer than a configurable threshold (e.g. 500ms). Simple, but can cause cascading aborts under heavy contention.
  • Wait-For Graph Analysis: A centralized or distributed deadlock detector periodically constructs global Wait-For directed graphs and aborts the youngest transaction in a detected cycle.

11. Consistent Hashing Implementation in Python

Below is a production-ready Python implementation of a Consistent Hashing Ring with virtual nodes and MD5 hashing:

import hashlib
import bisect

class ConsistentHashRing:
    def __init__(self, vnodes_per_node=100):
        self.vnodes_per_node = vnodes_per_node
        self.ring = []        # Sorted list of vnode hashes
        self.vnode_map = {}   # Map: vnode hash -> physical node string

    def _hash(self, key: str) -> int:
        md5_hex = hashlib.md5(key.encode('utf-8')).hexdigest()
        return int(md5_hex[:8], 16)

    def add_node(self, node: str):
        for i in range(self.vnodes_per_node):
            vnode_key = f"{node}#vnode-{i}"
            vnode_hash = self._hash(vnode_key)
            self.ring.append(vnode_hash)
            self.vnode_map[vnode_hash] = node
        self.ring.sort()

    def remove_node(self, node: str):
        for i in range(self.vnodes_per_node):
            vnode_key = f"{node}#vnode-{i}"
            vnode_hash = self._hash(vnode_key)
            idx = bisect.bisect_left(self.ring, vnode_hash)
            if idx < len(self.ring) and self.ring[idx] == vnode_hash:
                del self.ring[idx]
                del self.vnode_map[vnode_hash]

    def get_node(self, key: str) -> str:
        if not self.ring:
            return None
        key_hash = self._hash(key)
        idx = bisect.bisect_right(self.ring, key_hash)
        if idx == len(self.ring):
            idx = 0
        return self.vnode_map[self.ring[idx]]

# Usage Verification
ring = ConsistentHashRing(vnodes_per_node=100)
ring.add_node("node-1.db.internal")
ring.add_node("node-2.db.internal")
ring.add_node("node-3.db.internal")

print("User 1001 mapped to:", ring.get_node("user_id_1001"))
print("User 1002 mapped to:", ring.get_node("user_id_1002"))

12. Distributed Two-Phase Commit Coordinator in Go

Below is a production-grade Go implementation of a Two-Phase Commit (2PC) Coordinator executing parallel RPCs over gRPC channels:

package main

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

type ShardParticipant interface {
	Prepare(ctx context.Context, txID string) error
	Commit(ctx context.Context, txID string) error
	Abort(ctx context.Context, txID string) error
}

type TwoPhaseCoordinator struct {
	participants []ShardParticipant
}

func (c *TwoPhaseCoordinator) ExecuteTransaction(txID string) error {
	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
	defer cancel()

	// PHASE 1: Parallel Prepare
	var wg sync.WaitGroup
	errChan := make(chan error, len(c.participants))

	for _, p := range c.participants {
		wg.Add(1)
		go func(sp ShardParticipant) {
			defer wg.Done()
			if err := sp.Prepare(ctx, txID); err != nil {
				errChan <- err
			}
		}(p)
	}

	wg.Wait()
	close(errChan)

	// Evaluate Phase 1 Votes
	if len(errChan) > 0 {
		fmt.Printf("Tx %s: Prepare failed! Initiating Global Abort...
", txID)
		c.broadcastAbort(txID)
		return fmt.Errorf("transaction aborted due to prepare failure")
	}

	// PHASE 2: Global Commit
	fmt.Printf("Tx %s: All shards prepared. Executing Global Commit...
", txID)
	c.broadcastCommit(txID)
	return nil
}

func (c *TwoPhaseCoordinator) broadcastCommit(txID string) {
	for _, p := range c.participants {
		_ = p.Commit(context.Background(), txID)
	}
}

func (c *TwoPhaseCoordinator) broadcastAbort(txID string) {
	for _, p := range c.participants {
		_ = p.Abort(context.Background(), txID)
	}
}

13. Vitess Architecture: Scaling MySQL via VSchema Keyspaces

Vitess abstracts a cluster of MySQL instances into a unified database surface area. Using VSchema declarations, Vitess defines Keyspaces and primary vindexes (such as hash or unicode_loose_md5). Vitess query routers (VTGate) inspect inbound SQL, rewrite queries into shard-specific SQL dialects, and stream parallel socket connections back to the client.

14. Citus Engine: Distributed PostgreSQL via Table Partitioning

Citus is an extension that transforms PostgreSQL into a distributed database. By executing create_distributed_table('orders', 'company_id'), Citus splits PostgreSQL tables into underlying worker table shards (orders_102008, orders_102009). Local PostgreSQL query execution plans run in parallel on worker nodes, while the Citus coordinator merges aggregate results.

15. Cassandra & DynamoDB Distributed Token Rings

Apache Cassandra uses a decentralized Peer-to-Peer Token Ring (based on the Amazon Dynamo paper) without a master coordinator node. Every node in the cluster acts as a query router. Writes are routed to $R$ replica nodes using Murmur3 tokens, using Vector Clocks or Last-Write-Wins (LWW) timestamp resolution for conflict handling.

16. Range Partitioning in Google Spanner & TrueTime API

Google Spanner relies on atomic hardware GPS receivers and atomic clocks (TrueTime API) to provide strict External Consistency (Serializable ACID) across globally distributed sharded ranges. TrueTime bounds time uncertainty to $\epsilon \le 1 ext{ms}$, allowing Spanner to assign globally monotonic commit timestamps without centralized lock managers!

17. Shard Key Selection Playbook & Anti-Patterns

Selecting a poor shard key is the #1 cause of distributed database failure. Follow these golden design rules:

Shard Key Candidate Query Pattern Compatibility Distribution Uniformity Verdict & Recommendation
user_id / account_id Multi-tenant B2B / SaaS apps High (Uniform hash across tenants) EXCELLENT: Keeps tenant data localized to single shard!
created_at (Timestamp) Time-series range scans Severe Hotspotting (All writes hit 1 node) POOR: Use composite key (user_id, created_at) instead!
country_code Geographic localization Low Skew (e.g. US/EU vs tiny regions) RISKY: Causes massive storage imbalance across nodes.
UUIDv4 (Random UUID) Point Key-Value Lookups Perfect Uniformity GOOD for writes, POOR for range queries.

18. Hotspot Mitigation Strategies & Salted Keys

When a celebrity account or high-volume tenant causes a hotspot on a single shard key, systems use Key Salting. A random prefix $0..K$ is prepended to the key (salt_key = hash(user_id) % 10 + "_" + user_id), distributing the entity's records across 10 distinct shards!

19. Change Data Capture (CDC) and Debezium Shard Streaming

In distributed database architectures, analytics workloads should never execute scatter-gather queries on operational OLTP shards. Instead, Change Data Capture (CDC) pipelines (using Debezium and Kafka) capture write logs from each database shard and stream events into columnar data warehouses (like Snowflake or ClickHouse) for aggregate reporting.

20. Complete Python Test Suite & Benchmark Routine

Below is a runnable Python benchmark comparing Scatter-Gather query latency against Targeted Shard Routing across 1,000 simulated queries:

import time
import random

class RouterBenchmark:
    def __init__(self, num_shards=16):
        self.num_shards = num_shards
        self.shards = {i: {} for i in range(num_shards)}
        for uid in range(100000):
            shard_id = uid % num_shards
            self.shards[shard_id][uid] = f"user_data_{uid}"

    def targeted_query(self, user_id):
        shard_id = user_id % self.num_shards
        return self.shards[shard_id].get(user_id)

    def scatter_gather_query(self, target_val):
        results = []
        for shard_id in range(self.num_shards):
            for uid, val in self.shards[shard_id].items():
                if val == target_val:
                    results.append(val)
        return results

bench = RouterBenchmark()

t0 = time.time()
for _ in range(1000):
    uid = random.randint(0, 99999)
    bench.targeted_query(uid)
t_targeted = time.time() - t0

print(f"Targeted Routing (1,000 queries): {t_targeted:.4f} seconds")

21. Mathematical Proof of Uniform Hash Key Distribution

Given $K$ key items randomly mapped across $N$ virtual nodes using a uniform cryptographic hash function $H: K o [0, M]$, the probability $P(X = k)$ of a virtual node receiving exactly $k$ items follows a Binomial Distribution $B(K, rac{1}{N})$:

$$P(X = k) = \binom{K}{k} \left(\frac{1}{N}\right)^k \left(1 - \frac{1}{N}\right)^{K - k}$$

As $K, N o \infty$, by the Law of Large Numbers and Poisson Approximation, the variance ratio $ rac{\sigma}{\mu} = rac{1}{\sqrt{K/N}}$ approaches zero. With $v = 100$ vnodes per physical node, load imbalance across nodes is mathematically bounded within $\pm 5\%$ of perfect equality!

22. Distributed Query Optimization AST Rewriting

When a SQL query contains an IN clause spanning multiple keys (e.g. WHERE user_id IN (10, 25, 99)), the Query Router's AST Rewriter splits the query into distinct sub-queries grouped by target shard:

Original Query:
  SELECT * FROM users WHERE user_id IN (10, 25, 99);

Router AST Rewriter Execution:
  1. Hash(10) -> Shard 1; Hash(25) -> Shard 2; Hash(99) -> Shard 1
  2. Sub-Query 1 (Shard 1): SELECT * FROM users WHERE user_id IN (10, 99);
  3. Sub-Query 2 (Shard 2): SELECT * FROM users WHERE user_id IN (25);
  4. Parallel Dispatch -> Union Result Sets -> Return to Client!

23. Resharding Failure Recovery & Undo Logs

If an online resharding process fails midway due to a network partition between shards, the control plane relies on Undo Logs and transactional metadata state machines. The migration status transitions from PREPARING $ o$ COPYING $ o$ CATCHUP $ o$ SWITCHING. If a failure occurs before SWITCHING, the router drops transient data on target nodes and reverts back to the original source shard without data loss!

24. Distributed Storage System Comparison Matrix

System Sharding Model Consensus Layer Cross-Shard Transactions Primary Use Case
Vitess Hash / Custom VSchema MySQL Raft / Orchestrator Two-Phase Commit (2PC) Scaling Monolithic MySQL
CockroachDB Dynamic Range Split Multi-Raft Groups Serializable 2PC + Raft Global Distributed SQL
Apache Cassandra Consistent Hash Ring Paxos (Lightweight Tx) Eventual / Quorum (No 2PC) High-Throughput Write Ingestion
MongoDB Sharded Hash or Range Key Replica Set Raft-like Distributed 2PC Transactions Flexible Document Storage

25. Production Monitoring & Shard Skew Alerts

Engineers operating sharded clusters should monitor three critical metrics in Prometheus/Grafana:

  • Shard Storage Skew Ratio: $ rac{ ext{Max Shard Disk Usage}}{ ext{Min Shard Disk Usage}}$. Ratios $> 1.5$ trigger rebalancing alerts.
  • Scatter-Gather Query Rate: Percentage of total queries executing cross-shard scans. Target: $< 5\%$ of total QPS.
  • 2PC Abort Rate: Percentage of distributed transactions aborting during Phase 1 prepare checks.

26. Developer Pitfall Box

Monotonically Increasing Key Warning:

Never use auto-incrementing integers (AUTO_INCREMENT) or sequential timestamps (created_at) as your primary Shard Key in Range-Partitioned databases! 100% of write traffic will hit a single active node holding the highest key range, reducing your multi-node cluster to single-node throughput speeds!

27. Production Engineering Summary Checklist

  • Select a high-cardinality Shard Key that aligns with $> 90\%$ of application read/write query filters.
  • Use Consistent Hashing with virtual nodes (vnodes) to minimize data migration when scaling nodes.
  • Isolate heavy cross-shard analytics workloads to read replicas or Change Data Capture (CDC) data warehouses.
  • Replicate 2PC Transaction Coordinators using Raft/Paxos consensus to prevent in-doubt lock blocking.

28. Developer FAQ

Q1: What is the primary difference between Database Partitioning and Sharding?

Partitioning generally refers to splitting tables into smaller logical subsets on a single database server (e.g., PostgreSQL table partitioning). Sharding distributes those partitions across multiple independent physical servers over a network!

Q2: Why are cross-shard joins so slow in distributed databases?

Cross-shard joins require shuffling millions of raw data rows across network sockets between database nodes, replacing fast in-memory RAM pointer lookups with network latency bottlenecks.

Q3: How does Consistent Hashing prevent full cluster data rebalancing when adding a node?

Consistent Hashing maps both keys and virtual nodes to a 64-bit ring. Adding a physical node inserts vnodes on the ring, taking over key segments only from immediate neighbor nodes while leaving all other nodes unchanged!

Q4: What happens during Phase 1 of a Two-Phase Commit (2PC)?

The Coordinator asks each participant shard to prepare the transaction. Participants write mutations to local WAL, acquire row locks, and vote VOTE_COMMIT if ready or VOTE_ABORT if a conflict exists.

Q5: How do modern engines eliminate the 2PC single point of failure?

Modern engines (like CockroachDB and Spanner) group range partitions into Multi-Raft or Multi-Paxos consensus groups. If a coordinator node dies, Raft elects a new leader immediately to complete the 2PC commit.

Q6: What is a Scatter-Gather query in distributed databases?

A query executed without specifying a shard key. The query router must broadcast (scatter) the SQL request to every shard node, wait for responses, and merge (gather) the results before returning to client.

Q7: How does Range Partitioning perform dynamic splits?

When a range grows past a size threshold (e.g. 64MB), the engine splits the range key interval at the median key into two smaller range partitions and updates the global routing table.

Q8: What is key salting and when should you use it?

Key salting prepends a random hash prefix to high-traffic keys (like celebrity user accounts) to distribute their writes across multiple physical shards, preventing single-shard hotspot bottlenecks.

Q9: What is a Broadcast Join in a distributed query engine?

A join optimization where a small reference table is copied to all shard nodes. Each shard performs the join locally against its sharded data without transmitting large dataset rows across the network.

Q10: Why should you avoid auto-increment primary keys in sharded databases?

Auto-increment keys increase sequentially, causing 100% of write traffic to hit the highest key range partition node, destroying horizontal scaling benefits. Use UUIDv4 or K-ordered Snowflake IDs instead.

Q11: How does Vitess scale traditional MySQL architectures?

Vitess places stateless proxy routers (VTGate) in front of MySQL pools, using VSchema metadata to transparently route, split, and merge SQL queries across hundreds of underlying MySQL instances.

Q12: What role does Change Data Capture (CDC) play in sharded systems?

CDC platforms (like Debezium) stream write transaction logs out of individual shards into Kafka and analytical data warehouses, keeping heavy reporting queries away from operational OLTP database shards.

Q13: How does Google Spanner guarantee global External Consistency?

Google Spanner uses hardware GPS receivers and atomic clocks (TrueTime API) to bound clock uncertainty to $\le 1\text{ms}$, allowing distributed transactions to receive globally ordered commit timestamps without central locking.

Q14: How does online resharding migrate data without stopping writes?

Online resharding streams a baseline snapshot to target nodes, replays real-time CDC delta write logs until catch-up replication lag is minimal, and executes a sub-100ms atomic query router cutover flip.

Q15: What is a Hash Redistribute (Shuffle) Join?

A distributed join strategy where two sharded tables are dynamically re-hashed by the join key and transmitted across the network so matching join keys land on identical worker nodes for local processing.

Q16: How do distributed deadlock detectors identify cyclic locks?

Deadlock detectors build a global Wait-For directed graph where nodes represent transactions and edges represent lock dependencies. Detecting a cycle in the graph signals a deadlock, prompting an abort of the youngest transaction.

Q17: Why is virtual node (vnode) count important in Consistent Hashing?

Having too few vnodes causes uneven key distribution (data skew across physical servers). Assigning 100 to 256 vnodes per physical node mathematically guarantees uniform load distribution within a tight $\pm 5\%$ margin.

Q18: What is the difference between active-active and active-passive sharding?

Active-passive sharding routes writes for a shard to a single primary node while standby nodes replicate data. Active-active allows concurrent writes on multiple replica nodes using conflict resolution algorithms.

Q19: How does Citus extend PostgreSQL for horizontal scaling?

Citus partitions PostgreSQL tables into standard PostgreSQL tables distributed across worker nodes, using an extended query planner to parallelize SQL execution across the worker pool.

Q20: What is the impact of network partitions (CAP theorem) on sharded databases?

During a network partition, CP systems (CockroachDB, Spanner) reject writes on minority partitions to preserve consistency, while AP systems (Cassandra) accept writes and resolve conflicts later.

Q21: How do query routers handle SQL pagination across multiple shards?

For ORDER BY ... LIMIT N OFFSET M, the router fetches N + M rows from every shard in parallel, performs a global merge-sort in router memory, and discards the first M offset rows.

Q22: Why is multi-tenant B2B data ideal for horizontal sharding?

Multi-tenant SaaS applications naturally scope almost all queries by tenant_id or company_id. Using tenant_id as the Shard Key keeps 99% of transactions strictly single-shard!

Q23: What is the optimal storage range size in CockroachDB / Spanner?

Range sizes are typically kept between 64MB and 512MB. Smaller ranges allow fast background Raft transfers during node rebalancing, avoiding long disk scan freezes.

Q24: How does Vector Clock conflict resolution work in Cassandra?

Vector clocks attach a (node, counter) array to data updates, allowing distributed nodes to detect concurrent causal conflicts and present both versions (siblings) for client application reconciliation.


Written by Professor Pixel · CodingPancake Database Internals Series

Post a Comment

Previous Post Next Post