SEARCH ENGINE SYSTEM DESIGN

Scaling & Infra

Caching Layer

Search is read-heavy and popular queries repeat constantly — how does caching keep the index from being hit on every request?

RedisCDNLocal CacheResult Cache
ClientBrokerScatter-Gather

Click a node to see what it does. Switch tiers above to see how the design scales.

Overview

A small number of queries account for a large share of total traffic — the same handful of terms get searched constantly. This page treats caching as its own design problem: what to cache, what pattern to use, and what breaks when a cache node fails or a popular query’s cached entry expires all at once. Worth a first-class page rather than a detail buried in Query Serving, since interviewers frequently probe it on its own.

Basic Approach — No Cache, Query the Index Directly

How it works

Every search request goes straight through the broker to the sharded index, every time, even for identical repeated queries.

Client ──▶ Broker ──▶ Scatter-Gather Across Shards

Tradeoffs

  • Pro: Zero staleness — every result reflects the current index exactly.
  • Con: Popular queries redo the full scatter-gather round trip on every single request, even though the result was identical a second ago — wasted work that a cache would eliminate entirely.
  • Con: Read latency is tied directly to scatter-gather latency, with no way to serve a hot, frequently-asked query faster than a rare one.

Scaled Approach — Cache-Aside on the Query Result

How it works

Normalize the query (lowercase, trim, sorted filters) into a cache key. On a request, check Redis first; on a hit, return immediately; on a miss, run the normal scatter-gather path, populate Redis with a TTL, and return.

Client ─▶ Broker ─▶ Redis ──(miss)──▶ Scatter-Gather ─▶ (populate Redis)
GET search:machinelearning        → cache hit, return cached result
SET search:machinelearning EX 60  → populate on miss, 60s TTL

Unlike a typical write-heavy system, there’s usually no explicit invalidation step here — a single re-indexed document could affect results for an unbounded number of cached queries, so trying to invalidate precisely isn’t practical. Instead, lean on the “latency over freshness” priority named up front: a short TTL bounds staleness to an acceptable window without needing to track which cached queries a given document update might affect.

Tradeoffs

  • Pro: Removes the vast majority of read load from the index — hot queries are served entirely from memory.
  • Pro: Cache-aside degrades gracefully — if Redis is briefly unavailable, requests just fall through to the normal scatter-gather path.
  • Con — cache stampede: when a popular query’s entry expires, every concurrent request for it misses at once and all of them hit scatter-gather simultaneously, momentarily spiking load on every shard.
  • Con: A document that was just re-indexed won’t be reflected in a cached result for that query until its TTL expires — an explicit, accepted staleness window, not a bug.

Advanced Approach — Stampede Protection, Hot Queries, and Layering

How it works

Three refinements on top of plain cache-aside:

  • TTL jitter: randomize each entry’s TTL slightly (e.g., 60s ± 10s) so queries cached around the same time don’t all expire in the same instant.
  • Request coalescing (single-flight): when a query misses, only the first request is allowed to run scatter-gather and repopulate the cache; concurrent requests for the same query wait on that in-flight result instead of each independently re-triggering scatter-gather.
  • Edge/CDN caching for the most common queries: an extremely popular query (a trending news term) can be cached at a geographically distributed edge layer, serving repeat requests without even reaching the origin broker.
Client ─▶ Edge Cache ─▶ Redis ─▶ Request Coalescing ─▶ Scatter-Gather

Tradeoffs

  • Pro: Directly addresses the two failure modes that make caching hard in practice — stampedes and hot-key skew — rather than adding a cache and hoping traffic is evenly distributed.
  • Pro: Edge caching removes the very hottest, smallest slice of traffic before it even reaches the broker or any shard.
  • Con: Meaningfully more moving parts — coalescing needs an in-flight request map per cache node, and jitter/edge placement both need tuning specific to real traffic patterns.
  • Con: Multi-layer caching (edge + Redis) means multiple places a result can go stale, each on its own TTL.

Tech Choices

  • Redis — the default shared result cache: fast key lookups, TTLs, and atomic operations if request coalescing is implemented with a lock.
  • CDN / edge cache — for the smallest, hottest slice of globally popular queries, serving them geographically close to the requester.
  • Local (in-process) cache — an LRU cache inside the broker itself for the very hottest queries, trading a little memory per broker instance for avoiding a network hop to Redis entirely.

How to Vocalize This in an Interview

Don’t introduce Redis as a given — start from “scatter-gather is expensive to redo for the same query every time” to motivate caching at all. Be explicit that search caching skips precise invalidation on purpose (unlike a typical write path), because one document change can touch too many cached queries to track — naming that tradeoff unprompted, tied back to the freshness-vs-latency priority from scoping, is a strong signal of coherent design thinking rather than a bolted-on cache.