Skip to main content
Distributed Database Systems Concept

Distributed Database Systems

This module covers the distributed systems concepts essential for engineers joining teams at Vitess, PlanetScale, CockroachDB, TiDB, Neon, or building distributed database features at any company.
Target Audience: Distributed database engineers
Prerequisites: Storage Engine, Performance Engineering modules
Key Comparisons: PostgreSQL, Vitess, CockroachDB, Spanner, TiDB
Interview Relevance: Staff+ distributed systems roles

Part 1: Replication Fundamentals

Replication is the foundation of high availability and read scalability in distributed databases. It involves keeping a copy of the same data on multiple machines that are connected via a network. Real-world analogy: Replication is like a chain of notaries who all maintain identical copies of an official ledger. When someone makes a change, the original notary (primary) writes it down and sends a copy to the other notaries (replicas). The fundamental tension is between speed and accuracy: do you tell the client “done!” as soon as the primary writes it (fast but risky — what if the primary’s building burns down?), or do you wait until at least one other notary confirms they have the copy (slower but safer)? That is the core trade-off between asynchronous and synchronous replication.

1.0 The CAP Theorem

The CAP theorem states that a distributed data store can only provide two of the following three guarantees: Consistency, Availability, and Partition Tolerance. Since network partitions are inevitable in distributed systems, we must choose between Consistency (CP) and Availability (AP). Real-world analogy: Imagine a company with offices in New York and London. The phone line between them is the network. CAP says: if the phone line goes down (partition), you must choose. Either both offices stop accepting orders until the line is restored (Consistency — both offices agree on the state), or both offices keep accepting orders independently and reconcile the mess later (Availability — the system stays up but may have conflicts). You cannot have both during the partition. The nuance that CAP does not capture: in practice, partitions are rare, and the real engineering question is the latency-consistency trade-off during normal operation. CAP Theorem

1.1 Replication Topologies

How do we decide which node accepts writes and how data flows between nodes? This choice fundamentally impacts the system’s consistency and availability characteristics. Replication Topologies
  • Single Leader: All writes go to one node (Leader). Simple to reason about but the leader is a bottleneck and single point of failure for writes. (e.g., PostgreSQL, MySQL).
  • Multi-Leader: Writes can be accepted by multiple nodes. Great for multi-region setups but requires complex conflict resolution. (e.g., DynamoDB Global Tables).
  • Leaderless: Writes are sent to multiple nodes (quorum). High availability but eventual consistency and “read repair” are needed. (e.g., Cassandra, Dynamo).

1.2 PostgreSQL Streaming Replication

PostgreSQL uses physical replication, streaming the Write-Ahead Log (WAL) records from the primary to the standby. This is efficient and ensures the standby is an exact physical copy of the primary. Streaming Replication

1.3 Replication Lag

Replication lag is the delay between a write happening on the primary and it being visible on the standby. It is the enemy of read-your-writes consistency.

1.4 Conflict Handling

When running read-only queries on a standby, conflicts can occur with the incoming replication stream. For example, a query might be reading a row that the primary has just deleted.

Part 2: Consensus Algorithms

In distributed systems, we often need multiple nodes to agree on a value (e.g., “who is the leader?” or “what is the next log entry?”). This is the problem of consensus. Real-world analogy: Consensus is like a group of friends trying to agree on a restaurant over a flaky group chat. Messages can be delayed, duplicated, or lost. One person (the leader) proposes a restaurant. The others reply “yes” or “no.” If a majority says “yes,” the decision is final — even if some friends never got the message. If the proposer’s phone dies, someone else eventually steps up and proposes again. The key insight: you need a majority (quorum), not unanimity. Three out of five is enough because any two majorities must share at least one member, ensuring no contradictory decisions can both succeed.

2.1 Raft Fundamentals

Raft is a consensus algorithm designed to be easy to understand. It decomposes the problem into leader election, log replication, and safety. Raft Consensus Flow Raft Leader Election Process:
  1. Follower timeout: If follower doesn’t hear from leader, becomes candidate
  2. Request votes: Candidate increments term and requests votes from peers
  3. Vote granting: Followers vote for first candidate in each term
  4. Majority wins: Candidate with majority becomes leader
  5. Heartbeats: Leader sends periodic heartbeats to maintain authority

