1. Leader-Follower Replication & Read Replicas
Replication means keeping copies of the same data on multiple machines. The most common setup is leader-follower (also called primary-replica or master-slave): one node, the leader, accepts all writes; one or more follower nodes continuously replicate the leader's data and can serve reads. This does nothing for write capacity — every write still goes through the leader — but it does two things a single instance can't: it scales read throughput horizontally by spreading read queries across followers, and it improves durability and availability, since a follower can be promoted to leader if the original leader fails.
writes
[App Servers] ---------> [Leader DB]
| |
| reads (~90% of load) | replication stream
v v
[Follower DB 1] [Follower DB 2] [Follower DB 3]
^ ^ ^
+--- read queries routed here by the app layer ---+
Replication can be synchronous or asynchronous, and the choice is a direct latency-vs-durability tradeoff. With synchronous replication, the leader waits for at least one follower to confirm it has the write before acknowledging the client — a follower is always guaranteed up to date, but every write now pays the network round trip to that follower, and if the follower is slow or down, writes stall. With asynchronous replication, the leader acknowledges the write immediately and streams it to followers in the background — writes stay fast, but followers lag behind by some amount of time, and a leader crash before a write replicates means that write can be lost.
That lag is the reason read replicas aren't a free lunch: a client that writes data and then immediately reads it back from a follower can see stale data if the write hasn't replicated yet — the classic read-your-own-writes problem. Common mitigations are routing a user's own reads to the leader for a short window after they write, or using "read-after-write" session stickiness to a specific replica. This is a small, concrete preview of the strong-vs-eventual consistency tradeoff Week 9 covers formally — asynchronous replication is, in effect, choosing eventual consistency for your read replicas in exchange for write latency and availability.
A candidate who says "let's add read replicas" to solve a write-throughput problem has misdiagnosed the bottleneck — every write still funnels through one leader. If writes themselves are the bottleneck, replication alone doesn't help; you need sharding (Section 2) to split the write load across multiple leaders, each owning a different slice of the data.
4. The Operational Cost of Resharding
Resharding — changing the number of shards, or moving data because a shard grew too
hot or too large — is one of the most expensive operations in a distributed system's
lifecycle. With naive hash(key) % N sharding, changing N from 4 to 5
remaps roughly 80% of all keys to a different shard, meaning the vast majority of
rows need to be physically copied to a new machine while the system stays online and
serving traffic.
Naive hashing, N: 4 -> 5
key 101: hash % 4 = 1 -> hash % 5 = 1 (same, lucky)
key 102: hash % 4 = 2 -> hash % 5 = 2 (same, lucky)
key 103: hash % 4 = 3 -> hash % 5 = 3 (same, lucky)
key 104: hash % 4 = 0 -> hash % 5 = 4 (MOVED)
...in general, ~(N-1)/N of all keys move on almost any change
Consistent hashing: shards are placed as points on a hash
ring; a key belongs to the next shard clockwise from its own
hash position. Adding a 5th shard only steals the keys that
fall between it and its clockwise neighbor -- roughly 1/N of
the keyspace moves, not the whole dataset.
Beyond the hashing scheme, teams use two practical techniques to keep resharding tractable. First, over-provisioning logical shards: split the keyspace into far more logical partitions than physical machines up front (say, 4,096 logical shards mapped onto 8 physical database nodes), so that scaling out later means moving whole logical shards to new machines — a bookkeeping change — rather than rehashing the entire keyspace. Second, online migration with dual writes: during a migration, writes go to both the old and new shard locations while a background job backfills historical data, and reads gradually cut over once the new location is verified caught up — avoiding a hard downtime cutover.
This is also why the shard key decision in Section 3 matters so much: a bad shard key doesn't just cause a hot partition, it guarantees an expensive resharding project down the line to fix. The cost of resharding is the single strongest argument for spending real time on the shard key choice before the system launches, rather than treating it as a detail to revisit later.
When an interviewer pushes on "what happens when you need to add a shard?", naming consistent hashing specifically — and explaining that it moves roughly 1/N of the keys instead of nearly all of them — is a strong, concrete answer that shows you understand the operational cost, not just the initial design.
5. Hands-on Exercise
Design the data layer for a photo-sharing app
The app has 50M users, is read-heavy (photo views vastly outnumber uploads), and its two most common queries are "get this user's photos" and "get this photo's metadata by photo ID."
Requirements:
- Design a replication topology: how many read replicas would you start with, would you use synchronous or asynchronous replication, and what specific risk does your choice introduce? Justify each in one sentence.
- Choose a shard key for the photos table from these candidates —
photo_id,user_id,upload_date— and justify your choice against the app's two most common queries. - Name the specific hot-key risk your chosen shard key introduces, and describe one mitigation for it.
- Assuming 500M total photos and a target of no more than ~50M photos per shard, calculate the minimum number of shards needed.
- Write 2–3 sentences describing how you'd migrate from your Step 4 shard count to double that count without taking the system offline.
Step 2 has a trap: photo_id looks like the "natural" key for a photos table, but check it against "get this user's photos" specifically before choosing it. Whichever key you pick, Step 3 should name a real, specific risk — not a generic "some shards might be uneven" statement.
6. Knowledge Check
Four quick questions. Expand each to check your answer.
Q1
Why doesn't adding read replicas help a system that's bottlenecked on write throughput?
Why doesn't adding read replicas help a system that's bottlenecked on write throughput?
In leader-follower replication, every write still has to go through the single leader — followers only replicate data and serve reads. Adding more followers increases how much read traffic the system can absorb but does nothing to increase the leader's write capacity. A write-throughput bottleneck needs sharding, which introduces multiple independent leaders each owning a slice of the data, not more replicas of the same single leader.
Q2
What's the main tradeoff between range-based and hash-based sharding?
What's the main tradeoff between range-based and hash-based sharding?
Range-based sharding keeps range queries fast because contiguous key ranges live on one shard, but it tends to create a hot spot — new sequential keys (like newly created user IDs) all land on the most recent shard. Hash-based sharding spreads writes evenly across all shards by hashing the key, eliminating that hot spot, but it destroys locality: a range query like "all users created this week" now has to scatter across every shard and merge results.
Q3
What three properties should you evaluate a candidate shard key against, and why does query pattern matter as much as data distribution?
What three properties should you evaluate a candidate shard key against, and why does query pattern matter as much as data distribution?
A good shard key needs high cardinality, even distribution of data and access load, and alignment with the system's most common query pattern. Cardinality and distribution alone can produce a key that's perfectly balanced but still forces every common query to scatter across all shards and merge results — which is slow and defeats much of the point of sharding. Matching the query pattern is what keeps the majority of real queries confined to a single shard.
Q4
Why does naive hash(key) % N sharding make resharding so expensive, and how does consistent hashing fix it?
Why does naive hash(key) % N sharding make resharding so expensive, and how does consistent hashing fix it?
Because the modulus N is baked directly into every key's shard assignment, changing N — adding or removing even a single shard — reassigns roughly (N-1)/N of all keys to a different shard, requiring almost the entire dataset to be copied while the system stays live. Consistent hashing places shards as points on a hash ring and assigns each key to the next shard clockwise, so adding a shard only reassigns the keys that fall in the small arc it now owns — roughly 1/N of the keyspace instead of nearly all of it.