Vasu Patel logoVasu Patel
← Data at Rest, Data in Motion: Databases
All chapters in this track
  1. 01Chapter 1: Why Database Choices Matter
  2. 02Chapter 2: Relational Fundamentals: Tables, Keys, Constraints, Normalization
  3. 03Chapter 3: ACID Properties and Transactions
  4. 04Chapter 4: Isolation Levels and the Anomalies Each One Prevents
  5. 05Chapter 5: SQL Essentials and Query Optimization
  6. 06Chapter 6: Indexing Strategies: B-Tree, Hash, Composite, Covering
  7. 07Chapter 7: NoSQL Categories: Document, Key-Value, Wide-Column, Graph
  8. 08Chapter 8: Schema Design and Data Modeling (Relational and NoSQL Access Patterns)
  9. 09Chapter 9: The CAP Theorem and Consistency Models
  10. 10Chapter 10: Replication: Leader-Follower and Multi-Leader
  11. 11Chapter 11: Sharding and Partitioning Strategies
  12. 12Chapter 12: Caching Layers and Invalidation Strategies
  13. 13Chapter 13: Evolving Schemas Safely in Production
  14. 14Chapter 14: Diagnosing and Fixing a Slow Query, End to End
  15. 15Chapter 15: Schema Design Interview Exercises
  16. 16Chapter 16: Database Concepts in System Design and Production Debugging

Chapter 11 of 16

Chapter 11: Sharding and Partitioning Strategies

Why this matters

Replication (Chapter 10) solves availability and read scaling by copying the same data to multiple nodes — but it doesn't solve the problem of a dataset (or write load) becoming too large for any single node to handle at all. Sharding (splitting data across multiple nodes, where each node holds a different subset of the data) is the tool for that specific problem, and choosing a bad sharding key is one of the most consequential, hardest-to-reverse mistakes in database scaling.

The core concept

[BEGINNER] Sharding (also called horizontal partitioning) splits a dataset across multiple database nodes, where each shard holds a distinct subset of the rows — as opposed to replication, where every node holds a complete copy of the same data. Sharding lets total data volume and write throughput scale beyond what any single machine could handle, at the cost of significant added complexity: queries spanning multiple shards, transactions across shards, and rebalancing when shards grow unevenly all become genuinely harder problems than in a single-node system.

