1. Context & Architecture Reality
Project Identity, Version & License Baseline
| Project Component | Canonical Repository | Pinned Release | Governance / License | Edition Boundary |
|---|---|---|---|---|
| TiDB | pingcap/tidb | v7.5 (LTS) | PingCAP (Apache 2.0) | Open source |
| TiKV | tikv/tikv | v7.5 (LTS) | CNCF Graduated (Apache 2.0) | Open source |
| CockroachDB | cockroachdb/cockroach | v23.2 | Cockroach Labs (BSL 1.1 / CCL) | Source-available / Commercial |
Open-Source vs. Source-Available Licensing Distinction: While TiKV is an OSI-approved open-source CNCF Graduated project licensed under Apache 2.0, and TiDB is maintained by PingCAP under Apache 2.0, CockroachDB is not OSI open source. CockroachDB transitioned from Apache 2.0 to the Business Source License (BSL 1.1) in 2019, and in 2024 transitioned to the proprietary CockroachDB Software License (CCL), requiring commercial enterprise licensing for organizations with annual revenues exceeding $10M. Architects evaluating distributed SQL must distinguish between permissively licensed open source (TiKV/TiDB) and source-available commercial engines (CockroachDB) when planning deployment topologies, embedded appliances, or multi-tenant database-as-a-service offerings.
1. Placement Driver (PD) / TSO & DISTRIBUTED TRANSACTIONAL KV ENGINE (TiKV / Pebble)
Establishes the ingress and structural boundary across Placement Driver (PD) / TSO & DISTRIBUTED TRANSACTIONAL KV ENGINE (TiKV / Pebble): Allocates monotonic timestamps, Region rebalancing; Continuous sorted byte array: Key-Value pairs with MVCC timestamps; Local storage: RocksDB / Pebble LSM engines on NVMe drives Full horizontal scaling: Add TiDB for compute or TiKV for storage independently!.
2. Phase 1: Prewrite (start_ts) & Phase 2: Commit (commit_ts)
Coordinates core data-plane execution and state transitions through Phase 1: Prewrite (start_ts) & Phase 2: Commit (commit_ts): 1. Designate Row 1 as Primary Lock; 2. Write Secondary Locks pointing to Primary; 1. Commit Primary Lock atomically.
3. Decoupled Compute and Storage Layers & Multi-Raft Range Partitioning (Regions)
Enforces durability, state convergence, and output verification across Decoupled Compute and Storage Layers, Multi-Raft Range Partitioning (Regions), Google Percolator 2PC Transaction Protocol, Distributed Time: TrueTime vs Hybrid Logical Clocks within the Distributed SQL Architecture: TiDB, TiKV and Multi-Raft Consensus architecture.
For thirty years, database design was split into two hostile camps:
- Relational Databases (RDBMS): PostgreSQL, MySQL, and Oracle offered ANSI SQL, rich joins, secondary indexes, and strong ACID guarantees. However, they were architected for single-node vertical scaling. When data exceeded a single physical server, teams were forced into complex manual sharding, losing distributed joins, multi-table transactions, and cross-shard referential integrity.
- NoSQL Databases: Cassandra, MongoDB, and DynamoDB scaled horizontally across hundreds of commodity nodes with automated sharding and high availability. However, they abandoned relational features, transactions, and foreign keys, pushing transaction orchestration and conflict resolution onto application developers.
In 2012, Google published Spanner, followed by F1 and Percolator, proving that horizontal scalability and strong ACID consistency were not mutually exclusive.
This inspired the Distributed SQL (NewSQL) movement, led by TiDB (PingCAP) and CockroachDB. These systems present a standard relational SQL interface to applications while decoupling the stateless compute layer from a distributed, auto-sharding transactional key-value storage engine powered by Multi-Raft.
Curriculum Bridge (INF-I06-001 CORE-M33-001):
Whereas INF-I06-001 introduced the foundational taxonomy of Distributed SQL (comparing Google Spanner’s TrueTime against Amazon Aurora’s shared-storage log architecture), this lesson dives into the open-source and source-available codebase internals of TiDB/TiKV (raftstore Multi-Raft state machines, Placement Driver TSO, and three-column-family Percolator MVCC in RocksDB) versus CockroachDB (Pebble LSM storage, Hybrid Logical Clock read-restart windows, Write Intents, and single-round-trip Parallel Commits).
+--------------------------------------------------------------------------------------------------+
| DISTRIBUTED SQL LAYERED ARCHITECTURE |
| |
| Client Applications (MySQL / PostgreSQL Driver) |
| | |
| v |
| +-----------------------------------------------------------------------------------------+ |
| | STATELESS COMPUTE LAYER (TiDB / Cockroach SQL) | |
| | - SQL Parser, Cost-Based Optimizer, Distributed Execution Engine | |
| | - Coprocessor Pushdown (Filters & Aggregations pushed to storage nodes) | |
| +-----------------------------------------------------------------------------------------+ |
| | |
| +-------------------+ (Allocates monotonic timestamps / manages Region balance) |
| | v |
| | +-------------------------------------------------------------+ |
| | | PLACEMENT DRIVER (PD) / TSO (3-node Raft Consensus Cluster) | |
| | +-------------------------------------------------------------+ |
| v |
| +-----------------------------------------------------------------------------------------+ |
| | DISTRIBUTED STORAGE LAYER (TiKV / Cockroach KV Nodes) | |
| | | |
| | Node 1 Node 2 Node 3 | |
| | +---------------------+ +---------------------+ +---------------------+ | |
| | | Region 1 (Leader) | | Region 1 (Follower) | | Region 1 (Follower) | [Raft Grp 1]| |
| | | Region 2 (Follower) | | Region 2 (Leader) | | Region 2 (Follower) | [Raft Grp 2]| |
| | | Region 3 (Follower) | | Region 3 (Follower) | | Region 3 (Leader) | [Raft Grp 3]| |
| | +---------------------+ +---------------------+ +---------------------+ | |
| | (Local Storage Engine: RocksDB / Pebble LSM-Tree on NVMe) | |
| +-----------------------------------------------------------------------------------------+ |
+--------------------------------------------------------------------------------------------------+
2. Core Problem & Failure Modes
Operating a distributed, transactional relational database creates four acute failure modes:
-
The Sequential Monotonic Key Hotspot: Distributed SQL maps relational tables into a single globally sorted byte-ordered key-value space:
table_id + index_id + row_id. If a table uses anAUTO_INCREMENTinteger or timestamp as its primary key, every newly inserted row has a key lexicographically greater than previous rows. Consequently, of writes land in the single Region covering the high end of the keyspace, choking a single node’s NVMe drive while dozens of cluster nodes sit idle. -
Distributed Deadlocks in Two-Phase Commit: When Transaction 1 (updating rows across Region 1 and Region 2) and Transaction 2 (updating rows across Region 2 and Region 1) run concurrently, both transactions can acquire locks on one key and block waiting for the other. Without active distributed deadlock detection (Wait-For-Graph cycle checking), both transactions stall indefinitely.
-
Stranded Secondary Locks from Client Crashes: Under the Percolator 2PC protocol, a client prewrites locks on multiple storage nodes. If the client machine crashes or suffers a power cut after prewriting secondary locks but before committing the Primary Lock, those keys remain locked. Subsequent readers and writers encountering the orphaned locks will stall until decentralized lock resolution cleans them up.
-
The False Horizon of Clock Skew (Stale Reads): CockroachDB uses Hybrid Logical Clocks. If an NTP daemon on Node 5 loses synchronization and drifts by (exceeding
max_offset), the node can read a snapshot that misses writes that causally preceded it, violating serializability. CockroachDB mitigates this by having nodes crash themselves (node panic) if clock drift exceeds configured tolerances.
3. Technical Deep Dive
Multi-Raft Range Partitioning & Region Splitting
Traditional distributed consensus engines (like etcd) run a single Raft consensus group. Every log entry passes through one leader, capping write throughput at approximately .
Distributed SQL solves this through Multi-Raft:
- The global keyspace is treated as an infinite, continuous byte array: .
- The space is partitioned into contiguous chunks called Regions (in TiKV) or Ranges (in CockroachDB). The default target size is .
- Each Region has 3 or 5 replicas distributed across distinct physical nodes, forming an independent Raft consensus group with its own leader, log, and state machine.
Continuous Key Space:
["", "apple") --> Region 1 (Leader: Node 1, Followers: Node 2, Node 3)
["apple", "pear") --> Region 2 (Leader: Node 2, Followers: Node 1, Node 4)
["pear", +infinity)--> Region 3 (Leader: Node 3, Followers: Node 2, Node 4)
Dynamic Split Mechanics
When writes cause Region 1’s size to exceed , the Region leader executes an internal Raft log entry: Split(split_key = "banana").
- Once committed by quorum, Region 1 splits locally into Region 1A
["", "banana")and Region 1B["banana", "apple"). - Zero data is moved across the network during a split; it is an instantaneous metadata modification in the underlying RocksDB/Pebble engine!
The Google Percolator Distributed 2PC Protocol
TiKV implements ACID transactions using the Percolator protocol (Peng and Dabek, 2010), a decentralized two-phase commit protocol layered on top of MVCC.
Every row key has three internal column families in RocksDB:
data:K_startTSActual value payload.lock:KActive lock containing(primary_key, startTS, TTL).write:K_commitTSCommit record containingstartTS, marking the row visible atcommitTS.
Protocol Execution Steps
Client (Coordinator) TiKV Node A (Primary) TiKV Node B (Secondary)
| | |
1. Acquire start_ts from TSO | |
| | |
2. Phase 1: PREWRITE | |
|-- Prewrite Primary (Key X, val, start_ts) ->| |
| [Writes lock:X, data:X_start_ts] | |
|<- OK --------------------------------------| |
| | |
|-- Prewrite Secondary (Key Y, val, start_ts, primary=X) ------------------------->|
| [Writes lock:Y pointing to X, data:Y_start_ts] |
|<- OK -------------------------------------------------------------------------|
| | |
3. Acquire commit_ts from TSO (commit_ts > start_ts) |
| | |
4. Phase 2: COMMIT | |
|-- Commit Primary (Key X, commit_ts) ----->| |
| [Removes lock:X, writes write:X_commit_ts] |
|<- OK (TRANSACTION COMMITTED!) -----------| |
| | |
|-- Async Commit Secondary (Key Y, commit_ts) ----------------------------------->|
| [Removes lock:Y, writes write:Y_commit_ts] |
Key Invariant: As soon as the Primary Lock is committed in Phase 2, the transaction is guaranteed to be committed. If the client crashes before committing Secondary Key Y, a concurrent reader encountering lock:Y follows the pointer to Key X. Finding Key X committed, the reader rolls Key Y forward itself!
TiDB/TiKV Two-Layer Percolator vs. CockroachDB Single-Layer Parallel Commits
While both engines rely on Multi-Raft over LSM storage, their transaction protocols make distinct latency and deployment trade-offs:
- TiDB + TiKV (Decoupled SQL + PD TSO + Percolator): Stateless TiDB SQL servers allocate
start_tsandcommit_tsfrom the Placement Driver (PD) Timestamp Oracle (TSO) (which pre-allocates timestamp windows in memory to serve millions of timestamps/sec). Classic Percolator requires two Raft write phases (Prewrite + Primary Commit), though TiDB 5.0+ adds 1PC (committing single-Region transactions in one Raft round) and Async Commit (calculatingcommit_tsvia per-Regionmax_tswithout waiting for a second TSO/Primary-Commit round before returning to the client). - CockroachDB (Unified Single Binary + HLC + Parallel Commits): Every CockroachDB node runs both the PostgreSQL-compatible SQL gateway and the Pebble KV/Multi-Raft store. Instead of three RocksDB column families, CockroachDB writes provisional Write Intents inline in Pebble pointing to a single Transaction Record (
PENDINGSTAGINGCOMMITTED). Using Parallel Commits (Taft et al., SIGMOD ‘20), the coordinator pipelines all Write Intents and transitions the Transaction Record toSTAGINGconcurrently, committing cross-Range transactions in a single Multi-Raft consensus round-trip once all intent writes and the staging record succeed.
Distributed Time: TrueTime vs Hybrid Logical Clocks
To support Snapshot Isolation () and Strict Serializability without lock contention on reads, distributed databases require monotonically increasing timestamps across nodes.
In CockroachDB’s Hybrid Logical Clock (HLC) (Kulkarni et al., 2014), each node maintains tuple , where tracks the highest physical wall-clock timestamp observed so far and is a logical tie-breaker counter. Upon receiving an RPC carrying at local physical wall time , the recipient updates its clock via the four-branch rule:
Numerical Verification: Suppose Node A sends a write RPC stamped to Node B whose local clock lags at with . Node B computes and hits branch 3 (), setting . The resulting timestamp is strictly lexicographically greater than both and , preserving happens-before causality despite of backward NTP drift.
4. System Design Patterns & Trade-offs
+----------------------------------------------------------------------------------------------------+
| TRADEOFF MATRIX: NEWSQL ARCHITECTURAL DESIGNS |
+--------------------------+------------------------------------+------------------------------------+
| Dimension | TiDB / TiKV (Decoupled Compute/KV) | CockroachDB (Unified Node Binary) |
+--------------------------+------------------------------------+------------------------------------+
| Deployment Simplicity | Complex (Separate TiDB, TiKV, PD) | Simple (Single binary on all nodes)|
| Independent Scalability | High (Scale SQL CPU without disk) | Moderate (Scale nodes uniformly) |
| Timestamp Generation | Centralized TSO (Placement Driver) | Decentralized HLC (Pure software) |
| Protocol Compatibility | Native MySQL Protocol | Native PostgreSQL Protocol |
+--------------------------+------------------------------------+------------------------------------+
| Dimension | Shared-Storage (Amazon Aurora) | Shared-Nothing (TiDB / Cockroach) |
+--------------------------+------------------------------------+------------------------------------+
| Write Scalability | Single write-master node limit | Unbounded horizontal Multi-Raft |
| SQL Compatibility | 100% native PostgreSQL/MySQL engine| High, but some DDL edge differences|
| Cross-Region Active-Act | Read replicas only | Multi-region active-active tables |
+--------------------------+------------------------------------+------------------------------------+
5. Practical Implementation
Below is a complete TypeScript simulation of a Multi-Raft Range Manager and Percolator 2PC Transaction Engine. It implements continuous key range routing, region splitting, and primary/secondary lock resolution.
export interface KeyValueMVCC {
key: string;
value: string;
startTs: number;
commitTs?: number;
}
export interface LockRecord {
key: string;
primaryKey: string;
startTs: number;
ttlMs: number;
}
/**
* Single Multi-Raft Region covering key range [startKey, endKey)
*/
export class RaftRegion {
public data: Map<string, KeyValueMVCC[]> = new Map();
public locks: Map<string, LockRecord> = new Map();
constructor(
public readonly regionId: number,
public startKey: string,
public endKey: string,
public leaderNode: string
) {}
public containsKey(key: string): boolean {
const afterStart = this.startKey === '' || key >= this.startKey;
const beforeEnd = this.endKey === '' || key < this.endKey;
return afterStart && beforeEnd;
}
}
/**
* NewSQL Distributed Storage Engine Simulator
*/
export class DistributedSqlEngine {
private regions: RaftRegion[] = [];
private tsoClock: number = 1000;
private nextRegionId: number = 3;
constructor() {
// Initial cluster starts with 2 regions
this.regions.push(new RaftRegion(1, '', 'm', 'node-1'));
this.regions.push(new RaftRegion(2, 'm', '', 'node-2'));
}
public getTimestamp(): number {
return ++this.tsoClock;
}
public locateRegion(key: string): RaftRegion {
const region = this.regions.find((r) => r.containsKey(key));
if (!region) throw new Error(`Routing error: key ${key} has no owning region`);
return region;
}
/**
* Splits an existing Multi-Raft Region at splitKey with zero network data movement
*/
public splitRegion(splitKey: string, newLeaderNode: string = 'node-3'): {
leftRegion: RaftRegion;
rightRegion: RaftRegion;
} {
const target = this.locateRegion(splitKey);
if (splitKey === target.startKey) {
throw new Error(`Invalid splitKey '${splitKey}': matches region start boundary`);
}
const originalEndKey = target.endKey;
target.endKey = splitKey;
const rightRegion = new RaftRegion(
this.nextRegionId++,
splitKey,
originalEndKey,
newLeaderNode
);
// Partition existing MVCC versions and locks across [startKey, splitKey) and [splitKey, originalEndKey)
for (const [k, versions] of target.data.entries()) {
if (k >= splitKey) {
rightRegion.data.set(k, versions);
target.data.delete(k);
}
}
for (const [k, lock] of target.locks.entries()) {
if (k >= splitKey) {
rightRegion.locks.set(k, lock);
target.locks.delete(k);
}
}
this.regions.push(rightRegion);
this.regions.sort((a, b) => a.startKey.localeCompare(b.startKey));
return { leftRegion: target, rightRegion };
}
/**
* Executes Percolator 2-Phase Commit
*/
public executeTransaction(mutations: { key: string; value: string }[]): boolean {
if (mutations.length === 0) return true;
const startTs = this.getTimestamp();
const primaryMutation = mutations[0];
const secondaryMutations = mutations.slice(1);
// ==========================================
// PHASE 1: PREWRITE
// ==========================================
// 1. Prewrite Primary Key
const primaryRegion = this.locateRegion(primaryMutation.key);
if (primaryRegion.locks.has(primaryMutation.key)) {
throw new Error(`Write conflict: primary key ${primaryMutation.key} is locked`);
}
primaryRegion.locks.set(primaryMutation.key, {
key: primaryMutation.key,
primaryKey: primaryMutation.key,
startTs,
ttlMs: 5000
});
// 2. Prewrite Secondary Keys
const acquiredSecondaries: { key: string; region: RaftRegion }[] = [];
try {
for (const mut of secondaryMutations) {
const region = this.locateRegion(mut.key);
if (region.locks.has(mut.key)) {
throw new Error(`Write conflict: secondary key ${mut.key} is locked`);
}
region.locks.set(mut.key, {
key: mut.key,
primaryKey: primaryMutation.key, // Points to primary!
startTs,
ttlMs: 5000
});
acquiredSecondaries.push({ key: mut.key, region });
}
} catch (err) {
// Rollback prewrites on conflict
primaryRegion.locks.delete(primaryMutation.key);
for (const item of acquiredSecondaries) {
item.region.locks.delete(item.key);
}
return false;
}
// ==========================================
// PHASE 2: COMMIT
// ==========================================
const commitTs = this.getTimestamp();
// 1. Commit Primary (The point of no return)
primaryRegion.locks.delete(primaryMutation.key);
if (!primaryRegion.data.has(primaryMutation.key)) {
primaryRegion.data.set(primaryMutation.key, []);
}
primaryRegion.data.get(primaryMutation.key)!.push({
key: primaryMutation.key,
value: primaryMutation.value,
startTs,
commitTs
});
// 2. Asynchronously commit secondaries
for (const item of acquiredSecondaries) {
item.region.locks.delete(item.key);
const mut = secondaryMutations.find((m) => m.key === item.key)!;
if (!item.region.data.has(item.key)) {
item.region.data.set(item.key, []);
}
item.region.data.get(item.key)!.push({
key: item.key,
value: mut.value,
startTs,
commitTs
});
}
return true;
}
/**
* Reads data with Snapshot Isolation at current snapshot timestamp
*/
public readSnapshot(key: string): string | null {
const readTs = this.getTimestamp();
const region = this.locateRegion(key);
// If key is locked with startTs <= readTs, check primary for resolution
const lock = region.locks.get(key);
if (lock && lock.startTs <= readTs) {
throw new Error(`Locked key encountered: resolution required against primary ${lock.primaryKey}`);
}
const versions = region.data.get(key) || [];
// Find newest version committed at or before readTs
const valid = versions
.filter((v) => v.commitTs !== undefined && v.commitTs <= readTs)
.sort((a, b) => b.commitTs! - a.commitTs!);
return valid.length > 0 ? valid[0].value : null;
}
}
function runDemo(): void {
console.log('=== TiDB / TiKV & CockroachDB: Multi-Raft & Percolator 2PC ===');
const engine = new DistributedSqlEngine();
// Seed balances across Region 1 ["", "m") and Region 2 ["m", "")
engine.executeTransaction([
{ key: 'acct_alice', value: '1000' },
{ key: 'merchant_bob', value: '500' },
]);
console.log(
`Initial Region routing: 'acct_alice' -> Region ${engine.locateRegion('acct_alice').regionId}, ` +
`'merchant_bob' -> Region ${engine.locateRegion('merchant_bob').regionId}`
);
// Split Region 1 ["", "m") at splitKey "f" into Region 1 ["", "f") and Region 3 ["f", "m")
const { leftRegion, rightRegion } = engine.splitRegion('f', 'node-3');
console.log(
`Executed Multi-Raft Split at key 'f': Region ${leftRegion.regionId} ["${leftRegion.startKey}", "${leftRegion.endKey}") ` +
`& Region ${rightRegion.regionId} ["${rightRegion.startKey}", "${rightRegion.endKey}")`
);
// Atomic cross-Region transfer ($100 from acct_alice to merchant_bob)
const txSuccess = engine.executeTransaction([
{ key: 'acct_alice', value: '900' },
{ key: 'merchant_bob', value: '600' },
]);
console.log('Cross-Region Percolator 2PC transfer committed: ' + txSuccess);
console.log(
`Snapshot Isolation balances: acct_alice=$${engine.readSnapshot('acct_alice')}, ` +
`merchant_bob=$${engine.readSnapshot('merchant_bob')}`
);
}
runDemo();
Simulation Execution Output & State Transitions
=== TiDB / TiKV & CockroachDB: Multi-Raft & Percolator 2PC ===
Initial Region routing: 'acct_alice' -> Region 1, 'merchant_bob' -> Region 2
Executed Multi-Raft Split at key 'f': Region 1 ["", "f") & Region 3 ["f", "m")
Cross-Region Percolator 2PC transfer committed: true
Snapshot Isolation balances: acct_alice=$900, merchant_bob=$600
6. Failure Scenarios & Edge Cases
1. Monotonic Primary Key Hot Region Overload
An application creates a user table with an auto-incrementing integer id BIGINT AUTO_INCREMENT. All 20,000 new user signups per minute have IDs . Every write routes to the highest Region in the cluster. Even though the cluster has 60 servers, a single server handles 100% of write traffic, bottlenecking write throughput.
- Mitigation: Use
AUTO_RANDOM(TiDB) orUUIDv4primary keys.AUTO_RANDOMassigns random bits to the high-order bits of the integer, dispersing sequential IDs uniformly across the entire continuous keyspace.
2. Coprocessor OOM Under Unindexed Range Scans
A client issues SELECT * FROM orders WHERE status = 'PENDING' without an index on status. TiDB pushes the filter down to all TiKV nodes via the Coprocessor. Each TiKV node scans its local RocksDB store. If a single Region contains millions of records, the coprocessor can consume gigabytes of off-heap memory parsing table records, causing Linux to trigger an Out-Of-Memory (OOM) kill on the storage node.
- Mitigation: Mandate composite secondary indexes. Enforce query execution memory limits (
tidb_mem_quota_query = 1GB).
7. Production Operations & Observability
Critical Placement Driver (PD) Schedulers
The Placement Driver continuously analyzes cluster metrics and dispatches balancing operators:
balance-leader-scheduler: Migrates Raft leaders so CPU utilization is evenly distributed across all nodes.balance-region-scheduler: Moves Region peer replicas to balance NVMe disk storage across physical machines.hot-region-scheduler: Detects Regions with abnormally high read/write QPS and re-routes replicas to cool down overloaded nodes.
8. Security & Compliance
- mTLS Wire Encryption: Ensure
security.cluster-ssl-cais configured on all TiDB, TiKV, and PD nodes. All internal gRPC Raft message traffic must use mutual TLS with short-lived X.509 certificates. - Transparent Data Encryption (TDE): Enable hardware-accelerated AES-256 encryption on RocksDB SSTables and Raft log files to satisfy PCI-DSS and HIPAA data-at-rest controls.
9. Real-World Case Studies
Pinterest Graph Storage on TiKV
Pinterest migrated massive graph caching workloads to TiKV.
- By decoupling TiDB (stateless compute) from TiKV (storage), Pinterest scaled their caching infrastructure to over .
- Coprocessor pushdown reduced network data transfer by , because filtering and counting operations executed directly inside TiKV rather than streaming gigabytes of raw records over the internal network.
10. Technology Comparison Matrix
+----------------------------------------------------------------------------------------------------+
| DISTRIBUTED SQL PLATFORM MATRIX |
+----------------------+--------------------+--------------------+-----------------------------------+
| Platform | Consensus Protocol | Time Mechanism | Storage Engine |
+----------------------+--------------------+--------------------+-----------------------------------+
| TiDB / TiKV | Multi-Raft | Placement Driver | RocksDB LSM-Tree (C++ via Rust |
| | | (Centralized TSO) | FFI in TiKV) |
+----------------------+--------------------+--------------------+-----------------------------------+
| CockroachDB | Multi-Raft | Hybrid Logical | Pebble LSM-Tree (Go) |
| | | Clocks (HLC) | |
+----------------------+--------------------+--------------------+-----------------------------------+
| Google Spanner | Multi-Paxos | TrueTime (GPS and | Proprietary Log-Structured Store |
| | | Atomic Clocks) | |
+----------------------+--------------------+--------------------+-----------------------------------+
| YugabyteDB | Multi-Raft | Hybrid Logical | DocDB (Modified RocksDB in C++) |
| | | Clocks (HLC) | |
+----------------------+--------------------+--------------------+-----------------------------------+
11. Design Challenge & Exercise
Scenario: High-Volume Financial Ledger on NewSQL
You are designing the core ledger database for a digital bank with 10,000,000 accounts. Account transactions must be strictly serializable.
Requirements:
- Zero balance overdraft: If Account A transfers $100 to Account B, both balance updates must commit atomically or both fail.
- High contention: Merchant accounts (e.g., Walmart, Amazon) process 5,000 incoming payments per second on a single account ID.
Your Task:
- Design the SQL schema and primary key strategy to avoid monotonic key hotspots.
- Trace how Percolator 2PC handles concurrent payments to the same merchant account.
- Propose a conflict-mitigation pattern (such as sub-ledger sharding) to eliminate transaction rollback storms on hot accounts.
View Reference Architecture & Worked Solutions (CORE-M33-001)
1. SQL Schema & Primary Key Strategy to Prevent Monotonic Hotspots
- Primary Key Dispersion: Using a sequential
BIGINT AUTO_INCREMENTonledger_entriesoraccountsforces of new inserts into the rightmost Multi-Raft Region. In TiDB, useAUTO_RANDOM(5)(which scatters the top 5 bits of the 64-bit integer across key-range shards while keeping the remaining bits monotonic per shard) orSHARD_ROW_ID_BITSon non-clustered tables; in CockroachDB, useUUIDv4(gen_random_uuid()) or a hash-sharded index.
-- 1. Core Customer & Merchant Accounts (Dispersed across Regions by UUID / AUTO_RANDOM)
CREATE TABLE accounts (
account_id BIGINT PRIMARY KEY AUTO_RANDOM(5),
account_type VARCHAR(16) NOT NULL, -- 'CUSTOMER' | 'MERCHANT'
balance_cents BIGINT NOT NULL CHECK (balance_cents >= 0),
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
) PRE_SPLIT_REGIONS = 32;
-- 2. Immutable Double-Entry Audit Ledger (Idempotent by client_tx_id)
CREATE TABLE ledger_entries (
entry_id BIGINT PRIMARY KEY AUTO_RANDOM(5),
client_tx_id UUID NOT NULL,
debit_acct_id BIGINT NOT NULL,
credit_acct_id BIGINT NOT NULL,
amount_cents BIGINT NOT NULL CHECK (amount_cents > 0),
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE INDEX idx_idempotency (client_tx_id)
) PRE_SPLIT_REGIONS = 64;
2. Percolator 2PC Execution Trace Under Concurrent Merchant Contention
- Single-Transfer Trace (Customer Merchant ):
- Start Timestamp: Coordinator obtains
start_ts = 1001from PD TSO and readsaccounts(A)(balance_cents >= 10000) andaccounts(M). - Phase 1 (Prewrite): Coordinator designates
accounts(A)as the Primary Key andaccounts(M)+ledger_entries(E)as Secondary Keys. Each key’s owning TiKV Region checks two RocksDB column families:- Write-Write Conflict Check (
CF_WRITE): Scans for any committed record withcommit_ts > start_ts (1001). If found, another transaction modified the account afterstart_ts, so Prewrite aborts withWriteConflict. - Active Lock Check (
CF_LOCK): Checks iflock:Kalready exists. If free, TiKV writesdata:K_1001andlock:K(with secondary locks storing a pointerprimary = accounts(A)).
- Write-Write Conflict Check (
- Phase 2 (Commit): Coordinator obtains
commit_ts = 1005from TSO and sendsCommit(accounts(A), commit_ts = 1005)to the primary key’s Region. Once Raft commits replacinglock:Awithwrite:A_1005 -> 1001, the transfer is atomically and irrevocably committed. Secondary locks onaccounts(M)andledger_entries(E)are committed asynchronously.
- Start Timestamp: Coordinator obtains
- Why to One Merchant Row Collapses:
- Even though the customer debit keys
accounts(A_i)are spread across dozens of Regions, every transaction also mutates the exact same secondary keyaccounts(merchant_amazon). - At ( average inter-arrival time) and a Multi-Raft Prewrite + Commit round-trip duration of , concurrent transactions overlap on
lock:merchant_amazonat any millisecond. Under Optimistic 2PC, out of fail withWriteConflict; under Pessimistic locking (SELECT ... FOR UPDATE), all serialize in a single FIFO wait queue on one Raft leader, capping throughput at .
- Even though the customer debit keys
3. Sub-Ledger Sharding Pattern (Write-Sharded Buckets) to Eliminate Hot-Account Rollbacks
- Architectural Fix — Sharded Merchant Sub-Account Buckets:
- Split high-volume merchant accounts across (or ) sub-ledger rows in a
merchant_sub_accountstable keyed by(bucket_hash_prefix, merchant_id, bucket_id)wherebucket_id:CREATE TABLE merchant_sub_accounts ( shard_prefix INT NOT NULL, -- crc32(merchant_id, bucket_id) % 1024 merchant_id BIGINT NOT NULL, bucket_id SMALLINT NOT NULL, -- 0 .. 63 balance_cents BIGINT NOT NULL DEFAULT 0, PRIMARY KEY (shard_prefix, merchant_id, bucket_id) ) PRE_SPLIT_REGIONS = 64; - Write Path (Customer Payment):
- Pessimistically lock and debit the unsharded customer row
accounts(customer_id)(WHERE balance_cents >= amount_cents)—enforcing strict zero-overdraft serializability on the customer side where contention per individual human is near zero. - Credit a pseudorandomly selected merchant bucket
bucket_id = Hash(client_tx_id) % 64inmerchant_sub_accounts. - Because incoming credits are strictly additive (, cannot overdraft the merchant) and distributed uniformly across distinct keys in separate Multi-Raft Regions, per-bucket contention drops from to per Region—easily sustained with sub-10ms latency and zero rollback storms!
- Pessimistically lock and debit the unsharded customer row
- Read / Settlement Path (Merchant Balance & Payouts):
- Querying the merchant’s real-time balance executes a parallel Coprocessor pushdown
SELECT SUM(balance_cents) FROM merchant_sub_accounts WHERE merchant_id = ?at a single MVCC snapshot timestamp (read_ts), returning an exact linearizable sum across all buckets.
- Querying the merchant’s real-time balance executes a parallel Coprocessor pushdown
- Split high-volume merchant accounts across (or ) sub-ledger rows in a
12. Assessment
Review the 4 scenario-based quiz questions at the top of this lesson to test your mastery of NewSQL architectures, Multi-Raft range partitioning, and Percolator distributed transactions.
13. Summary & Architectural Decision Guide
- TiDB / TiKV Architecture: Separates stateless SQL compute (TiDB) from Multi-Raft key-value storage (TiKV). Relies on centralized Placement Driver (PD) and Timestamp Oracle (TSO) for Google Percolator 2PC transactions.
- CockroachDB Architecture: Unified node architecture where every node runs SQL execution and Multi-Raft Range storage. Eliminates centralized TSO using Hybrid Logical Clocks (HLC) and read-restart uncertainty windows.
- When to Choose TiDB: MySQL compatibility, massive analytical hybrid HTAP queries via TiFlash columnar replicas, and high write volumes.
- When to Choose CockroachDB: PostgreSQL wire compatibility, multi-cloud and cross-region survival, and operational simplicity (single binary).
14. Authoritative References & Version Context
- Peng and Dabek (2010), “Large-scale Incremental Processing Using Distributed Transactions and Notifications”, USENIX OSDI ‘10 (Google Percolator) — usenix.org
- Dongxu Huang et al. (2020), “TiDB: A Raft-based HTAP Database”, VLDB ‘20 — doi.org/10.14778/3415478.3415535
- Rebecca Taft et al. (2020), “CockroachDB: The Resilient Geo-Distributed SQL Database”, ACM SIGMOD ‘20 — doi.org/10.1145/3318464.3386134
- Version Context: Architecture reflects TiDB 7.5 LTS / TiKV 7.5 (Multi-Raft Regions) and CockroachDB 23.x / 24.x LTS (HLC Raft Ranges).