LearnHLDSharding

Sharding

Sharding is the point where a database stops being a database and becomes a distributed system you are now responsible for. Joins stop working. Transactions stop working. Unique constraints stop working. ORDER BY with LIMIT becomes a merge across every shard.

All of that is worth it eventually. It is almost never worth it as early as people reach for it, which is why the first thing to say in an interview is what you would try before sharding, and why that has run out.

Your answer

You shard a social app by user id hash. Name one query that used to be one statement and is now genuinely hard.

What you try first

Say these out loud in order. Each one buys real time and none of them costs you joins.

Read replicas, if the load is reads. This is the common case and it takes an afternoon. A cache in front, which for a read heavy workload usually removes more load than a shard would. Vertical scaling, which is unglamorous and buys a lot: a single modern machine handles far more than most people assume. Then archiving cold rows out of the hot tables, because a large fraction of most tables is data nobody queries.

Shard when writes exceed what one primary can take, or when the working set no longer fits in memory on the largest machine you can buy. Those are the two honest triggers.

What they are checking

Candidates who jump straight to sharding are demonstrating that they know the word. Thirty seconds spent on what you would do first, and why it has stopped working here, is worth more than a detailed shard key discussion, because it is the part that separates people who have run a database from people who have read about running one.

The shard key decides everything

Pick the key badly and you get a distributed system with all of the cost and none of the benefit, because one shard takes most of the load. Try a few.

Shard key
12
13
12
13
12
13
13
12
0
1
2
3
4
5
6
7
Shard 1 carries 13.1% (even would be 12.5%)
Even, boring, and correct for most workloads. Every user lands somewhere random, so load spreads regardless of signup date or geography. The cost is that any query not filtered by user id has to visit every shard.

Two lessons from those four. Hashing fixes skew that comes from ordering, and does nothing about skew that is in the data itself. And a key that is meaningful to humans is usually skewed, because humans are unevenly distributed across everything.

Hash, range, or a directory

Placement
Computed in the client, so there is no lookup in the request path and nothing to keep consistent. Even distribution, and range queries are gone: fetching everything created last Tuesday means asking every shard. Resizing moves nearly everything unless you use consistent hashing or logical shards.

The trick that makes resharding survivable

Do not map keys to physical shards. Map keys to a large fixed number of logical shards, say 1,024, and map logical shards to physical machines in a small table.

Now growing the cluster is moving logical shards between machines, not rehashing data. The key to logical shard mapping never changes, so no key ever needs recomputing. Going from 8 machines to 16 means moving 512 logical shards, which you can do one at a time, in the background, with the ability to stop halfway. Compare that with rehashing every row while the site is up.

Pick the logical shard count once and pick it high. It is the one number here you cannot change later without the migration you were trying to avoid.

Figure 1. Two mappings. The first never changes, so no data is ever rehashed. The second is a small table you edit to rebalance.

What you lose, and what to do about it

Joins across shards. Denormalise so the data you read together lives together, or do the join in the application, or keep a small reference table replicated to every shard.

Transactions across shards. Design so they are not needed, which usually means choosing the shard key so that things which change together share a shard. Where you genuinely need one, you are in saga and compensation territory, the same shape as the payment rail.

Unique constraints. A unique index only covers one shard. Global uniqueness, on an email address for example, needs a separate table keyed by that value, and that table is either unsharded or sharded by the value itself.

Pagination and sorting. ORDER BY created_at LIMIT 20 has to fetch 20 from every shard and merge. Workable at eight shards, painful at a thousand, and the reason cursor pagination and per-shard time ordered ids matter.

Counting. COUNT(*) becomes a scatter gather. Keep a counter, or accept an estimate.

Break it

Shard load
balanced
balancedtenant growsshard 3 saturatedsplit out
Healthy. Eight shards, tenants spread evenly by hash, roughly 12% of traffic each. Sharding is doing its job and nobody is thinking about it.

Interview replay

Interviewer
Your main database is struggling. Would you shard it?
Almost always a trap. The word "struggling" carries no information about why.
You
Not yet, and probably not first. I would want to know whether it is reads or writes. If it is reads, replicas and a cache are far cheaper and I keep joins and transactions. If the working set has outgrown memory, archiving cold rows often buys a year. I would shard when writes exceed what one primary can take, because that is the problem replicas do not solve.
Names the one condition sharding actually fixes. This answer is short and it is the whole question.
Interviewer
Say writes are the problem. What is your shard key?
Now the real design starts.
You
Whatever the highest volume query filters on, so that most reads hit one shard. For a social app that is usually the user id, hashed. I would specifically avoid a time based key, because recent data takes nearly all the traffic and the newest shard becomes a hot spot while the rest hold cold history.
Derives the key from the access pattern rather than from the data model. Rejects the plausible wrong answer and says why.
Interviewer
How do you go from 8 shards to 16 without downtime?
The question that separates a drawing from a plan.
You
By not mapping keys to machines directly. Keys hash to a large fixed number of logical shards, say 1,024, and a small table maps logical shards to machines. Growing means moving logical shards, not rehashing rows, so I can move them a few at a time, in the background, and stop if something looks wrong. If that indirection is not already there, the migration is dual writes to both layouts, a backfill, verification, then a cutover, and it is weeks of work.
Gives the clean answer and the honest answer for a system that did not plan ahead. The second half is what makes it credible.
Interviewer
One tenant is fifty times bigger than everyone else. Now what?
Checking whether you know the limits of hashing.
You
Hashing cannot help, since all their data belongs together by definition. I would put an override in the mapping table and give them a dedicated shard, possibly dedicated hardware. That is the argument for keeping a directory layer even when you are hashing: even distribution by default, and the ability to place one key by hand when reality does not cooperate.
Connects back to the earlier design choice rather than treating it as a new problem.

Trade-offs

ChoiceWhat you gainWhat you payPick it when
Hash shardingEven distribution with no lookup in the request path and nothing to keep consistent.Range queries scatter to every shard, and resizing is painful without logical shards.The default when most queries filter on one entity, which is most transactional workloads.
Range shardingRange scans stay local, and splitting a hot range is a local operation.Clustered access patterns create hot shards, and the newest range is usually the hottest.Time series and archives, where you read ranges and rarely write to old ones.
DirectoryPlace any key anywhere, so one oversized tenant is an entry in a table rather than a redesign.A lookup on the request path, and a service whose outage makes every shard unreachable.Multi-tenant systems with wildly uneven tenants. Often layered over hashing rather than replacing it.
Logical shards over physicalRebalancing moves shards instead of rehashing rows, so growth is a background operation.One more indirection, and a shard count you have to choose correctly on day one.Always, if you are building the sharding layer now. Retrofitting it is the migration itself.

Checkpoint

Checkpoint

1. What is the honest trigger for sharding rather than adding replicas?

2. Why is created_at usually a poor shard key?

3. You hash keys to 1,024 logical shards, then map those to 8 machines. What does the indirection buy?

Say this in 60 seconds

I would not shard first. Reads scale with replicas and a cache, and archiving cold rows often buys a year, so I would shard when writes exceed one primary or the working set no longer fits in memory. The shard key comes from the highest volume query, usually a hashed user or tenant id, so most reads hit one shard. I would avoid a time based key, because traffic concentrates on recent data and the newest shard becomes permanently hot. I would hash to a large fixed number of logical shards and map those to machines in a small table, so growing the cluster moves shards rather than rehashing rows. And I would keep the ability to override placement for a single key, because hashing spreads load evenly only when the data itself is even, and one enterprise tenant is never even.

IndGeek provides solutions in the software field, and is a hub for ultimate Tech Knowledge.