Unsharded: ONE database node holds ALL customer data (limited by that one machine's capacity)

Sharded:
Shard 1 (customers A-H)    Shard 2 (customers I-P)    Shard 3 (customers Q-Z)
Each shard is a separate database instance, holding only ITS slice of the data.

Sharding strategies

[BEGINNER]

Range-based sharding — split by ranges of the shard key's value (e.g., customer ID 1-1,000,000 on shard 1, 1,000,001-2,000,000 on shard 2):

Trade-off: range queries within one shard are efficient (data is contiguous),
but data distribution can become badly UNEVEN over time -- if customer IDs are
assigned sequentially and older customers are more active, early shards can
become disproportionately "hot" while later ones stay comparatively idle.

Hash-based sharding — apply a hash function to the shard key, and use the hash to determine which shard a row belongs to:

shard_number = hash(customer_id) % number_of_shards
Trade-off: distributes data much more evenly across shards (a good hash function
scatters values uniformly, avoiding the range-based "hot early shard" problem),
but destroys any meaningful physical ordering -- a range query ("all customers
with IDs between X and Y") now has to check EVERY shard, since a hash-based
scheme provides no guarantee that a contiguous ID range lives on one shard.

Directory-based sharding — maintain an explicit, separate lookup table mapping each shard key to its shard, rather than computing the mapping algorithmically:

Trade-off: maximum flexibility (shards can be rebalanced by simply updating the
lookup table, without needing to physically move data according to a rigid
formula) at the cost of an additional lookup step on every single query, and
the lookup table itself becoming a new critical dependency (and potential
bottleneck/single point of failure) that must itself be made highly available.

Choosing a shard key: the single most consequential decision

[SENIOR INSIGHT] A poorly chosen shard key is one of the hardest mistakes to fix after the fact, because fixing it means re-sharding — physically moving large volumes of data between shards while the system is live, a genuinely difficult, risky, often multi-week production operation. The two properties a good shard key needs, together:

  1. Even distribution — writes and storage should spread roughly evenly across shards; a poorly chosen key can create a hot shard that receives dramatically more traffic than the others, defeating the entire purpose of sharding (this is precisely the range-based sharding risk illustrated above).
  2. Query alignment — the shard key should match the field most commonly used to filter queries; a query that doesn't include the shard key in its filter condition must be broadcast to every shard and have results merged (a scatter-gather query), which is far slower and more resource-intensive than a query that can be routed directly to the one relevant shard.
Good shard key example: sharding an orders table by customer_id, when the
dominant query pattern is "get all orders for customer X" -- this query
routes directly to exactly one shard, and (assuming customers generate
roughly similar order volumes) distributes load evenly.

Bad shard key example: sharding the same orders table by order STATUS
(pending/shipped/delivered) -- this creates severely UNEVEN shards (most
orders are eventually "delivered," so that shard grows enormous while
"pending" stays small), AND most useful queries ("all orders for a customer")
would need to scatter-gather across every status shard, since a customer's
orders are scattered across all status values.

Common mistakes

  1. Choosing a shard key based on what seems like a natural identifier, without analyzing actual query patterns and write distribution — the customer_id-vs-status example above shows how two "reasonable-sounding" choices can have dramatically different real-world outcomes.
  2. Not anticipating that a shard key providing even distribution TODAY might not stay even as the system evolves — a shard key that was fine at launch can become uneven as usage patterns shift, requiring a difficult re-sharding operation to correct later.
  3. Building a system that requires frequent scatter-gather queries (queries not aligned with the shard key), then being surprised these queries perform far worse than expected — always check whether a query's primary filter aligns with the chosen shard key.
  4. Sharding before it's actually needed. Sharding adds substantial operational complexity (cross-shard transactions become hard or impossible, rebalancing is a real engineering project, application code must be shard-aware) — this complexity should be adopted in response to a genuine, measured scaling need (a single node genuinely can't handle the data volume or write throughput), not preemptively "to be ready for scale" that may never materialize (the same premature-optimization theme running throughout this book, applied here at its highest-stakes point).

Checklist: sharding decisions

  1. Only shard once a single node's capacity (storage or write throughput) is a real, measured constraint, not preemptively.
  2. Choose a shard key based on actual, dominant query patterns AND even distribution of writes/storage — analyze both before committing, since the decision is extremely expensive to reverse.
  3. Minimize scatter-gather queries by choosing a shard key aligned with the most common, most performance-critical query filter.
  4. Plan for shard rebalancing from the start, even if range/hash-based sharding is chosen initially — data distribution patterns can shift over a system's lifetime in ways the original sharding scheme didn't anticipate.

Discussion / interview questions

  1. "What's the difference between sharding and replication?" Strong answer: Replication copies the SAME complete dataset to multiple nodes (for availability and read scaling); sharding splits the dataset into DIFFERENT subsets across multiple nodes (for total data volume and write throughput scaling) — they solve different problems and are often used together.

  2. "What makes a good sharding key, and what happens if you choose a bad one?" Strong answer: A good key provides even distribution across shards and aligns with the dominant query pattern; a bad key creates hot shards (uneven load) and/or forces frequent scatter-gather queries across every shard — and fixing a bad shard key after the fact requires a difficult, risky re-sharding operation.

  3. "Compare range-based and hash-based sharding." Strong answer: Range-based keeps data physically ordered (efficient range queries) but risks uneven distribution over time (hot shards); hash-based distributes data evenly but destroys physical ordering, making range queries require checking every shard.

  4. "Why is sharding often recommended as a last resort, adopted only when genuinely needed?" Strong answer: Sharding adds substantial operational and application complexity — cross-shard transactions/queries become difficult, rebalancing is a real engineering effort, and shard key mistakes are expensive to fix — this complexity should be justified by a real, measured scaling constraint, not adopted preemptively.

Chapter summary / cheat sheet

  • Sharding splits data across nodes (each holding a different subset); replication (Chapter 10) copies the same data across nodes — different problems, often combined.
  • Range-based sharding: ordered, efficient range queries, but risks uneven ("hot") shards over time. Hash-based sharding: even distribution, but destroys physical ordering for range queries. Directory-based: flexible rebalancing, at the cost of an extra lookup step and a new critical dependency.
  • A good shard key needs both even distribution and alignment with the dominant query pattern — getting this wrong is one of the most expensive mistakes to fix later (requiring live re-sharding).
  • Shard only in response to a real, measured single-node capacity constraint — not preemptively, given the substantial complexity it introduces.