Deep dive 2 · 20 min

Sharding Strategies

Sharding splits data across machines so no single database limits storage or write throughput. The shard key you pick is the most important decision.

Say it in 30 seconds
  • 80% vs 20%keys moved 4 to 5 nodes: modulo vs ring
  • 16,384hash slots in Redis Cluster
  • 4,096/msSnowflake ids per worker per millisecond
Show what to say

“I shard last, only when writes or data size outgrow one primary after indexes, caching and replicas. The shard key matters most: high cardinality and matching the main query, usually user_id. I'd use consistent hashing with virtual nodes, so adding a node moves about 1 over N of the keys instead of nearly all of them.”

Trade-offs

  • Hash vs range sharding: hash spreads evenly but loses range scans; range keeps scans cheap but timestamps and auto-increment ids make a hot shard.
  • Directory vs hash: a lookup table lets you move one huge tenant alone, at the cost of an extra, cached lookup.
  • Salting vs a plain key: salting spreads a hot key over N shards, but reads cost N times more, so salt only measured hot keys.

Bring it up before they ask

A query without the shard key becomes scatter-gather, where the slowest shard sets the latency, so keep those off hot paths.

New to the words? Read the one-minute primer

0 of 0 sections ticked
30-second answer

only when one database can't hold the data or the writes. The matters more than the scheme; moves only about 1/N of keys when a node joins.

When to shard

One database is one checkout till.

Read more · 340 words

opens more tills: it splits the data across several databases (), each holding a slice.

It is the last resort, because joins, and changes get harder forever. Try the cheap fixes first:

  1. A bigger machine.
  2. Indexes. Most "slow databases" are missing one.
  3. and for reads.
  4. Archiving cold rows.

Shard when writes or data size outgrow one , rarely for reads alone. Do the sum first. 500 million users at 5 KB each is 2.5 TB. That fits on one big machine, so size alone is a weak reason. Writes are the usual one.

Your systemShard?Why
200 GB, 2K writes a secondNoOne tuned primary copes
2.5 TB, 3K writes a secondNot yetTry a bigger machine, replicas, a cache
5 TB, 50K writes a secondYesOne primary cannot take the writes

The numbers are rough. A tuned primary often handles a few thousand writes a second; tens of thousands is where sharding starts to pay for its pain. Walk the tree with your own numbers. Most systems stop at the first or second question.

Do you need to shard yet?yesnoyesnoShardNot yetDo writes (tens ofthousands a second) ordata (many terabytes)Do those firstHave you tried indexes,a bigger machine, acache, read replicas and

Have you tried indexes, a bigger machine, a cache, read replicas and archiving cold rows?

Choosing the shard key

The decides which queries stay on one and whether the load spreads, so it matters more than the scheme you hash it with.

Read more · 420 words

A good key:

  • Has many values. country has about 200, so 200 shards at most.
  • Spreads evenly. No value owns a big share of the traffic.
  • Matches your main query. Mostly "this user's orders"? Use user_id.
  • Avoids hot ranges. Timestamps and auto-increment ids pile new writes on one shard.

Run a candidate through the tree. Every "no" is a reason to pick a different column.

Try some keys for an orders table:

KeyVerdictWhy
user_idGoodMany values, spread evenly, one user's orders stay together
countryWeakAbout 200 values, and one country can hold most users
is_premiumBadTwo values means two shards, ever
created_atBadEvery new write lands on the newest shard

The usual answer is user_id (tenant_id in SaaS). It is the default: start there unless the main query says otherwise.

Skew is how much more the busiest shard carries than its fair share, and it sets your real capacity. 10,000 writes a second over 4 shards is 2,500 each when the key spreads evenly. Take some made-up but plausible workloads. If one country holds 60% of users, its shard takes 6,000 writes a second, 2.4 times the fair 2,500. If one tenant is 40%, that shard takes 4,000. With created_at ranges the newest shard takes all 10,000.

If one shard tops out at 5,000 writes a second, the country key already fails at 6,000, and the timestamp key fails at twice its limit, while three machines sit idle. Adding shards does not fix skew: the hot value still lands on one of them.

Store the data twice. When one key cannot serve two big access patterns, keep two copies with two keys. Buyers ask "my orders" and sellers ask "orders for my shop", and no single column serves both. Write the order once to the store by buyer_id, then copy it by event into a second store sharded by seller_id. Use it when both queries are on hot paths. The price is double storage and a lag of milliseconds to seconds on the second copy. If only one pattern is hot, serve the other with instead.

