Scaling reads — indexes, replicas, Redis, CDN, stampede protection

Scaling reads for system design interviews

Handle massive read load without crushing the primary: indexes and denormalization, read replicas and sharding, Redis and CDN caching, then hot keys, cache stampede, and versioned invalidation — with interview scenarios.

Why reads break first

Writes create data; reads consume it. An Instagram feed load can trigger 100+ reads for one daily post. Tweets, product pages, and YouTube views all share the same imbalance: one write, thousands of readers.

This is the deep dive behind Common patterns. Pair with Database indexing, Caching strategies, Sharding, and Numbers to know.

01In-DB

Indexes · denorm · hardware.

02Horizontal

Replicas · shards.

03Cache

Redis · CDN · TTL.

04Edge cases

Hot keys · stampede · versions.

Four-rung ladder from in-DB optimize to hot-path hardening.
Exhaust simpler rungs before Redis and CDNs.

The problem

Open Instagram: dozens of photos, each needing metadata, user info, likes, comment previews — potentially 100+ reads for one session. You might write one photo a day. Amazon product views dwarf uploads; YouTube views dwarf uploads. Read-to-write often starts at 10:1 and climbs to 100:1+.

As reads climb, the primary DB saturates. This is often physics, not a bug you debug away: finite CPU instructions, RAM, disk I/O. Throwing more application code at a saturated disk doesn't help.

Optimize within your database

Before new infrastructure, squeeze headroom from one database: indexing, hardware, denormalization, materialized views.

Indexing

An index is a sorted lookup into rows — like a book index. Without one, the DB does a full table scan (O(n)). With a B-tree (most common), lookups become O(log n). Hash indexes for exact matches; specialized indexes for full-text or geo. Deep dive: Database indexing.

Full table scan versus B-tree index lookup.
Index filter, join, and sort columns first — under-indexing kills more apps than over-indexing.

Hardware upgrades

SSD vs HDD (10–100× random I/O), more RAM (working set in memory), more cores. Boring, effective — buys breathing room. Mention it; don't stop there.

Vertical scaling from HDD to SSD with more RAM and cores.
Buys time — rarely the complete interview answer.

Denormalization

Normalized schemas reduce redundancy via joins. Read-heavy paths pay for those joins on every request. Denormalize hot reads: duplicate fields into an order_summary table so one query replaces a multi-table join. Trade write complexity (update many places) for read speed — only when read/write ratio justifies it.

Normalized multi-table joins versus denormalized order_summary.
Storage & write cost up · read query down to one table.

Materialized views

Precompute expensive aggregations (avg product rating) via background jobs instead of on every page load. Refresh on a schedule or on write.

-- Expensive every page load:
SELECT p.id, AVG(r.rating) FROM products p
JOIN reviews r ON p.id = r.product_id GROUP BY p.id;

-- Precompute:
CREATE MATERIALIZED VIEW product_ratings AS
SELECT p.id, AVG(r.rating) as avg_rating
FROM products p JOIN reviews r ON p.id = r.product_id
GROUP BY p.id;

Scale the database horizontally

When one server hits limits (~50K–100K read QPS as a rough interview rule of thumb after indexing — see Numbers to know), add servers.

Read replicas

Writes to primary; reads to followers. Bonus: promote a replica on primary failure. Sync replication = consistent but slower writes; async = faster but replication lag (user writes, then reads stale from a lagging replica → read-your-writes from primary for that session).

Primary takes writes; three replicas serve reads.
Lag is the classic follow-up — know sync vs async trade-offs.

Sharding for reads

Replicas don't shrink dataset size. Huge tables still scan slowly even when indexed. Sharding splits data: faster per-query, more places to send reads. Mostly a write-scaling tool — prefer caching for many read problems. See Sharding.

Users, products, and orders databases by domain.
Functional sharding — profile reads hit a smaller Users DB.
US EU APAC regional databases.
Geographic sharding — lower latency, less load per region.

Add external caching layers

Access is skewed: viral tweets, popular products get re-read constantly. Caches store hot results in RAM — sub-ms vs tens of ms for DB. Alternatives: more replicas or cache; caches usually win for read-heavy skewed workloads.

Application-level caching

