Skip to main content

Track 5: Data Systems at Scale

Building and understanding data systems that handle millions of operations per second. This track covers the fundamental engineering challenge of data-intensive applications: how do you organize, distribute, and access massive datasets when a single machine is not enough? Every decision in this space is a trade-off between read performance, write performance, storage efficiency, and query flexibility.
Track Duration: 44-54 hours
Modules: 5
Key Topics: Partitioning, Consistent Hashing, Spanner, Kafka, Stream Processing

Module 22: Partitioning Strategies

Why Partition?

Partitioning Strategies

Key-Range Partitioning

Hash Partitioning

Secondary Indexes

The library analogy: Think of local indexes like a bookshelf in each library branch that only catalogs the books in that branch. To find all books on “distributed systems” across the entire city, you must visit every branch. Global indexes are like a single city-wide catalog that tells you which branches have which books — one lookup gives you the answer, but every time a branch adds a book, someone must update the central catalog, which takes extra effort. Practical scenario: An e-commerce platform partitions orders by order_id (hash partitioning for even distribution). But the “My Orders” page needs to query by user_id, and the admin dashboard needs to query by date_range. With a local secondary index on user_id, the “My Orders” query must scatter-gather across all partitions — at 100 partitions and 5ms per partition, that is 500ms if done sequentially. The solution is either a global index on user_id (fast reads, slower writes) or a denormalized table that is also partitioned by user_id (maintained via Change Data Capture from the primary table). The right choice depends on your read-to-write ratio.

Advanced: Z-Order Curves & Multi-dimensional Partitioning

Standard partitioning (Key-Range or Hash) works well for one-dimensional keys. However, for multi-dimensional data (e.g., searching for users by (latitude, longitude) or (age, income)), a single-key partition leads to expensive “scatter-gather” queries. Z-Order Curves (Morton Order) map multi-dimensional data into a single dimension while preserving locality.

How it works: Bit Interleaving

To map a 2D point (x,y)(x, y) to a 1D Z-value:
  1. Represent xx and yy as binary strings.
  2. Interleave the bits: Z=ynxn...y1x1y0x0Z = y_n x_n ... y_1 x_1 y_0 x_0.

Why it matters for Distributed Systems:

  • Locality Preservation: Points that are close in 2D space are usually close on the 1D Z-curve.
  • Range Queries: A 2D box query becomes a set of 1D range scans on a standard Key-Range partitioned database (like HBase or Cassandra).
  • Used By: Amazon DynamoDB (for Geo-spatial), Uber (H3 is a hexagonal alternative), and many GIS systems.
Distributed pitfall: Z-Order curves are not perfect — they can produce “false positives” where nearby Z-values map to distant 2D points (at the boundaries of the Z-shaped path). In practice, this means your range scan will return some irrelevant results that you must filter out in a post-processing step. The trade-off is still worthwhile: scanning 3 contiguous ranges on one partition is vastly cheaper than scatter-gathering across all partitions. If you need tighter locality for geospatial queries specifically, consider Hilbert Curves (used by Google S2 Geometry), which have better worst-case locality than Z-Order at the cost of more complex encoding.

Module 23: Consistent Hashing

The foundational algorithm for distributed systems. Consistent hashing solves a deceptively simple problem: when you add or remove a server, how do you avoid reshuffling all the data? With naive hash(key) % N, changing N moves almost everything. Consistent hashing arranges servers on a conceptual ring so that adding or removing a server only affects its immediate neighbors — roughly 1/N of the keys move instead of all of them. The apartment building analogy: Imagine tenants assigned to floors by hashing their name. With regular hashing, adding a floor means everyone moves. With consistent hashing, the new floor only takes tenants from the floor “next to it” on the ring. Everyone else stays put. Consistent Hashing

Virtual Nodes

Implementation

Alternative: Rendezvous Hashing

Choosing between consistent hashing and rendezvous hashing: Use consistent hashing when you have many nodes and need O(log n) lookups (the common case for distributed caches and databases). Use rendezvous hashing when the node count is small (under 100) and you want simpler code without the virtual node bookkeeping. In both cases, the key property is the same: adding or removing a node only reassigns approximately 1/N of the keys. A senior engineer would say: “The choice between them is about implementation complexity and lookup speed, not correctness — both provide minimal disruption on topology changes.”

Module 24: Distributed Databases Deep Dive

Google Spanner

Spanner TrueTime & Commit Wait

CockroachDB

Cassandra

When to Use What?


Module 25: Distributed Storage Systems

HDFS / GFS Architecture

Object Storage (S3 Architecture)

Comparison: Replication vs. Erasure Coding