Is this a good shard key?yesyesnonoyesnoyesnoGood key, plan for hotvaluesHash it, do not range itA good shard keyDo new rows always getlarger values (atimestamp, anCan one value carry ahuge share of thetraffic (a celebrity, aToo few valuesDoes it have far moredistinct values than youwill have shardsWrong keyDoes your most commonquery filter on thiscolumn?

Does your most common query filter on this column?

Share of writes on the busiest of 4 shards
0255075100%25%user_id, hashed40%tenant, one at 40%60%country,one at 60%100%created_at ranges
Show data table
Share of writes on the busiest of 4 shards
%
user_id, hashed25%
tenant, one at 40%40%
country, one at 60%60%
created_at ranges100%

Press Play to watch it run · 3 steps

Orders are written once by the app and copied by an event to a second store sharded by seller, so buyer queries and seller queries each touch one shard.

One table, two shard keysOrders are written once by the app and copied by an event to a second store sharded by seller, so buyer queries and seller queries each touch one shard.writepublishcopyreadOrder serviceOrders by buyershard key buyer_idEvent streamorder createdOrders by sellershard key seller_id

Tap any box to see what it is and what it talks to.

  • Service
  • Database
  • Queue
Open in playground
Text description
  1. A buyer places an order. It is written to the store sharded by buyer_id, the source of truth.
  2. The service publishes an order-created event.
  3. A consumer inserts the same order into the store sharded by seller_id, a few milliseconds to seconds later.

Strategies and consistent hashing

A router (a library or a proxy) turns the into a shard, like a mail sorter reading the postcode.

Read more · 765 words

Play the four scenarios.

There are five ways to turn a shard key into a shard, and hash on user_id is the default answer. Pick another only when a specific need names it.

StrategyHow it worksUse it whenCosts
Hash mod N (the default)hash(key) mod N picks the shard. Load spreads evenly because hashes look random, and a lookup by key touches one shardYou read and write by key and do not scan ranges: users, orders by user, sessions. MongoDB hashed shard keys work this way; Cassandra and DynamoDB hash partition keys too, though they use a ring or partitions rather than a plain modRange scans (every shard answers). Growth: plain mod moves N/(N+1) of keys when N changes
RangeEach shard holds a contiguous slice of the key space, so neighbours live together and a range scan stays on one shardYou scan by key interval and new keys do not arrive in order: (sensor_id, time), alphabetical listings, a leaderboard cut into score bands. Bigtable, HBase and Spanner split ranges this way, automatically when a range growsHot last shard on timestamps or auto-increment ids, where every new write hits the last range
DirectoryA table says which shard each key lives on, so you can place any key anywhere and move it laterA few keys are huge or special: a SaaS with 1 million small tenants and a handful 50 times the median, or a tenant that pays for isolation. 1 million tenants at about 50 bytes each is 50 MB, so the directory fits in memoryAn extra lookup on every request (cache it, about 1 ms); a directory that must be replicated (a single point of failure); stale entries after a move. The shard must refuse a tenant that is no longer its own, so the app refreshes and retries
GeoEach user's data lives in the region where they live, chosen once at signup. It is a directory whose key is a regionLaw or latency demands it: data residency rules that keep EU data in the EU, or a round trip across the Atlantic (about 90 ms) against a local one (under 10 ms) felt on every click. Balance is not the goal, so a busy region simply gets more shardsGlobal queries that fan out across oceans, and users whose home region is wrong
Fixed slots (Redis Cluster)Hash every key into a fixed number of slots (16,384 in Redis Cluster), then assign slots to machines. Adding a node moves whole slots, so keys are never rehashedYou want growth without consistent hashing: about the same 25% moved going from 3 to 4 nodes. Pick many more logical shards than machines, as the resharding section showsSlots must be assigned and tracked

Play Add a shard to see the catch of plain mod.

. Picture a clock face. Nodes and keys both land on it, and a key belongs to the next node clockwise. Add a node and only its neighbours' keys move. Drag (extra points per node) down to 1 and watch the load drift uneven.

With hash % N, N/(N+1) of keys move when you add a node. A ring moves only 1/(N+1).

At 4 to 5 nodes, that is 80% versus 20%: the size of a 's miss storm. With 1 million keys, hash mod N moves 800,000 and the ring moves 200,000. Even doubling from 4 to 8 nodes moves half the keys with mod, because a key stays only when hash mod 4 equals hash mod 8.

gets the same 1/N result without a ring: every key scores each node with hash(node + key) and picks the highest. Removing a node moves only its own keys. It is simpler to code, and it costs one hash per node per lookup, fine for tens of nodes.

Press Play to watch it run · 5 steps

The app hands every query to a shard router, which picks one of three shards by shard key. Each shard is a primary with its own replica; shard 3 joins only for resharding.

Routing requests to shardsThe app hands every query to a shard router, which picks one of three shards by shard key. Each shard is a primary with its own replica; shard 3 joins only for resharding.newbackfillAppA client library, or a proxy such as Vitess vtgate or MongoDB mongos.Shard routerkey → shardShard 0primaryShard 1primaryShard 2primaryShard 3 (new)resharding onlyReplica 0Replica 1Replica 2

Tap any box to see what it is and what it talks to.

  • Service
  • API gateway
  • Database
Open in playground
Text description
  1. The app saves an order for userId 42 through the shard router.
  2. hash(42) mod 3 = 2, so the row goes to shard 2. Any query with the user id touches exactly one shard.
  3. Shard 2 replicates to its own replica: every shard is a small primary/replica cluster.
  4. Shard 2 confirms the commit.
  5. Done. Later reads for user 42 run the same function and land on the same shard.

Press Play to watch it run · 3 steps

The router computes hash(key) mod N and sends the row to that shard. Shard 3 is empty until the shard count goes from 3 to 4.

Hash shardingThe router computes hash(key) mod N and sends the row to that shard. Shard 3 is empty until the shard count goes from 3 to 4.AppHash routerhash mod NShard 0Shard 1Shard 2Shard 3 (new)N goes 3 to 4

Tap any box to see what it is and what it talks to.

  • Service
  • API gateway
  • Database
Open in playground
Text description
  1. Fetch user 42. Say hash(42) is 7,000,013.
  2. 7,000,013 mod 3 is 2, so the router asks shard 2 and nobody else.
  3. One shard, one lookup. Even load is the strength of hashing.
Consistent hashing is a clock face of servers: each key walks clockwise to the next server it meets.
Interactive

Consistent hashing ring

Add or remove a node: only the keys next to it move. Raise virtual nodes to even out the load.

4 nodes20 vnodes each

Add or remove a node to see how many keys move. Outlined dots are keys that changed owner.

Keys per node

  • Node A30 keys
  • Node B21 keys
  • Node C31 keys
  • Node D38 keys

Ideal (dashed): 30 keys each. Spread (max ÷ min): 1.81×.

% of keys moved when going from N to N+1 nodes
  • hash % N
  • Consistent hashing
0255075100%27111620Nodes before adding one (N)hash % NConsistenthashing
Show data table
% of keys moved when going from N to N+1 nodes
Nodes before adding one (N)hash % N (%)Consistent hashing (%)
266.733.3
37525
48020
583.316.7
685.714.3
787.512.5
888.911.1
99010
1090.99.1
1191.78.3
1292.37.7
1392.97.1
1493.36.7
1593.86.3
1694.15.9
1794.45.6
1894.75.3
19955
2095.24.8

Cross-shard queries, transactions and ids

Any query without the must ask every , and any write across two shards loses the database's .

Read more · 675 words

"Top 10 posts today" on 64 shards is 64 calls for one question.

sends the query to every shard in parallel and merges the answers, and the slowest shard sets the . Say each shard is slow 1% of the time. With 64 shards, the chance that at least one is slow is 1 minus 0.99 to the 64th power, about 47%. Your per-shard becomes nearly the median. Use it for admin pages, reports and rare searches. For a hot path, precompute: a background job keeps the answer in a small table. Play , One slow shard and Precomputed fix.

Global . A second table, by the column you want to search, that maps it to the : for a login by email, one lookup in the index, then one lookup on the right user shard. Use it for the few lookups on other columns that happen on every request: email, phone, username. Compare a local index, which sits inside each shard and still needs a fan-out. The index costs a second write, and the two writes are not one transaction, so it can lag or miss. Write it through an and retry a not-found once. Unique columns need care: two users signing up with the same email on different shards need the index to be the arbiter.

Cross-shard transactions. locks rows on every shard until a coordinator says commit, so it is slow, and if the coordinator dies the locks hang. The better answer is to design it away: keep data that changes together on one shard (an order and its lines both keyed by user_id). When a workflow must span shards, use a : a chain of local transactions, each with a written undo. Play payment fails.

A saga is not isolated: other readers can see the reserved item before the charge settles. Make every step and every undo safe to retry, and keep the orchestrator's progress in a table so a crash resumes instead of forgetting.

Ids across shards. Two auto-increment primaries hand out the same id. Options:

  • UUIDs: time-ordered UUIDv7 is fine; random v4 fragments indexes.
  • A : simple, but one more service.
  • : timestamp, worker id and sequence in 64 bits. No coordination per id.

Press Play to watch it run · 3 steps

A coordinator fans one query out to every shard and merges the answers. A summary table avoids the fan-out for hot paths.

Scatter-gather and its slow shardA coordinator fans one query out to every shard and merges the answers. A summary table avoids the fan-out for hot paths.readAppCoordinatormergeShard 0Shard 1Shard 2slow todayTop-10 tableprecomputed

Tap any box to see what it is and what it talks to.

  • Service
  • API gateway
  • Database
  • Cache
Open in playground
Text description
  1. Top 10 products by sales. No shard key in the query, so every shard may hold part of the answer.
  2. The coordinator asks each shard for its own top 10, in parallel.
  3. Three lists of 10 come back. It merges 30 rows and keeps the best 10.

Press Play to watch it run · 6 steps

Each step is a local transaction on one shard. If a later step fails, the earlier steps are undone by compensating actions.

A saga across shardsEach step is a local transaction on one shard. If a later step fails, the earlier steps are undone by compensating actions.reserve 1 itemreservedalt [all steps work]charge 20 dollarschargedcreate ordercreated
  • Service
  • Database
Text description
  1. Orchestrator → Stock shard: reserve 1 item
  2. Stock shard replies to Orchestrator: reserved
  3. Orchestrator → Payments shard: charge 20 dollars
  4. Payments shard replies to Orchestrator: charged
  5. Orchestrator → Orders shard: create order
  6. Orders shard replies to Orchestrator: created

Hot keys and resharding

A good can still have one scorching value, and then one machine melts while the others idle.

Read more · 720 words

There are three shapes, and each has its own fix.

  • One . A celebrity with 100 million followers sends every like to one key: 160,000 likes a second hit a node that tops out near 100,000. Salt the key into N sub-keys (likes:42:0 to likes:42:15): write to a random one, sum them on read. With 16 salts that is about 10,000 writes a second per sub-key. Reads cost 16 times more, so salt only measured hot keys, and the summed count for a second. Another way out is to count in the app server's memory and flush a batch every second, if losing a second on a crash is acceptable.
  • One . A carries far more than its share because of how keys are assigned, not because one key is huge. The classic cause is a range or timestamp key. Fix it at the key: hash it, and keep time as the sort key inside the shard. Watch each shard's writes per second and its disk growth; a shard at 2 times the average is the early warning.
  • One giant tenant. One customer holds a big share of rows, such as a single business with 40% of a SaaS's data. Hashing keeps all of it together, on one shard. Give that tenant a dedicated shard through the directory, and leave the rest hashed. Do the same for a tenant who pays for isolation.

Play The problem, Salted writes and Summed reads.

means moving data to new shards while the system keeps serving traffic, and it is a step sequence, not an event: six steps, and only the last cannot be undone.

  1. Add the new shards

    Empty, before any traffic moves.

  2. Dual-write

    Moving keys write to both. The old shard stays the truth.

  3. Backfill

    Copy old rows in throttled batches.

  4. Verify

    Compare counts and checksums.

  5. Flip reads

    A few percent at a time, behind a flag.

  6. Stop old writes and clean up

    Delete the moved rows. Only this step is irreversible.

The sum for a slow, safe move: 2 TB copied at a throttled 50 MB per second is 40,000 seconds, about 11 hours. That is fine, because the old shard keeps serving. Throttle the copy so it does not starve live traffic, and do it at the quiet hours.

What goes wrong: the backfill race. A backfill copying row 42 as version 1 can land after a live dual-write put version 2 there, and the new shard ends up with stale data that checksums on counts will not notice. Make the copy an upsert that only writes if its version is newer, so it is safe to replay. Verify with per-range checksums of the row contents, not only counts, before flipping any reads. Keep the old data until the new shards have served reads cleanly for a day.

Plan ahead with logical shards. The cheapest reshard is the one that rehashes nothing. Create many logical shards (say 256) on a few machines from day one, and map logical shards to machines in a small table. Play Add machine D.

Growing from 3 to 4 machines moves 64 of the 256 logical shards, 25% of the data, each as a small job with the steps above. The step from key to logical shard never changes. This is the Cluster idea, and the default way to avoid a painful reshard. The limit: you can never have more machines than logical shards, so choose generously.

Press Play to watch it run · 2 steps

One celebrity counter takes every like on a single node. Salting writes to a random one of N sub-keys and sums them when reading.

Salting a hot keyOne celebrity counter takes every like on a single node. Salting writes to a random one of N sub-keys and sums them when reading.allFansApplikes:42one key, one nodelikes:42:0node 1likes:42:1node 2likes:42:15node 3

Tap any box to see what it is and what it talks to.

  • Client
  • Service
  • Cache
Open in playground
Text description
  1. A celebrity posts. 160,000 likes a second arrive for one post.
  2. All of them increment the same key, so the same node. If one node tops out near 100,000 operations a second, it melts while the cluster idles.

Press Play to watch it run · 3 steps

Keys hash into 256 logical shards that never change. Machines own logical shards, so growing moves a few of them and rehashes nothing.

Logical shards on few machinesKeys hash into 256 logical shards that never change. Machines own logical shards, so growing moves a few of them and rehashes nothing.moveKeyLogical shardhash mod 256Machine A0 to 84Machine B85 to 170Machine C171 to 255Machine Dnew, empty

Tap any box to see what it is and what it talks to.

  • Client
  • API gateway
  • Database
Open in playground
Text description
  1. hash(42) mod 256 is 106, so user 42 is in logical shard 106. That number never changes for this user.
  2. A table says logical shards 85 to 170 live on machine B, so that is where the row is.
  3. Done.

Code

Hash sharding

hash-shard.js
import { createHash } from 'node:crypto';
const hash = s => createHash('md5').update(s).digest().readUInt32BE(0);

// Hash the key and take it modulo the shard count. Even spread, but changing the count moves almost every key.
export const shardFor = (id, shardCount) => hash(String(id)) % shardCount;

// const db = pools[shardFor(userId, pools.length)];

Range sharding

range-shard.js
// Each shard owns a contiguous range of ids. Range scans stay on one shard,
// but sequential keys (auto-increment ids, timestamps) send every new write to the last shard.
const ranges = [{ max: 1e6, db: 'db0' }, { max: 2e6, db: 'db1' }, { max: Infinity, db: 'db2' }];
export const shardFor = id => ranges.find(r => id < r.max).db;

Directory (lookup) sharding

directory-shard.js
// A lookup table maps each tenant to a shard. The hot copy lives in Redis; the directory DB is the source of truth.
export async function shardForTenant(tenantId) {
  const shard = await redis.hget('shard-map', tenantId)
    ?? await directory.lookup(tenantId);
  return shard;
}
// Moving one huge tenant is a data copy plus one row update: directory.assign('acme', 'shard-7')

Consistent hashing

hash-ring.js
import { createHash } from 'node:crypto';
const hash = s => createHash('md5').update(s).digest().readUInt32BE(0);

export class HashRing {
  constructor(nodes = [], vnodes = 100) { this.vnodes = vnodes; this.ring = []; nodes.forEach(n => this.add(n)); }
  add(node) {
    for (let i = 0; i < this.vnodes; i++) this.ring.push({ pos: hash(`${node}#${i}`), node });
    this.ring.sort((a, b) => a.pos - b.pos);
  }
  remove(node) { this.ring = this.ring.filter(p => p.node !== node); }
  get(key) {                                        // binary search for the first point at or after hash(key)
    const h = hash(key);
    let lo = 0, hi = this.ring.length;
    while (lo < hi) {
      const mid = (lo + hi) >> 1;
      if (this.ring[mid].pos < h) lo = mid + 1; else hi = mid;
    }
    return this.ring[lo % this.ring.length].node;   // wrap around the ring
  }
}
// 4 → 5 nodes moves ~20% of keys (all of them to the new node); hash % N would move ~80%

Rendezvous hashing

rendezvous.js
import { createHash } from 'node:crypto';
const hash = s => createHash('md5').update(s).digest().readUInt32BE(0);

// Rendezvous (highest random weight) hashing: each key picks the node with the largest hash(node + key).
// No ring to maintain, and removing a node only moves that node's keys.
export const rendezvous = (key, nodes) => nodes.reduce((best, n) => (hash(n + key) > hash(best + key) ? n : best));

Online resharding

resharding.js
// Online resharding: never stop the world. The old shard stays the source of truth until reads flip.
export async function save(row) {
  await Promise.all([oldShard.write(row), newShard.write(row)]); // dual-write
}
// backfill in background, compare checksums, flip reads, delete old data

Scatter-gather

scatter-gather.js
// Scatter-gather: a query that can't use the shard key asks every shard, then merges, re-sorts and cuts
export async function topProducts(limit = 10) {
  const perShard = await Promise.all(shards.map(db =>
    db.query('SELECT id, name, sales FROM products ORDER BY sales DESC LIMIT $1', [limit])));
  return perShard.flatMap(r => r.rows).sort((a, b) => b.sales - a.sales).slice(0, limit);
}
// Latency = the slowest shard. If this query is frequent, maintain a precomputed global table instead.

Hot-key salting

hot-key-salting.js
// Hot-key salting: spread one celebrity counter over 16 sub-keys; reads add them up.
// (Separate GETs rather than MGET, because in Redis Cluster the sub-keys live in different slots.)
const SALTS = 16;
export const like = postId => redis.incr(`likes:${postId}:${Math.floor(Math.random() * SALTS)}`);
export async function likes(postId) {
  const parts = await Promise.all(Array.from({ length: SALTS }, (_, i) => redis.get(`likes:${postId}:${i}`)));
  return parts.reduce((sum, v) => sum + Number(v ?? 0), 0);
}

Snowflake ids

snowflake.js
// Snowflake-style 64-bit ids: unique across shards with no coordination, roughly time-ordered.
// 41 bits ms since a custom epoch (~69 years) | 10 bits worker | 12 bits sequence (4,096 ids/ms/worker)
const EPOCH = 1_704_067_200_000n;                       // 2024-01-01T00:00:00Z
export function snowflake(workerId) {
  if (!Number.isInteger(workerId) || workerId < 0 || workerId > 1023) throw new RangeError('workerId must be 0–1023');
  let lastMs = -1n, seq = 0n;
  return function next() {
    let now = BigInt(Date.now());
    if (now < lastMs) throw new Error('clock moved backwards');
    if (now === lastMs) {
      seq = (seq + 1n) & 4095n;
      if (seq === 0n) while ((now = BigInt(Date.now())) <= lastMs) {}   // sequence exhausted: wait for the next ms
    } else seq = 0n;
    lastMs = now;
    return ((now - EPOCH) << 22n) | (BigInt(workerId) << 12n) | seq;
  };
}
// Send ids to browsers as strings: 64-bit values don't fit in a JS number.

Sharding in the interview

Bring up only after you have shown that one cannot take the writes, then walk through five decisions in order.

Read more · 430 words

The order is what shows you have done this before.

  1. Prove one database is not enough

    Do the sum out loud: "50,000 writes a second against a primary that handles a few thousand, and 5 TB of data."

  2. Choose the shard key from the main query

    Name the query, then the column: user_id, or tenant_id for SaaS. Say what you rejected: timestamps and low-cardinality columns.

  3. Pick the strategy

    Hash on the key by default. Range only for scans, a directory for giant tenants, geo for residency.

  4. Handle the fallout

    Cross-shard queries go to or a global index, off the hot path. Name the hot-key fix: salting, or a dedicated .

  5. Say how you grow

    Many logical shards on few machines, or , and the online steps: dual-write, backfill, verify, flip reads.

If the interviewer saysYou raise
"Writes outgrow the primary"Shard by user_id, hash, after replicas and caching
"We need to scan by date"Hash by id, sort by time inside the shard; or range with a hash prefix
"One customer is 40% of the data"A directory and a dedicated shard
"Users must stay in their country"Geo sharding with a home region
"How do you add a shard?"Logical shards, then dual-write and backfill

Quick checkWhich decision comes right after proving one primary is not enough?

Interview questions

What's the shard key for a multi-tenant SaaS?

tenant_id, with a directory so huge tenants get their own .

How do you run queries that span shards?

Keep them off hot paths: for admin, else a global index.

How do you reshard without downtime?

Add shards, dual-write, backfill, verify, flip reads, stop old writes.

Why do you need virtual nodes?

One point per node can leave a node with twice its share. Many points even it out and spread a dead node's keys over many survivors.

Hash or range sharding for time-series data?

Hash by series and sort by time inside each shard. Pure range-by-time sends every write to the newest shard.

Check yourself

Question 1 of 5

Score 2 out of 3 or better to mark this page complete.

1. Going from 4 to 5 nodes with hash % N moves about…

Patterns behind this

The coding patterns this design is built on. Practise them and the design questions get easier.

Next: Next: choosing the right store for each kind of data.