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.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.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.- 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.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.- Follower timeout: If follower doesn’t hear from leader, becomes candidate
- Request votes: Candidate increments term and requests votes from peers
- Vote granting: Followers vote for first candidate in each term
- Majority wins: Candidate with majority becomes leader
- 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.- Key-Based (Hash):
shard_id = hash(key) % num_shards. Even distribution, but resharding is expensive and range queries are inefficient. - Range-Based:
shard_iddetermined 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.- 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.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.- Prepare Phase: Coordinator asks all participants “Can you commit?”. Participants lock resources and persist their vote.
- 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 API: Returns
[earliest, latest]. The actual time is guaranteed to be within this interval. - Commit Wait: A transaction
Tiwaits untilTT.now().earliest > Ti.commit_timestampbefore reporting success. This ensures that ifT2starts afterT1finishes,T2will definitely seeT1’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.- 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.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
Q: How would you design a globally distributed database like Spanner?
Q: How would you design a globally distributed database like Spanner?
-
Data Placement
- Shard data by primary key range
- Each shard replicated across zones/regions
- Paxos or Raft for consensus per shard
-
Time Synchronization
- GPS + atomic clocks for TrueTime (Spanner approach)
- Or HLC with uncertainty intervals (CockroachDB approach)
- Critical for external consistency
-
Transactions
- Lock-free reads at snapshot timestamp
- Pessimistic locking for writes
- 2PC for cross-shard transactions
- Commit wait to ensure external consistency
-
Read Optimization
- Stale reads from local replica (bounded staleness)
- Strong reads require leader/leaseholder
- Follower reads with clock check
-
Schema Management
- Online schema changes (like PG’s concurrent operations)
- Distributed DDL coordination
- Schema version tracked per shard
Q: Compare Vitess, CockroachDB, and TiDB architectures
Q: Compare Vitess, CockroachDB, and TiDB architectures
- 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
- 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
- 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
- Vitess: Existing MySQL, horizontal read scaling
- CockroachDB: Greenfield, strong consistency required
- TiDB: Mixed OLTP/OLAP, MySQL compatibility
Q: How does CockroachDB handle a node failure during a transaction?
Q: How does CockroachDB handle a node failure during a transaction?
- Each transaction has a “transaction record” stored in a range
- Record tracks: status (PENDING/COMMITTED/ABORTED), timestamp, intents
- If coordinator fails, transaction record remains
- Other transactions encountering intents check transaction record
- If record says COMMITTED: resolve intent as committed
- If record says ABORTED: delete intent
- If record says PENDING and heartbeat expired:
- Push transaction timestamp or abort it
- Garbage collect stale intents
- Raft detects leader failure (~3-10 seconds)
- New leader elected
- Uncommitted Raft entries replayed
- Transactions on failed node:
- Can retry from any other node
- Intents still visible, resolved based on txn record
- No transaction coordinator bottleneck
- Any node can resolve intents using txn record
- Heartbeats prevent zombie transactions
Q: Design a sharding strategy for a multi-tenant SaaS application
Q: Design a sharding strategy for a multi-tenant SaaS application
- Tenant isolation
- Tenant-level scaling
- Cross-tenant queries (admin/analytics)
- Even distribution
- Sub-sharding by (tenant_id, secondary_key)
- Example: (tenant_id, order_date) for time-based queries
- Or dedicated shard per large tenant
- Hash tenant_id for even distribution
- Many small tenants share shards
- No hot shard issues
- Scatter-gather (expensive but possible)
- Materialized views in analytics system
- Separate OLAP store (TiFlash, ClickHouse)
- Start unsharded
- Add tenant_id to all tables
- Enable sharding when needed
- Move large tenants to dedicated shards
Next Steps
PostgreSQL Contributing
Interview Preparation
Interview Deep-Dive
Explain the CAP theorem with concrete database examples. Then tell me why most experienced engineers consider it an oversimplification.
Explain the CAP theorem with concrete database examples. Then tell me why most experienced engineers consider it an oversimplification.
- 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).
Walk me through a distributed transaction in CockroachDB. What happens at each step and how is it different from PostgreSQL's local transactions?
Walk me through a distributed transaction in CockroachDB. What happens at each step and how is it different from PostgreSQL's local transactions?
- 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).
You are choosing between Vitess and CockroachDB for a new project. What questions do you ask to make the decision?
You are choosing between Vitess and CockroachDB for a new project. What questions do you ask to make the decision?
- 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.