Skip to main content

Partitioning Strategies: Range vs Hash vs Directory

Level 3: Level 3: Scaling the Data Tierhard35 minpartitioningshardingskew

Range wins range scans but hotspots on sequential keys, hash spreads evenly but loses ranges, directory adds a flexible routing hop; map every dominant query to its partitions.

Splitting past one machine

When one machine can no longer hold the dataset or absorb the write rate, the only real fix is horizontal partitioning (sharding): split the rows across many nodes so each node owns a slice. Contrast this with vertical partitioning (splitting a wide table into narrow ones by column) and functional partitioning (giving each service its own database). Vertical and functional buy some headroom, but only horizontal partitioning scales writes and dataset size without bound, so it is the technique interviewers mean by "shard it."

The design choice is the partition function: given a key, which partition owns it. Three families dominate.

Check yourself
You are sharding an append-heavy events table. A teammate suggests partitioning by timestamp so recent events sit together. Predict the write pattern.

Range partitioning assigns contiguous key ranges to partitions (users A to F on p0, G to M on p1; or time ranges for events). Its superpower is range scans: "all orders from last Tuesday" touches one or two partitions. Its curse is hotspots on sequential keys. If you range-partition by an auto-increment ID or a timestamp, every new write lands on the highest partition, so one node absorbs 100% of the write traffic while the rest sit idle. This is the single most common partitioning mistake.

Hash partitioning applies a hash to the key and assigns by the result (often hash(key) mod N). It spreads load evenly and kills sequential hotspots, because adjacent keys scatter. The cost is that you lose efficient range queries: "orders from last Tuesday" now fans out to every partition (scatter-gather). The other trap is hash mod N specifically: change N (add a node) and almost every key remaps, forcing a near-total reshuffle. Consistent hashing exists to fix exactly that.

Directory (lookup-based) partitioning keeps an explicit routing table mapping key ranges or key groups to partitions, maintained in a coordination service (ZooKeeper/etcd) or a metadata store. It gives maximum flexibility: split a hot range, move a heavy tenant to its own node, rebalance surgically. The price is an extra lookup hop on the request path and a routing service you must keep highly available, since it is now on the critical path.

Secondary indexes get partitioned too

A local (document-partitioned) index stores each partition's index alongside its own data, so a query on a non-partition-key column must scatter-gather across all partitions and merge (DynamoDB LSI, Elasticsearch by default). A global (term-partitioned) index partitions the index itself by the indexed term, so a lookup hits one index partition, but writes must update an index partition that may live on a different node, making writes slower and asynchronous (DynamoDB GSI). The index does not live for free on one node.

Interview nuance: always map the dominant queries onto the partition scheme out loud. "This query hits one partition, that one is scatter-gather bounded by the slowest node." Interviewers are checking whether you know which reads got expensive, not just that you sprinkled the word "shard."

Table
Map the dominant queries onto the scheme out loud: which reads hit one partition, and which became scatter-gather.
StrategyHow keys mapWinsCosts
RangeContiguous key ranges (A-F, G-M, N-Z) or time rangesRange scans hit 1 or 2 partitionsSequential keys hotspot the highest partition
Hashhash(key) mod N scatters adjacent keysEven spread, no sequential hotspotRange scans fan out to every partition; changing N remaps almost every key
DirectoryExplicit lookup table maps keys to partitionsSurgical rebalance: split a hot range, move a heavy tenantExtra lookup hop plus a routing service that must stay highly available
Map the dominant queries onto the scheme out loud: which reads hit one partition, and which became scatter-gather.

Recap: horizontal partitioning is the only way to scale writes and data past one node; range wins range scans but hotspots on sequential keys, hash spreads evenly but loses ranges and reshuffles on mod N, directory adds a flexible routing hop, and secondary indexes are either scatter-gather locals or write-costly globals.

Check yourself

Match each behavior to the partitioning strategy that produces it.

'All orders from last Tuesday' touches one or two partitions
An auto-increment key turns one node into the sole write target
Adjacent keys scatter, so a date query fans out to every partition
Adding one node under plain 'hash(key) mod N' remaps almost every key
Move one heavy tenant to its own node by editing a routing table
An extra lookup hop and a routing service that must stay highly available

Apply

Your turn

The task this lesson builds to.

Design the partitioning scheme for a 20 TB messaging store doing 200k writes/sec; pick a partition strategy and defend it against skew.

Think about

  1. What does range vs hash vs directory partitioning optimize and cost?
  2. How do local vs global secondary indexes work across partitions?
  3. How does each query map to partitions?

Practice

Make it stick

A second problem on the same idea, so it survives past today.

Design the partitioning scheme for Stripe-style payment events at 50 TB and 300k events/sec, where the two dominant access patterns fight each other: (1) low-latency single-object reads by event_id, and (2) an analytics/reconciliation job that must scan 'all events for merchant M in a date range.' Pick a scheme and defend it against both skew and the range-scan requirement.

Think about

  1. How can one scheme serve both hash-friendly point reads and range-friendly merchant scans?
  2. What does a whale merchant at 40% of volume do to its partition, and what splits it?
  3. How does encoding routing information into the event_id avoid a fan-out?