Cruxly

Cruxly › System design › Foundations of Data Systems

Partitioning and Sharding

the shard key is the design

Open the interactive lesson

When one machine cannot hold the data or absorb the writes, you split it. The split is the hard part: choose the key badly and you get hot partitions, cross-shard queries and a migration you cannot perform online.

Establish that you need it

Sharding costs complexity, so earn it with a number. One well-provisioned OLTP primary handles roughly 10-20k small durable writes/s (more with careful batching), and modern hardware holds tens of terabytes.

ReasonShard?
Write throughput exceeds what one primary can absorbYes
Data volume exceeds one node's storageYes
Blast radius: one failure should take out 1/N of users, not allYes
Read throughputNo. Replicas are simpler

If none applies, say so: "3,000 writes/s is one Postgres primary with room to spare. I'll keep it simple and revisit at 10x." Candidates lose points for unjustified complexity far more often than for insufficient scale.

Choosing the key

You need all three properties.

PropertyPassesFailsWhy it fails
High cardinalityuser_id — millions of valuescountry — about 200Not enough distinct values to spread across partitions after 10× growth
Even distributionuser_idconversation_idFine until one conversation has 50,000 members
Query alignmentconversation_id for a chat readmessage_idPerfect spread, but every conversation read becomes a scatter-gather

:::key Shard by whatever appears in the WHERE clause of your hottest read Cardinality and distribution are the easy two. Query alignment is where answers fall apart: a key can be statistically perfect and still force every common read to fan out to every shard. Even spread is worthless if nothing can be answered from one partition. :::

Hash, range, and directory

StrategyHowGood atCosts
Hashpartition = hash(key) % NEven distributionRange queries become scatter-gather
RangeContiguous key ranges per partitionEfficient range queriesSequential keys create a hot partition
DirectoryExplicit lookup table from key to partitionTotal flexibility: move one noisy tenant to its own shard. Right for multi-tenant systems with wildly uneven tenantsA lookup on every operation, and a service that must not go down

:::warn The % N trap Changing N remaps nearly every key, so adding one node effectively invalidates the whole placement. Consistent hashing fixes it: adding a node moves only 1/N of keys. :::

Walk the ring. "Consistent hashing" recited without the walk sounds memorized.

  1. The hash space is a circle, say [0, 256), wrapping from 255 back to 0. Servers A, B, C hash to points on it.
  2. A key hashes to its own point and walks clockwise to the first server, its owner: key-1 → A, key-2 → B.
  3. Add D just behind B. D only takes the keys in its own arc: key-2 → D. key-1 never crosses D's arc and stays on A.
  4. Remove A instead: its keys walk on clockwise to the next server, so key-1 → B. Keys that weren't on A don't move.

Adding or removing one server only perturbs the keys in that server's arc, never the rest of the ring. That's why it costs 1/N instead of "nearly everything."

Virtual nodes. With one point per server, arc sizes are luck of the hash: A gets a huge arc, C a sliver everyone piles onto. Give each physical server many points scattered around the ring. Its total coverage is the sum of many small arcs, which averages out close to 1/N. Without them the distribution comes out lumpy.

Range keys need a high-cardinality prefix:

shard by timestamp  →  every write today lands on today's partition
shard by (user_id, timestamp)  →  user_id spreads writes,
                                  timestamp still orders within a user

Hot partitions

Even a good key produces outliers: the celebrity account, the enormous group chat, the tenant a hundred times bigger than everyone else.

:::key Lead with detection, because it's what gets pushed on "I'd see one partition's p99 write latency and CPU climbing while the others stay flat, so I need per-partition metrics. An average across shards hides exactly this: the healthy shards dilute the one that's dying." :::

Then the fixes, in the order you'd reach for them:

FixHowCost
Sub-partition the hot keyconv_id:bucket, bucket a small random numberReads must query all k buckets and merge — apply only to keys you've identified as hot
Dedicated capacityMove the outlier to its own partitionRequires a directory scheme
Cache in frontAbsorb the readsDoes nothing for a write hotspot
Change the approach entirelyFan out writes for normal users; skip fan-out for celebrities and merge at read timeTwo code paths, chosen by a threshold — usually the best answer when available

What sharding costs you

Say these unprompted. This is the part that shows you've lived with a sharded system.

What you loseThe workaroundWhat the workaround costs
Cross-shard transactionsA saga: steps plus compensating actionsNo atomicity; you hand-roll rollback
Cross-shard joins, global indexesA separate index table sharded by that columnIt's now eventually consistent with the primary data
Cheap aggregatesRunning countersCOUNT(*) was a scatter-gather; now it's bookkeeping you maintain
Easy uniquenessA separate authority, or IDs unique by constructionTimestamp + node + counter — which at least sorts roughly by time

:::key The real fix for cross-shard transactions Choose a key so that things that must be transactional share a shard. A saga is the answer when you're stuck. A key that keeps the invariant inside one partition is the answer while you're still designing. :::

Rebalancing is an operational project, not a config change. Going from 8 shards to 16 means moving data while serving traffic: dual-write → backfill → verify → cut over reads → stop the old write. Knowing that order is a strong signal on its own.

The pre-emptive trick: create many more logical partitions than physical nodes up front, say 1,024 logical on 8 physical. Growth becomes reassigning logical partitions to new hardware, a cheap metadata change, instead of a re-hash that moves nearly every key.

How to say it in the interview

"At 50k writes per second a single primary is an order of magnitude over, so this has to partition. The key decision is the shard key — I'll pick one, then immediately check it for hot partitions and for queries it makes expensive."

Checklist

Practice this interactively
Share this lessonWhatsAppLinkedInX

Related lessons