Staff Tip: Erasure coding is best for cold data (infrequently accessed) where storage cost dominates. For hot data, replication is preferred to avoid the CPU and latency penalties of “reconstruction reads.”
Staff Tip: Shared-storage architectures are generally better for RDBMS workloads (SQL) that are hard to shard but need high availability and read scalability. Shared-nothing is still king for massive horizontal scale (billions of rows) where a single storage tier would become a bottleneck.

Compute/Storage Separation: The Snowflake Model

While Aurora uses “Database-Aware” storage, analytical systems like Snowflake and Google BigQuery use a different pattern: Storage/Compute Separation via Object Storage.

1. Decoupled Scaling

  • Compute: Stateless “Virtual Warehouses” (clusters of VMs). Can be turned off or scaled to 100 nodes in seconds.
  • Storage: Highly durable, cheap Object Storage (S3/Azure Blob/GCS).

2. The Metadata Layer (The “Brain”)

The metadata layer tracks:
  • Which files (S3 objects) belong to which table.
  • Statistics for pruning (min/max values in each file).
  • Transactional state (using MVCC).

3. Local Caching

To avoid the latency of S3 on every query, compute nodes use Ephemeral SSDs to cache frequently accessed blocks.
  • Result: Performance of local disk with the elasticity of the cloud.

Advanced: The Shuffle Problem & Distributed Query Execution

While databases like Spanner handle OLTP (Online Transaction Processing), large-scale analytics (OLAP) systems like Presto/Trino, Spark, and ClickHouse face a different challenge: The Shuffle.

1. What is a Shuffle?

A shuffle is the process of redistributing data across a cluster so that all records with the same key end up on the same physical node. This is required for:
  • Joins: To join Table A and Table B on user_id, all rows for user_id=123 from both tables must meet on the same node.
  • Aggregations: To GROUP BY city, all “NYC” rows must be on one node.

2. The Shuffle Bottleneck

Shuffle is the most expensive operation in distributed systems because it involves:
  • Network I/O: Moving terabytes of data across the wire.
  • Disk I/O: Buffering data to disk if it doesn’t fit in RAM (spilling).
  • Serialization: Converting objects to bytes and back.

3. Execution Models: Exchange vs. Push-Based

  • Exchange Operators (Volcano Model): Each node pulls data from its predecessors. This is simple but can be slow due to “pull” overhead.
  • Push-Based (Vectorized): Modern engines like DuckDB or Photon (Databricks) push batches of data (vectors) through the pipeline, maximizing CPU cache efficiency.

4. Join Strategies

Staff Tip: To optimize distributed queries, your goal is to minimize the shuffle. Use broadcast joins whenever possible, and try to partition your “fact” and “dimension” tables on the same key at the storage level.

Advanced Storage: Storage Engines (LSM-Trees vs. B-Trees)

In distributed databases like Cassandra, RocksDB, and Spanner, the Storage Engine—the part that actually writes to disk—is a critical design choice.

1. B-Trees (Read-Optimized)

Standard in RDBMS (Postgres, MySQL).
  • Structure: Sorted tree of fixed-size blocks (pages).
  • Write: Overwrites data in place.
  • Pros: Fast, deterministic reads.
  • Cons: Write amplification (updating a small row requires writing a full page) and random disk I/O.

2. LSM-Trees (Write-Optimized)

Log-Structured Merge-Trees are used in Cassandra, BigTable, and RocksDB.

The LSM Write Path (The “Log” part)

Writes never overwrite data. They are always appended.
  1. WAL (Write Ahead Log): Append-only log for durability.
  2. Memtable: In-memory sorted buffer (often a Skip List or Red-Black Tree).
  3. SSTable (Sorted String Table): When the memtable is full, it’s flushed to disk as an immutable sorted file.

The LSM Read Path

  1. Check the Memtable.
  2. Check the Bloom Filter (a probabilistic data structure that tells you if a key might exist or definitely doesn’t exist).
  3. If Bloom filter says “maybe,” check the SSTable Index and read from disk.

Compaction (The “Merge” part)

Since SSTables are immutable, updates/deletes just add more records. Compaction merges these files in the background.

Comparison: The Three Amplifications

In Staff-level interviews, you must discuss the RUM Conjecture (Read, Update, Memory): Staff Tip: If your system has high write volume (e.g., logging, metrics, or a distributed ledger), choose an LSM-Tree. If you need low-latency range reads on a stable dataset, choose a B-Tree.

Advanced: The Bw-Tree & LLAMA (Lock-Free Storage)

For high-concurrency systems (like Microsoft SQL Server’s Hekaton or Azure Cosmos DB), traditional B-Trees suffer from latch contention (locking) and “in-place” update overhead. The Bw-Tree (Buzzword Tree) solves this using a log-structured, lock-free approach.

1. The Mapping Table

