Demystifying Database Sharding: How to Split Data Without Splitting Your Brain
Over the last couple of weeks, our Designing Data-Intensive Applications book club has been discussing Chapter 7: Sharding.
Unlike some of our previous sessions, these turned into less of a presentation and more of a brainstorming exercise. We would pick a design, ask "But what happens when...?", and discover that the answer usually introduced another trade-off.
There was also this exchange:
“The previous chapter sounded much bigger and lengthier than this one.”
Someone immediately replied:
“Well, that chapter was replication. This one is sharding.”
Fair point. 😄
But the joke accidentally captures the concepts rather well.
With replication, we make more copies.
With sharding, we break things apart.
And while the word sharding might sound intimidating, the basic idea is surprisingly simple.
Imagine Your Database Has Outgrown Its House
Suppose your application starts with one database server.
For a long time, life is good.
Your server stores:
Users
Orders
Products
Payments
Comments
...Then your application grows.
One million users become ten million.
Ten million become a hundred million.
The database gets bigger. Write traffic increases. Storage fills up. CPU usage climbs.
At some point, upgrading to an even bigger machine stops being attractive—or simply stops being possible.
So instead of asking:
"How do I make this machine bigger?"
we ask:
"How do I spread the work across multiple machines?"
That is where sharding comes in.
Instead of one database containing everything:
Database
All the datawe divide the dataset:
Shard 1 Shard 2 Shard 3
Users A-H Users I-P Users Q-ZEach shard is effectively a smaller database responsible for some subset of the data. The primary motivation is scalability: data volume and write throughput can be distributed across machines rather than forcing one node to handle everything.
Sounds straightforward.
Unfortunately, distributed systems rarely let us leave the meeting that early.
The moment we split our database, several new questions appear.
Which record goes into which shard?
How do we make sure one shard doesn't receive most of the traffic?
What happens when we add another server?
How does an application know which server contains a particular record?
What if we want to search by something other than the sharding key?
Chapter 7 is really about answering those questions.
First Rule of Sharding: Don't Shard Unless You Need To
One of my favorite lessons from this chapter isn't an algorithm.
It's restraint.
Sharding is a powerful tool, but it is also a heavyweight solution. If one machine can comfortably handle your data volume and write throughput, keeping the architecture simple is often better.
Engineers sometimes think of scalability as:
More distributed = more scalable = better architectureA healthier equation is:
Simplest architecture that handles the required scale
=
better architectureSharding introduces an entirely new dimension into your data model because every record now needs an answer to:
Where does this record live?
That answer is usually determined by a partition key.
And choosing that key is one of the most important decisions you'll make.
The Partition Key: Your Database's Postal Code
Think about delivering a package.
The postal service doesn't search every house in America to find your address.
Your ZIP code progressively narrows down where the package needs to go.
A partition key does something similar.
Suppose we have:
user_id = 12345Our sharding algorithm might determine:
user_id 12345 → Shard 7Now the database knows exactly where to look.
But imagine querying something that doesn't tell us the partition key:
Find everyone named "John"Suddenly we may have no idea which shard contains those users.
We might have to ask:
Shard 1: Any Johns?
Shard 2: Any Johns?
Shard 3: Any Johns?
...
Shard 100: Any Johns?You've just turned one query into 100 queries.
This is why choosing the partition key is not merely a storage decision.
It can shape your application's entire query model.
Strategy 1: Sharding by Key Range
The easiest strategy to visualize is key-range sharding.
Imagine an encyclopedia.
Volume 1 contains:
A–BVolume 2:
C–Dand so on.
To look up Database, you don't search every volume. You know roughly which one should contain it.
Databases can do the same thing:
Shard 1: customer_id 1 – 1,000,000
Shard 2: customer_id 1,000,001 – 2,000,000
Shard 3: customer_id 2,000,001 – 3,000,000The book uses essentially this encyclopedia analogy when introducing key-range sharding.
This approach has a wonderful property:
Range queries are easy.
Suppose you're storing sensor readings:
sensor_42:2026-09-01
sensor_42:2026-09-02
sensor_42:2026-09-03Because related keys remain together and sorted, asking for:
"Give me sensor 42's readings from September 1–30"
can be very efficient. The chapter specifically highlights sorted storage and efficient range scans as an advantage of key-range sharding.
But there is a trap.
The Celebrity Problem: Hot Shards
Imagine partitioning events by timestamp.
August data → Shard 8
September data → Shard 9
October data → Shard 10Looks logical.
Except it's currently September.
Where are almost all new writes going?
┌─────────────┐
August shard │ │ 😴
└─────────────┘
┌─────────────┐
September shard │🔥🔥🔥🔥🔥🔥 │
└─────────────┘
┌─────────────┐
October shard │ │ 😴
└─────────────┘Congratulations: you have ten machines and the performance characteristics of approximately one machine.
This is called a hot shard or hot spot.
A related problem is a hot key.
Imagine a social network where most users receive a handful of interactions.
Then Taylor Swift posts something.
Millions of users suddenly interact with the same user or post ID.
Your hashing algorithm may be distributing keys perfectly evenly, but the traffic itself isn't evenly distributed. The chapter makes exactly this distinction: uniform key distribution does not guarantee uniform load when a few keys receive disproportionate traffic.
That's an important distributed-systems lesson:
Balanced data does not necessarily mean balanced work.
Strategy 2: Hash the Key
One way to avoid neighboring keys piling into the same shard is to hash them.
Suppose our IDs are:
1001
1002
1003
1004Instead of placing nearby values together, a hash function might produce something conceptually like:
hash(1001) → 847263
hash(1002) → 193724
hash(1003) → 638291
hash(1004) → 372918Nearby input values no longer produce nearby output values.
The data gets scattered.
That is great for distributing load.
But we've paid for that benefit.
Remember our nice range query?
Find IDs 1000–2000Those records may now exist across many shards.
Hashing improves distribution by deliberately destroying ordering; as the chapter's summary notes, this generally makes range queries less efficient.
This trade-off comes up constantly in system design:
Key-range sharding
↓
Great locality and range queries
↓
Higher hot-spot risk
Hash sharding
↓
Better distribution
↓
Poor key ordering / range queriesNeither is "the correct algorithm."
Your access patterns decide.
Why hash(key) % number_of_servers Is a Trap
At first, hashing seems to give us an obvious implementation.
Suppose there are four servers:
server = hash(key) % 4Beautiful.
Until Black Friday arrives and you need a fifth server.
Now:
server = hash(key) % 5The mapping changes for a huge number of keys.
Records that previously belonged to Server 2 might now belong to Server 4.
Records on Server 1 might move to Server 3.
Adding one machine can trigger massive amounts of data movement.
Chapter 7 explicitly calls out this weakness of the simple mod N approach: changing N remaps many keys unnecessarily.
So databases use smarter strategies.
One is to create many more shards than physical machines.
For example:
1,000 logical shards
10 database nodes
≈ 100 shards/nodeWhen Node 11 arrives, instead of recalculating where every key belongs, we move some whole shards:
Node 1 ──┐
Node 2 ──┼──> Node 11
Node 3 ──┘The mapping:
key → sharddoesn't change.
Only:
shard → serverchanges.
That's much easier to manage.
Consistent Hashing: "Please Move as Little as Possible"
This leads us to one of those terms engineers hear frequently:
consistent hashing.
The name can be slightly misleading. "Consistent" here is not about database consistency guarantees.
The useful mental model is:
When the cluster changes, try to keep each key where it already is.
A good consistent-hashing strategy aims for two things:
- distribute keys approximately evenly;
- when the number of shards changes, move as few keys as necessary.
You can think of it as reorganizing an office.
If one new employee joins, you don't want to tell all 200 employees:
"Everyone switch desks!"
You want:
"Let's move five people around and make room."
Distributed databases appreciate the same courtesy.
Rebalancing: Moving Without Stopping the World
Eventually your cluster changes.
A node is added.
A node is removed.
One shard gets too large.
A shard becomes extremely busy.
The database may need to rebalance.
With key-range sharding, for example, a big range can be split:
A ---------------- Zbecomes:
A -------- M N -------- Zand the two ranges can live on different nodes.
The difficulty is that splitting and moving shards is not free.
Potentially gigabytes of data need to move across the network while the database is still serving production requests.
This is why automatic rebalancing, while attractive, deserves respect.
Chapter 7 describes an especially interesting failure scenario:
- A node becomes overloaded.
- It responds slowly.
- Automatic failure detection decides the node must be dead.
- The cluster starts moving its data elsewhere.
- Rebalancing generates even more network and CPU load.
- More machines become slow.
- They too begin looking dead.
Welcome to a cascading failure.
Sometimes automation saves operators.
Sometimes automation is an extremely fast way to automate your outage.
There is value in occasionally keeping a human in the loop.
Okay, We Sharded the Database. Where Is My Data?
There's another problem.
Suppose:
customer_id = 42belongs to:
Shard 17Great.
But which machine currently hosts Shard 17?
Node A?
Node B?
Node C?
Node D?Remember: shards move.
So clients need some mechanism for request routing.
The chapter outlines three common approaches:
1. Client → Any database node → Correct node
2. Client → Routing layer → Correct database node
3. Smart client → Correct database node directlyApproach 1 keeps clients simple but can add an extra network hop.
Approach 2 introduces a dedicated routing layer.
Approach 3 removes the middleman but makes clients smarter and requires them to keep their shard map current.
And someone must maintain the authoritative mapping:
Shard 1 → Node B
Shard 2 → Node A
Shard 3 → Node D
Shard 4 → Node B
...Coordination systems such as ZooKeeper or etcd can fill this role; the chapter also discusses databases with built-in consensus-based coordination.
Now we have discovered something important.
Sharding isn't just:
split data across machines.
It becomes:
split data, track the pieces, move the pieces, and make sure everyone knows where the pieces currently are.
Secondary Indexes: Where Sharding Gets Really Interesting
Now imagine we're building a used-car marketplace.
We shard listings by listing_id.
Listing 127 → Shard A
Listing 742 → Shard BEasy.
Someone searches:
"Show me listing 742."
We know exactly where to go.
But real users don't search for cars by database IDs.
They search:
Color = Red
Make = Honda
Year > 2022
Price < $30,000These are secondary indexes.
And secondary indexes make sharding much more interesting because the thing you're searching by is no longer necessarily the thing you sharded by.
The chapter presents two major choices.
Option 1: Local Secondary Indexes
Each shard indexes only its own records.
Shard A
red → [12, 15, 91]
blue → [33, 76]
Shard B
red → [501, 762]
blue → [620]Writing is convenient.
When record 91 changes, we update Shard A.
Done.
But ask:
"Show me every red car."
and suddenly we need:
Ask Shard A
Ask Shard B
Ask Shard C
Ask Shard D
...
Merge resultsThe book calls out the scalability problem clearly: if every query has to touch every shard, adding more shards doesn't necessarily increase your query throughput, and tail latency can become worse.
So:
Local indexes make writes easier but can make reads expensive.
Option 2: Global Secondary Indexes
Instead, create one logical index covering the whole dataset:
red → [12, 15, 91, 501, 762, ...]Of course, we can't put this giant index on a single machine.
We're sharding because single machines are exactly what we're trying to escape.
So we shard the secondary index too.
Perhaps:
colors A–R → Index Shard A
colors S–Z → Index Shard BNow searching:
color = redcan go directly to the shard containing red.
Reads improve.
Writes get harder.
Updating one car might now involve:
Primary-data shard
+
Color-index shard
+
Make-index shard
+
Potentially other index shardsA single logical update has crossed several machines.
Global indexes therefore make the read path more targeted but complicate writes and consistency; some systems may update such indexes asynchronously, which can mean temporarily stale index results.
Another beautiful distributed-systems trade-off:
Local secondary index
Cheap writes
Expensive reads
Global secondary index
More complicated writes
Better targeted readsThere is no free lunch.
Only different restaurant bills.
An Underrated Use of Sharding: Multitenancy
One section I found particularly useful in the second edition is sharding for multitenant systems.
Imagine a SaaS product:
Customer A
Customer B
Customer C
Customer DRather than sharding arbitrary records, you might assign tenants to shards:
Shard 1 → Customers A, B, C
Shard 2 → Customers D, E, F
Shard 3 → Customer GThat can provide benefits beyond raw scalability.
The chapter identifies advantages including resource isolation, permission isolation, cell-based fault isolation, per-tenant backup and restore, data residency, and gradual schema rollout.
This gives us a broader perspective:
Sharding isn't always about "the database is too big."
Sometimes it's about creating useful boundaries.
And good boundaries are one of the recurring themes in scalable system design.
The challenge, of course, appears when one tenant becomes huge—or when features require data across several tenants. Then our neat boundary becomes another distributed-systems problem.
The Pattern Behind All of This
After discussing this chapter for two weeks, my biggest takeaway wasn't any particular hashing algorithm.
It was this:
Sharding is the art of deciding what should stay together.
Put related data together and operations within that boundary become easy.
Split data apart and you gain parallelism.
But every operation that crosses that boundary becomes more expensive.
That explains almost everything we've discussed:
Good partition key
↓
Most requests hit one shard
↓
Simple + fast
Poor partition key
↓
Requests span many shards
↓
Coordination + network calls + complexityAnd it explains why some of the hardest sharding questions aren't really database questions.
They're application questions.
What data is usually accessed together?
What data is written together?
What queries must be fast?
Where could hot keys emerge?
Which operations require global knowledge?
How large could one tenant become?
Those answers should influence your sharding strategy far more than whether a particular database supports some clever hashing algorithm.
A Practical Sharding Checklist
If I were reviewing a system-design proposal involving sharding, these are the questions I'd start with:
- Do we actually need sharding yet? Could a single database plus replication handle our scale?
- What is the partition key? Does it align with our most common access patterns?
- Could that key create hot spots? Think timestamps, celebrities, giant customers, popular products, and viral content.
- Do we need range queries? Key-range sharding may preserve useful locality while hashing generally sacrifices it.
- What happens when we add machines? Understand the rebalancing strategy and how much data must move.
- Who knows where each shard lives? Clients, routers, database nodes, or a coordination service?
- What queries don't contain the partition key? Those may become scatter/gather queries.
- Do we need secondary indexes? Decide whether local or global indexing better matches your read/write workload.
- How many operations cross shard boundaries? Frequent cross-shard work may be telling you that your partition boundary is wrong.
- What happens during failure or rebalancing? Design for the system while it's changing, not only for its steady state.
Sharding in One Picture
If I had to summarize Chapter 7 on a whiteboard, I'd draw this:
DATA
│
│ Choose partition key
▼
┌───────────────┐
│ Sharding Rule │
└───────┬───────┘
│
┌──────────────┼──────────────┐
▼ ▼ ▼
Shard A Shard B Shard C
│ │ │
▼ ▼ ▼
Node 1 Node 2 Node 3
│
│ Cluster changes
▼
Rebalancing
│
│ Client asks for data
▼
Request Routing
│
│ Search by another field?
▼
Secondary Indexes
┌────────┴────────┐
Local Global
index index
easy writes easier reads
harder reads harder writesThat is sharding.
The algorithms matter.
But the real engineering lies in the arrows.
Final Thought: Sharding Moves Complexity
Sharding doesn't magically make data smaller.
It changes who is responsible for which part of it.
One big problem becomes several smaller problems that can run independently.
That's the source of scalability.
But independence has a price.
Whenever an operation needs information from several shards, the complexity we removed comes right back in the form of networking, routing, coordination, distributed transactions, or result aggregation.
The chapter closes on precisely this tension: shards scale because they operate mostly independently, but writes involving several shards are inherently more difficult—especially when some succeed and others fail.
So perhaps the most useful mental model for sharding isn't:
How do I split my database?
It is:
Where can I draw boundaries so that most of my system can work independently?
Get that boundary right, and sharding becomes a powerful scaling tool.
Get it wrong, and you'll have a beautifully distributed collection of new problems.
And unlike replication, unfortunately, those problems may not come with extra copies. 😄
This article was inspired by our community discussions while reading Chapter 7, “Sharding,” of Martin Kleppmann and Chris Riccomini's second edition of Designing Data-Intensive Applications. The chapter covers sharding strategies, hot spots, rebalancing, request routing, multitenancy, and local versus global secondary indexes.