hub.gazar.dev · senior-to-staff · worked system design

Design Google Search: web search

A fully worked answer in the course order: requirements, numbers, API, data, the high-level diagram, the deep dives that actually decide it, then reliability, observability, and the trade-offs. The lever here is the inverted index: turn the whole web into a term-to-documents map, shard it across thousands of machines, and answer a query by scattering to all shards and gathering the best. Back to the course.

The prompt, scoped

"Design Google Search" is "crawl the web, index it, and answer queries fast and well." We keep the spine: a crawler that discovers and fetches pages, an indexing pipeline that builds an inverted index, and a query path that retrieves and ranks results, plus typeahead. Out of scope, said up front: the real ranking ML, ads, personalization, and the knowledge graph. The hard parts are the index and the scatter-gather query.

Step 1

Requirements: functional and non-functional

The functional core:

Out of scope (say it): the ranking model itself, ads, personalization, knowledge panels.

The non-functional shape:

billions of documents query p99 < 200ms read-heavy serving freshness matters, eventual is OK availability over completeness

Two facts drive it: the index is an enormous, mostly-static artifact that must be queried in milliseconds, and crawling/indexing is a continuous, asynchronous pipeline behind it. A result a few hours stale is fine; a slow or failed query is not.

Maps to S2 (requirements) and S1 (availability vs completeness on the query path).

Step 2

Estimation: the numbers that bound the design

QuantityAssumptionResult
Documents~billions of pagesthe index is far too big for one machine
Index sizepostings for every termsharded across thousands of serving nodes
Queriestens of thousands+/secread-heavy; must fan out and return in ms
Crawlcontinuous, prioritizedre-crawl important / changing pages more often
The shapeone query touches every shardscatter-gather is the query pattern

The number that decides the architecture

The index does not fit on one machine, not close. So it is sharded by document across thousands of nodes, and a query scatters to all of them in parallel, each returning its best local matches, then a coordinator gathers and ranks. That single fact, "the index is too big, so fan out", shapes the whole serving path.

Maps to S2 (capacity estimation) and S4 (sharding the index).

Step 3

API: query and suggest

EndpointDoesNotes
GET /search?q=ranked resultsscatter-gather across index shards, then rank; returns top-K + snippets
GET /suggest?prefix=typeaheada separate, very fast prefix service over popular queries
crawler (internal)fetch + parse pagesfed by the URL frontier; emits documents to the indexer
indexer (internal)build the inverted indexbatch/stream pipeline writing index shards

Only two public endpoints. The heavy machinery, crawling and indexing, is an internal pipeline that produces the artifact the query path reads.

Maps to S5 (API design): a tiny read surface over a huge prebuilt index. See the API cheat sheet.

Step 4

Data model and store choices

Document(doc_id PK, url, title, fetched_at, page_rank)
InvertedIndex:  term -> posting list [ (doc_id, positions, score), ... ]
Frontier:       priority queue of URLs to crawl (+ seen-set for dedup)
LinkGraph:      doc -> outlinks            (feeds PageRank / authority)
Suggest:        trie of popular queries by frequency

stores
  Inverted index .. huge, read-heavy, sharded BY DOCUMENT   -> in-memory / SSD across many nodes
  Doc store ....... url, snippet, metadata                  -> distributed KV
  Frontier ........ priority queue + seen-set               -> queue + a dedup store
  Suggest ......... prefix lookups                          -> in-memory trie service

Why document partitioning, not term partitioning

You can shard the index two ways. By term (each node owns some terms, holds the full posting list) means a multi-term query hits only a few nodes, but those posting lists are gigantic and create hot spots. By document (each node owns a slice of docs, with the full term dictionary for them) means every query scatters to every shard, but each shard does a small, balanced amount of work. Web search uses document partitioning: balanced load and easy parallelism beat fewer-nodes-per-query.

Maps to S3 (the index as the access-pattern-driven structure) and S4 (partitioning strategy). See the data-stores cheat sheet.

Step 5

High-level architecture

Two halves meeting at the index. The crawl + index pipeline (top) pulls URLs from the frontier, fetches pages from the web, and the indexer writes them into the document-partitioned index shards. The query path (bottom) takes a search to a coordinator that scatters to every shard and gathers the ranked results. The index is the artifact one side builds and the other side reads.

URL frontier priority queue The web Crawlers fetch + parse Indexer build index Inverted index shards doc-partitioned replicated · thousands of nodes User search Query coordinator scatter-gather + rank fetch next URLs docs new URLs found write index query scatter / gather
Crawl + index (top): the frontier feeds crawlers, which fetch the web and emit docs to the indexer, which writes the document-partitioned index shards. Crawlers feed newly discovered URLs back into the frontier. Query (bottom): the coordinator scatters a search to every shard and gathers the ranked results. Blue = the index, green = the frontier queue, orange = the external web.

Maps to S7 (architectural styles) and S6 (pipelines, scatter-gather).

Step 6

Deep dive A: the crawler frontier

Crawling the web is a scheduling problem, not a fetching one. The frontier decides what to crawl next, how often, and how to do it without hammering any one site.

the frontier = a priority queue of URLs + a seen-set