2.2 Raft Implementation Details

Raft ensures that if any machine applies a log entry to its state machine, no other machine will ever apply a different command for the same log index.

2.3 Multi-Paxos vs Raft

While Raft is popular for its clarity, Paxos (specifically Multi-Paxos) is the historical standard. Both achieve the same goal but differ in terminology and flexibility.

2.4 Raft in CockroachDB

CockroachDB uses Raft not just for the whole cluster, but for each individual “Range” (shard) of data. This allows for fine-grained high availability and consistency.

Part 3: Sharding Strategies

When a dataset becomes too large for a single node, we must split it across multiple nodes. This process is called sharding (or partitioning). Real-world analogy: Sharding is like organizing a massive library across multiple buildings. You need a rule for which building holds which books. Hash sharding is like assigning buildings by the hash of the book’s ISBN — perfectly even distribution, but if someone asks for “all books by Author X,” you must visit every building. Range sharding is like putting A-M in Building 1 and N-Z in Building 2 — great for browsing a range of authors, but if most authors’ last names start with ‘S’, Building 2 is overcrowded (hot spot). Directory sharding keeps a master catalog that says exactly where each book is — flexible but the catalog itself becomes a bottleneck. Performance pitfall — cross-shard queries: The moment a query touches more than one shard, performance degrades dramatically. The system must scatter the query to all relevant shards, wait for the slowest one, and gather results. A query that takes 5ms on a single node can take 50ms when scattered across 10 shards. The golden rule of sharding: design your schema so that the most common queries hit a single shard. Co-locate related data by choosing a sharding key that matches your access patterns (e.g., tenant_id for multi-tenant SaaS).

3.1 Sharding Approaches

There are several ways to determine which shard a particular row belongs to. Sharding Strategies
  • Key-Based (Hash): shard_id = hash(key) % num_shards. Even distribution, but resharding is expensive and range queries are inefficient.
  • Range-Based: shard_id determined by key ranges (e.g., A-M, N-Z). Efficient range queries, but prone to “hot spots” if keys are sequential (e.g., timestamps).
  • Directory-Based: A lookup service maps keys to shards. Flexible placement, but the lookup service is a bottleneck.

3.2 Vitess Architecture

Vitess is a database clustering system for horizontal scaling of MySQL. It abstracts the sharding complexity from the application. Vitess Architecture
  • VTGate: A stateless proxy that routes queries to the correct shard(s). It speaks the MySQL protocol.
  • VTTablet: A sidecar process that runs alongside each MySQL instance, managing it and handling replication.
  • Topology Service: Stores the cluster configuration (keyspaces, shards) in a consistent store like etcd or ZooKeeper.

3.3 Vindex (Vitess Index)

A Vindex is a mapping that tells Vitess how to route a query based on a column value. It’s essentially the sharding key definition.

3.4 Resharding

Resharding is the process of changing the number of shards, usually splitting one shard into two as data grows. Vitess handles this online with minimal downtime using VReplication. Resharding Process

Part 4: Distributed Transactions

When a transaction spans multiple shards, we need a protocol to ensure atomicity: either all shards commit, or none do. Real-world analogy: A distributed transaction is like coordinating a multi-party real estate closing. The buyer, seller, bank, and title company all need to sign simultaneously. If any party backs out, the whole deal is off. Two-Phase Commit (2PC) is the closing agent who first asks everyone “Are you ready to sign?” (Prepare phase). If everyone says yes, the agent says “Sign now” (Commit phase). The danger: if the closing agent has a heart attack after everyone said “ready” but before saying “sign,” all parties are stuck — they cannot proceed or back out until the agent recovers. That blocking problem is why 2PC is the “necessary evil” of distributed databases.

4.1 Two-Phase Commit (2PC)