The core innovation is a Mapping Table that maps a logical Page ID to a physical memory pointer.
  • Benefit: Instead of updating a page in-place (which requires a lock), you create a new version of the page and update the pointer in the mapping table using a single Atomic CAS (Compare-and-Swap).

2. Delta Records

Instead of rewriting a 4KB page for a 10-byte change, Bw-Tree appends a Delta Record to the existing page.
  • Chain: A logical page becomes a chain: [Mapping Table] -> [Delta 2] -> [Delta 1] -> [Base Page].
  • Consolidation: When the chain grows too long (e.g., > 8 deltas), a thread “consolidates” them into a new Base Page.

3. LLAMA (Latch-Free, Log-structured Access Method)

LLAMA is the storage subsystem that manages these pages. It ensures that both the in-memory state and the on-disk state are log-structured and latch-free.

Advanced: WAL Internals & Zero-Copy I/O

At the “Principal” level, performance is often gated by how fast you can write the Write-Ahead Log (WAL). High-performance logs (like Kafka or Finagle) use OS-level optimizations to bypass the CPU.

1. Zero-Copy (sendfile)

In a traditional write, data moves: Disk → Kernel Buffer → App Buffer → Socket Buffer → NIC. With Zero-Copy (sendfile system call), the data moves: Disk → Kernel Buffer → NIC. This reduces context switches and memory bus contention, allowing a single node to saturate a 100Gbps link.

2. Group Commit

Instead of calling fsync() for every request (which is slow due to disk head movement), the system buffers multiple writes and performs one large synchronous write.
  • Trade-off: Increases latency for individual requests but massively increases total throughput.

3. Direct I/O (O_DIRECT)

Databases like ScyllaDB or Oracle bypass the OS Page Cache entirely.
  • Why? The OS cache uses LRU (Least Recently Used), but databases often know better (e.g., “I’m doing a sequential scan, don’t cache this”). Direct I/O allows the database to manage its own “Buffer Pool” with domain-specific knowledge.

Module 26: Stream Processing

Kafka Architecture

Kafka Architecture

Kafka Guarantees

Stream Processing Concepts

Handling Late Data (Post-Watermark)

When an event arrives with t<Wt < W, it is considered Late. Options:
  • Drop: Discard the event (standard for real-time dashboards).
  • Side Output: Send to a separate “late-events” stream for manual correction.
  • Update: Re-calculate the window and emit an updated result (costly).

2. Multi-Range Transactions (Percolator Model)

When a transaction touches keys in multiple ranges (e.g., a bank transfer from User A to User B), the database must coordinate across Raft groups. CockroachDB and TiDB use a variant of Google’s Percolator protocol. The Two-Phase Commit (Simplified):
  1. Prewrite: Write “intent” records (locks) to all involved keys across ranges. The first key is the Primary Lock.
  2. Commit: If prewrite succeeds, write a commit record to the primary key’s row. Then, asynchronously resolve all other intents.
  3. Recovery: If a transaction crashes, other transactions can detect stale intents and either roll them forward (if primary is committed) or roll them back.

3. Timestamp Oracle (TSO)

To achieve Serializable Snapshot Isolation (SSI), the database needs globally ordered timestamps. This is handled by a Timestamp Oracle (TSO):
  • In TiDB, the TSO is a centralized service (Placement Driver).
  • In CockroachDB, it’s decentralized using Hybrid Logical Clocks (HLC).

4. Multi-Region SQL

For global deployments, data placement becomes critical for latency. Staff Tip: For Staff+ interviews, be ready to discuss how these databases handle follower reads (reading from a non-leader replica for lower latency) and the consistency trade-offs involved (stale reads vs. waiting for leader).

Key Interview Questions

TrueTime + Commit Wait:
  1. TrueTime API returns time interval [earliest, latest]
    • GPS receivers + atomic clocks
    • Uncertainty typically 1-7ms
  2. Commit protocol:
    • Transaction gets timestamp T at commit
    • Wait until TrueTime.after(T) is true
    • This ensures T is definitely in the past
    • No other transaction can get earlier timestamp
  3. Result: If T1 commits before T2 starts:
    • T1’s timestamp < T2’s timestamp
    • Real-time ordering preserved globally
Trade-off: Commit latency includes wait time (average ~7ms)
Three mechanisms:
  1. Idempotent Producer
    • Broker deduplicates by (producer_id, sequence_number)
    • Prevents duplicates from retries
  2. Transactional Producer
    • Atomic writes across partitions
    • Uses two-phase commit internally
  3. Consumer read_committed
    • Only sees committed messages
End-to-end exactly-once:
  • Consume → Process → Produce atomically
  • Commit consumer offset in same transaction
  • If crash, replay from last committed offset

Next Steps

Continue to Track 6: Production Excellence

Learn observability, chaos engineering, and SRE practices