priority
  - important pages (high PageRank, news) re-crawled often
  - the long tail crawled rarely
  - new links discovered during a crawl are enqueued

politeness
  - rate-limit per domain (robots.txt, crawl-delay)
  - do not let 1000 crawlers hit one host at once
  - partition the frontier by host so per-host limits are local

dedup
  - a seen-set (e.g. a Bloom filter over URL hashes) avoids
    re-crawling the same URL endlessly
  - canonicalize URLs (strip fragments, normalize) before dedup

Why the frontier is the hard part

The naive crawler floods sites and loops forever on the same pages. The frontier turns crawling into a governed schedule: prioritize by importance and freshness, dedup with a seen-set, and enforce politeness per host. It is the scheduler that makes a planet-scale crawl both polite and productive.

Maps to S4 (partitioning by host) and S9 (politeness is rate-limiting; be a good citizen).

Step 6 · continued

Deep dive B: the inverted index and scatter-gather

The data structure that makes search possible, and the query pattern it forces. An inverted index maps each term to the list of documents that contain it.

inverted index
  "system"  -> [doc3, doc9, doc42, ...]   (posting list, with scores)
  "design"  -> [doc9, doc42, doc88, ...]
  query "system design" -> INTERSECT the posting lists -> candidates

document partitioning (web search)
  - shard N holds a slice of documents + the full term dict for them
  - a query goes to EVERY shard (scatter)
  - each shard intersects locally and returns its top-K
  - coordinator merges all shards' top-K (gather), re-ranks, returns

  scatter-gather with a TIMEOUT
  - a slow shard is dropped; serve from the rest
  - missing a few docs from one shard is acceptable; waiting is not

The senior point

Two moves score. First, document partitioning so every shard does a small, balanced amount of work and the query parallelises cleanly. Second, a strict timeout on the gather: a query is only as fast as its slowest shard, so you drop stragglers and serve a 99%-complete result in time rather than a perfect result too late. Completeness yields to latency, on purpose.

Maps to S4 (document partitioning) and S1 (latency vs completeness).

Step 6 · continued

Deep dive C: ranking and typeahead

Retrieval finds candidates; ranking orders them; typeahead is a separate fast path. Ranking is two phases so the expensive scoring only touches a few documents.

ranking (two phase)
  1. RETRIEVAL (cheap, in the shard): intersect posting lists,
     score by BM25 / TF-IDF, return each shard's top-K candidates
  2. RE-RANK (expensive, in the coordinator): score the merged
     candidates with richer signals (authority/PageRank, freshness,
     query features) -> final top-10
  => cheap filter over billions, expensive scoring over hundreds

typeahead (separate service)
  - a trie / prefix tree of popular queries, weighted by frequency
  - prefix lookup returns the top completions in ms
  - rebuilt periodically from query logs; it does not touch the index

Why two-phase ranking

You cannot run an expensive relevance model over billions of documents per query. So retrieval is a cheap, index-native filter that produces a few hundred candidates, and the costly re-ranking runs only on those. The same shape appears in feeds and recommendations: a fast recall step, then a slow precision step on the survivors.

Maps to S1 (simplicity vs capability: phase the work) and S4 (typeahead as a separate cache).

Step 7

Reliability & failure design

If this fails…ModeWhat the user sees
An index shard (slow or down)fail opendropped from the gather by timeout; results 99% complete, still fast
A shard replicadegradeanother replica serves that document slice; no gap
Crawler / indexerretryasync pipeline; a failed fetch or index job retries, freshness lags slightly
Frontier overloadsheddeprioritize low-value crawls; protect query serving capacity
Coordinatordegradestateless and replicated; another coordinator handles the query

Query serving is built to degrade, not fail: shards are replicated, the gather drops stragglers on a timeout, and coordinators are stateless and replaceable. The crawl/index pipeline is asynchronous and retryable, so a failure there costs freshness, never availability. Everything is bounded with timeouts, and the scatter-gather timeout is the single most important reliability knob.

Maps to S9 (designing for failure): fail open on a shard, drop stragglers. See the resilience cheat sheet.

Step 7 · continued

Observability & operability

Maps to S10 (observability). See the observability cheat sheet.

Step 8

Trade-offs along the six core axes

Axis (S1)The call hereWhy it is least-wrong
Latency vs completenesstimeout slow shardsa 99%-complete result in time beats a perfect one too late
Consistency vs availabilityavailability + eventual indexa slightly stale result is fine; the query must always answer
Space vs timea huge precomputed indexspend storage to turn a search into milliseconds of lookups
Partitioningby document, not termbalanced shard load and clean parallelism over fewer-nodes-per-query
Simplicity vs capabilitytwo-phase rankingcheap recall over billions, expensive precision over hundreds
Freshness vs costprioritized re-crawlcrawl important/changing pages often, the long tail rarely

The whole design is one structure and one pattern: an inverted index too big for any machine, sharded by document, queried by scattering to every shard and gathering the best within a deadline. Build it with a governed crawl, rank it in two phases, and say that, and the boxes follow.

Maps to S1 (the six core trade-offs) and S12 (present and defend).

Use this in the cohort