The standard protocol for distributed atomic commit. It involves a Coordinator and multiple Participants. Two-Phase Commit
  1. Prepare Phase: Coordinator asks all participants “Can you commit?”. Participants lock resources and persist their vote.
  2. Commit Phase: If all say “Yes”, Coordinator tells everyone to “Commit”. If any say “No” (or timeout), Coordinator tells everyone to “Abort”.

4.2 Spanner’s TrueTime

Google Spanner achieves external consistency (linearizability) at a global scale using TrueTime, which exposes time as an interval of uncertainty. TrueTime and Spanner
  • TrueTime API: Returns [earliest, latest]. The actual time is guaranteed to be within this interval.
  • Commit Wait: A transaction Ti waits until TT.now().earliest > Ti.commit_timestamp before reporting success. This ensures that if T2 starts after T1 finishes, T2 will definitely see T1’s effects.

4.3 CockroachDB’s Hybrid-Logical Clocks

CockroachDB cannot rely on atomic clocks like Spanner. Instead, it uses Hybrid Logical Clocks (HLC) to provide causality tracking and loose time synchronization. Real-world analogy: HLC is like a wall clock with a sticky note. The wall clock (physical component) shows real time but might be slightly off across offices. The sticky note (logical counter) tracks “I received a message stamped at 3:00:05 but my clock says 3:00:03, so I bump my sticky note to say I’m logically after 3:00:05.” This way, causal ordering is preserved even when physical clocks disagree. The trade-off vs. TrueTime: HLC does not require GPS/atomic hardware, but it creates “uncertainty windows” where a reader might encounter a write whose timestamp could be in the future, forcing a transaction restart. Hybrid-Logical Clocks
  • Physical Component: Wall clock time (NTP).
  • Logical Component: A counter to order events that happen within the same physical tick or when clocks move backwards.
  • Result: Provides “causal consistency” and allows for efficient snapshot isolation.

4.4 Distributed Transactions in Vitess

Vitess supports different transaction modes depending on the consistency requirements.

Part 5: CockroachDB Deep Dive

CockroachDB is a distributed SQL database built on top of a key-value store (RocksDB) using Raft for consensus.

5.1 Range Architecture

Data is divided into contiguous chunks called Ranges. Each Range is a Raft group. CockroachDB Range Architecture

5.2 CockroachDB Transaction Flow

CockroachDB uses a decentralized transaction model with “Write Intents”.

5.3 Read-Write Conflicts

Since transactions are distributed, conflicts are resolved using timestamps and priorities.

Part 6: Interview Questions

Distributed Systems Deep Dive

Key Design Points:
  1. Data Placement
    • Shard data by primary key range
    • Each shard replicated across zones/regions
    • Paxos or Raft for consensus per shard
  2. Time Synchronization
    • GPS + atomic clocks for TrueTime (Spanner approach)
    • Or HLC with uncertainty intervals (CockroachDB approach)
    • Critical for external consistency
  3. Transactions
    • Lock-free reads at snapshot timestamp
    • Pessimistic locking for writes
    • 2PC for cross-shard transactions
    • Commit wait to ensure external consistency
  4. Read Optimization
    • Stale reads from local replica (bounded staleness)
    • Strong reads require leader/leaseholder
    • Follower reads with clock check
  5. Schema Management
    • Online schema changes (like PG’s concurrent operations)
    • Distributed DDL coordination
    • Schema version tracked per shard
Vitess:
  • Middleware layer over MySQL
  • Application does sharding logic (VTGate)
  • MySQL handles storage (InnoDB)
  • Pros: Leverage MySQL ecosystem, operational simplicity per shard
  • Cons: 2PC for cross-shard, no distributed ACID by default
CockroachDB:
  • Ground-up distributed SQL
  • Raft consensus per range (~512MB)
  • RocksDB (LSM) for storage
  • HLC for timestamp ordering
  • Pros: Strong consistency, automatic sharding
  • Cons: Write amplification from LSM, HLC complexity