Redis/Memcached between app and DB. Cache-aside: check cache → miss → DB → populate. Celebrity profiles stay hot; inactive profiles expire after TTL.

App checks Redis then database on miss and populates cache.
Full patterns: Caching strategies.

Invalidation strategies:

  • TTL — simple; serve stale until expiry; size from NFRs ("≤30s stale search")
  • Write-through / invalidate-on-write — fresher; write latency + careful error handling
  • Write-behind — async invalidate; short stale window
  • Tagged invalidation — clear related keys by tag
  • Versioned keys — bump version; old keys become unreachable

CDN and edge caching

Cache public content at edge PoPs — Tokyo user hits Tokyo edge, not Virginia origin. Can cut origin load 90%+ for product pages, thumbnails, search results. Don't CDN private DMs or account settings (no shared hit rate).

User to regional CDN edges then origin on miss.
Shared public content only — invalidation across edges is the cost.

When to use in interviews

Almost every interview ends in scaling talk. For each high-volume API, optimize reads: query plan → replicas → cache → CDN.

Common scenarios

  • Bitly — extreme read/write; cache short→long mapping aggressively; CDN for global hits
  • Ticketmaster — cache event/venue/charts; not seat availability (oversell risk); replicas for browse, primary for buy
  • News feed — precompute active feeds; cache recent posts; paginate (users read first items)
  • YouTube — cache metadata; eventually consistent view counts; CDN thumbnails

Common deep dives

Queries slow as the dataset grows?

10K users → snappy; 10M → 30s lookups. Full table scans: find-by-email reads all rows. Add indexes; compound index column order matters ((status, created_at) helps status and status+created, not created alone).

Millions of concurrent reads for one cached key?

Celebrity post: 500K QPS on one key overwhelms one cache shard.

Many clients hammering one cache key then fanout across keys.
Coalesce inflight fetches (one backend call per app server) · or fanout key:1..N.

Request coalescing collapses N concurrent misses into one backend fetch per process. Key fanout stores identical copies under feed:star:1..10; clients pick randomly — trade memory and harder invalidation for surviving the herd.

Cache stampede on TTL expiry?

100K QPS homepage; entry expires; all miss at once → DDOS your own DB.

Mass cache miss flooding the database.
Singleflight lock · probabilistic early refresh · background refresh for critical keys.

Distributed lock: only first miss rebuilds (timeouts if rebuild fails). Probabilistic early refresh: chance of background refresh rises as TTL nears end — spreads load. Critical keys: refresh every 50 of 60 minutes so they never expire under traffic.

Immediate visibility after updates?

Venue address change can't wait an hour in CDN. Naive delete-on-write races (request between delete and new write caches stale again).

Cache key version bump from v42 to v43 after DB update.
Bump version in the same DB transaction — old keys become unreachable, no delete race.

Read version (small cached key) → fetch event:123:v43. Trade-off: two lookups; orphaned versions cleaned by TTL. For feeds/search, use a deleted-items set and filter on read while background-invalidating large structures. Match consistency to data: profiles tolerate 5 min; venues may need immediate.

In your interview

"Reads dominate — I'll scale the read path in order: indexes on filter/join/sort columns, denormalize only hot paths, read replicas with read-your-writes for the session, then Redis cache-aside with TTL sized from staleness NFRs plus invalidate-on-write for critical keys, CDN for public assets. Hot keys get coalescing or fanout; TTL cliffs get early refresh. I won't draw Redis before I can argue hit rate and invalidation."

Read path Fermi drill

Cost and performance levers

Interview Q&A by level

Practice saying these out loud for scaling reads. Interviewers grade clarity and judgment more than buzzwords.

Interview takeaway

Match depth to the bar: define → trade off → operate. Don't dump principal answers in an entry-level screen.

Wrapping up

Read scaling is the most common interview scaling challenge. Read traffic grows faster than writes; physics eventually wins. Optimize in-DB first — modern databases handle more than candidates assume when indexed — then replicas, then caches. Know performance wins and operational cost of each rung.

Continue with Common patterns, Database indexing, Caching strategies, Sharding, Redis, Design Gopuff, Design Ticketmaster, and Numbers to know.

← Lattice