TiDB:
  • Hybrid: SQL layer (TiDB) + storage layer (TiKV)
  • Raft per region (96MB default)
  • RocksDB for storage (TiKV)
  • Percolator-style transactions
  • Pros: PD for scheduling, analytics with TiFlash
  • Cons: Complexity of multiple components
When to choose:
  • Vitess: Existing MySQL, horizontal read scaling
  • CockroachDB: Greenfield, strong consistency required
  • TiDB: Mixed OLTP/OLAP, MySQL compatibility
Scenario: Node fails mid-transactionTransaction Record Approach:
  1. Each transaction has a “transaction record” stored in a range
  2. Record tracks: status (PENDING/COMMITTED/ABORTED), timestamp, intents
  3. If coordinator fails, transaction record remains
Recovery Process:
  1. Other transactions encountering intents check transaction record
  2. If record says COMMITTED: resolve intent as committed
  3. If record says ABORTED: delete intent
  4. If record says PENDING and heartbeat expired:
    • Push transaction timestamp or abort it
    • Garbage collect stale intents
Lease Holder Failure:
  1. Raft detects leader failure (~3-10 seconds)
  2. New leader elected
  3. Uncommitted Raft entries replayed
  4. Transactions on failed node:
    • Can retry from any other node
    • Intents still visible, resolved based on txn record
Key Insight:
  • No transaction coordinator bottleneck
  • Any node can resolve intents using txn record
  • Heartbeats prevent zombie transactions
Requirements Analysis:
  • Tenant isolation
  • Tenant-level scaling
  • Cross-tenant queries (admin/analytics)
  • Even distribution
Strategy: Tenant-based sharding:
Handling large tenants:
  • Sub-sharding by (tenant_id, secondary_key)
  • Example: (tenant_id, order_date) for time-based queries
  • Or dedicated shard per large tenant
Handling small tenants:
  • Hash tenant_id for even distribution
  • Many small tenants share shards
  • No hot shard issues
Cross-tenant queries:
  • Scatter-gather (expensive but possible)
  • Materialized views in analytics system
  • Separate OLAP store (TiFlash, ClickHouse)
Migration strategy:
  • Start unsharded
  • Add tenant_id to all tables
  • Enable sharding when needed
  • Move large tenants to dedicated shards

Next Steps

PostgreSQL Contributing

Submit patches to PostgreSQL

Interview Preparation

Senior database engineer interviews

Interview Deep-Dive

Strong Answer:
  • CAP states that during a network partition, a distributed system must choose between consistency (every read returns the latest write) and availability (every request gets a response). Examples: CockroachDB and Spanner are CP — during a partition, ranges without quorum reject writes to preserve consistency. Cassandra and DynamoDB are AP — they continue accepting writes during partitions and resolve conflicts later via last-write-wins or vector clocks.
  • Why it is an oversimplification: (1) CAP is binary, but real systems operate on a spectrum. Spanner achieves effective 5-nines availability while being CP because TrueTime minimizes the window where the tradeoff manifests. (2) CAP says nothing about latency, which matters more in practice than theoretical availability. A CP system that takes 5 seconds to respond during normal operation is worse than an AP system with 50ms latency and occasional stale reads. (3) The PACELC extension is more useful: during Partition, choose A or C; Else (normal operation), choose Latency or Consistency. CockroachDB is PC/EC (consistent always), DynamoDB is PA/EL (available during partition, low latency normally).
Follow-up: How does CockroachDB maintain consistency without TrueTime like Spanner has?CockroachDB uses Hybrid Logical Clocks (HLC) instead of TrueTime. HLC tracks a physical timestamp component (from NTP) plus a logical counter for ordering events within the same physical tick. The tradeoff: CockroachDB cannot guarantee external consistency (if T2 starts after T1 commits on a different node, T2 is guaranteed to see T1). Instead, it handles uncertainty windows — if a read encounters a write with a timestamp within the clock skew window, it restarts the transaction at a higher timestamp. This means higher clock skew increases transaction restart rates but never compromises safety. Spanner avoids restarts by using commit-wait (waiting out the uncertainty window), which requires TrueTime’s low uncertainty bound (1-7ms from GPS/atomic clocks).
Strong Answer:
  • BEGIN: CockroachDB assigns a provisional commit timestamp using HLC. No messages are sent — the gateway node just records the transaction locally.
  • WRITE (UPDATE): The gateway routes the write to the leaseholder of the relevant Range (a ~512MB chunk of the keyspace). The leaseholder writes an “intent” — a provisional value tagged with the transaction ID. This intent is replicated via Raft to a majority of the Range’s replicas. The intent is visible only to the owning transaction; other transactions encountering it must wait or push.
  • READ: If the read hits a Range with no conflicting intents, it proceeds locally from the leaseholder (if the lease is valid). If it encounters another transaction’s intent, it checks that transaction’s record to determine if it is committed, aborted, or pending. Pending intents cause the reader to wait or push the writer’s timestamp.
  • COMMIT: The gateway writes a transaction record (status: COMMITTED) to the Range that owns the transaction’s key. This is a single Raft write. Once committed, the transaction is durable. Intent resolution (converting intents to committed values and cleaning up) happens asynchronously.
  • Key differences from PostgreSQL: PostgreSQL runs entirely within one process on one machine — no network round trips, no consensus protocol. CockroachDB’s write path requires at least one Raft round trip (majority acknowledgment) per Range touched. A transaction touching 3 Ranges requires 3 Raft rounds plus the commit record write. This is why CockroachDB’s write latency is inherently higher than PostgreSQL’s (typically 5-20ms vs sub-millisecond).
Follow-up: What happens if the gateway node crashes mid-transaction?The transaction’s intents remain on their respective Ranges, and the transaction record (if written) shows PENDING. Other transactions encountering these intents check the transaction record. If the heartbeat on the transaction has expired (the gateway is not renewing it), the intents can be cleaned up — either resolved as committed (if the record says COMMITTED) or aborted (if PENDING with expired heartbeat). No data is lost, no coordinator recovery protocol needed. This is a major advantage over traditional 2PC where coordinator failure can leave participants stuck in the PREPARED state.
Strong Answer:
  • Question 1: Do you have an existing MySQL ecosystem? Vitess is a sharding middleware for MySQL — if you have years of MySQL operational expertise, tooling, and monitoring, Vitess preserves that investment. CockroachDB is a greenfield distributed SQL database with its own operational model.
  • Question 2: Do you need cross-shard ACID transactions? CockroachDB provides distributed ACID transactions natively — a transaction can span any number of Ranges with full serializability. Vitess supports cross-shard 2PC but it is opt-in, slower, and less battle-tested. If your application requires frequent cross-shard transactions, CockroachDB is the stronger choice.
  • Question 3: What is your consistency model requirement? CockroachDB enforces serializable isolation by default. Vitess inherits MySQL’s isolation levels (typically READ COMMITTED or REPEATABLE READ per shard). For financial or inventory systems requiring strong consistency, CockroachDB is safer.
  • Question 4: How important is resharding flexibility? Vitess handles resharding online via VReplication with well-documented procedures. CockroachDB auto-splits and rebalances Ranges without operator intervention. If your shard key distribution is unpredictable, CockroachDB’s automatic rebalancing is a significant operational advantage.
  • Question 5: What is your team’s database expertise? Vitess adds a layer on top of MySQL, meaning your DBAs manage MySQL instances plus Vitess infrastructure (VTGate, VTTablet, topology service). CockroachDB is a single distributed system, but debugging distributed query plans and Raft group issues requires new skills.
Follow-up: What about TiDB as a third option?TiDB is compelling when you need MySQL wire-protocol compatibility (like Vitess) with CockroachDB-like distributed ACID semantics. TiDB separates compute (TiDB nodes) from storage (TiKV, using Raft per region). It also has TiFlash for real-time analytics — HTAP capability that neither Vitess nor CockroachDB offers natively. The tradeoff is component complexity: you are operating TiDB, TiKV, PD (placement driver), and optionally TiFlash. Choose TiDB when you need mixed OLTP/OLAP on the same dataset with MySQL compatibility.