~learn.

Google Search, the whole design.

A web search engine that answers 100K queries a second over 50B+ pages in under 500 ms, because the index is sharded by document, every query fans out to all shards in parallel, and crawling stays off the clock.

7 diagrams · 8 sections · revise in ~10 min · Download PDF prints it in the light theme, diagrams included

SQ-05 · Google Search · design sheethimanshukukreja.in/learn/sheets/bonus-05-google-search

At a glance#

Number Value What it drives
Searches ~14B / day (2025 estimate) ~160,000 / s on average
Design load 10K → 100K QPS peak more replicas per shard, result cache (~30 % hits)
Pages 50B → 500B (design); Google ~400B docs (2020) thousands of document shards
Query latency < 500 ms p99 understand 20 · retrieve 100 · score 200 · snippets 50 · respond 30 ms
Inverted index 25T postings · 300 TB raw · ~60 TB compressed ~180 TB with 3 replicas: sharded, hot parts in memory
Page store ~2.5 PB (50B × 50 KB compressed) the biggest storage item, not the index
Scoring ~10,000 candidates × ~10 μs ≈ 100 ms cap candidates before scoring
Freshness breaking news < 15 min · most < 24 h fresh tier + priority crawl

Architecture#

DiagramSearch on one page: crawl and index offline, scatter-gather on the query path
%% title: Search on one page: crawl and index offline, scatter-gather on the query path
flowchart TD
  U@{ icon: "sd:users", label: "Users<br/>~14B searches a day" } --> FE["Query frontend<br/>spell · tokenize · synonyms · intent · 20 ms"]:::edge
  FE --> QC@{ icon: "sd:cache", label: "Result cache<br/>short TTL · ~30 % hits" }
  FE ==> RT["Root / merger<br/>scatter to every shard · gather local top-k"]:::hot
  RT ==> SH@{ icon: "sd:index", label: "Document-sharded index<br/>thousands of shards · 3–10× replicas" }
  RT --> FT@{ icon: "sd:index", label: "Fresh tier · in memory<br/>priority-crawled news, in minutes" }
  RT ==> RK["Ranker<br/>BM25 · PageRank · freshness · location · clicks<br/>snippets for the top 10"]:::core
  FR@{ icon: "sd:queue", label: "URL frontier<br/>priority · 1 req/s per domain" } --> CW@{ icon: "sd:robot", label: "Crawler workers<br/>robots.txt · async fetch" }
  CW -.->|"new links"| FR
  CW --> PS@{ icon: "sd:database", label: "Page store<br/>~2.5 PB compressed" }
  PS --> IB["Indexer<br/>tokenize · build segments · PageRank"]:::service
  IB -->|"finished segments"| SH
  class U client
  class QC cache
  class SH,FT,PS store
  class FR queue
  class CW service
DiagramScatter-gather: the slowest shard sets the pace
%% title: Scatter-gather: the slowest shard sets the pace
flowchart TD
  RT["Root · fans out to ~1,000 shards<br/>all fast only ~0.004 % of the time"]:::hot --> S1["Shard 1 · docs 0–1M<br/>intersect postings · local top-k"]:::service
  RT --> S2["Shard 2 · docs 1M–2M<br/>local top-k"]:::service
  RT --> SN["Shard N · primary slow"]:::bad
  SN -->|"no answer in a few ms"| HR["Hedged request to a replica<br/>take whichever answers first"]:::queue
  S1 --> MG["Merge local top-k → ~10,000 candidates<br/>deadline: return what has arrived"]:::ok
  S2 --> MG
  HR --> MG

Key flows#

DiagramA query: "best pizza near me" in under 500 ms
%% title: A query: "best pizza near me" in under 500 ms
sequenceDiagram
  autonumber
  participant U as User
  participant F as Query frontend
  participant R as Root / merger
  participant S as Index shards (all)
  participant K as Ranker
  U->>F: best pizza near me
  F->>F: spell · tokenize · synonyms · local intent (20 ms)
  alt popular query, cached
    F-->>U: cached results
  else cache miss
    F->>R: parsed query
    R->>S: same query to every shard in parallel
    S-->>R: local top-k each (100 ms budget)
    R->>K: ~10,000 merged candidates
    K->>K: BM25 · PageRank · freshness · location · clicks → top 100 (200 ms)
    K->>K: snippets for the top 10 (50 ms)
    K-->>U: results page (~400 ms)
  end
DiagramTail latency: one shard is slow
%% title: Tail latency: one shard is slow
sequenceDiagram
  autonumber
  participant R as Root / merger
  participant P as Shard 7 primary
  participant Q as Shard 7 replica
  R->>P: query
  Note over R: no answer after a few ms
  R->>Q: hedged request, same query
  alt replica answers first
    Q-->>R: local top-k
    Note over R: use it · ignore the late primary
  else both miss the deadline
    Note over R: return without shard 7 · slightly incomplete beats slow
  end
DiagramBreaking news: searchable within 15 minutes
%% title: Breaking news: searchable within 15 minutes
sequenceDiagram
  autonumber
  participant D as News detector
  participant C as Crawler
  participant T as Fresh tier
  participant R as Root / merger
  D->>D: query spike · new entities
  D->>C: trusted sources jump the frontier
  C->>T: index new pages in seconds
  R->>T: query the fresh tier
  R->>R: + main index · boost freshness, trust
  R->>R: cache hot query, short TTL
  Note over T: merged into the main index later

Data model#

DiagramCore entities
%% title: Core entities
classDiagram
  class Shard {
    +shardId : int
    +docRange : slice of doc IDs
    +replicas : 3 to 10
  }
  class PostingsList {
    +term : string
    +docFrequency : int · for IDF
    +postings : sorted by docId
  }
  class Posting {
    +docId : int
    +termFrequency : int
    +positions : int list
    +size : ~12 B
  }
  class Document {
    +docId : int
    +url : string
    +pageRank : float
    +lastCrawled : timestamp
    +contentFingerprint : hash · dup and spam check
  }
  class CrawlURL {
    +url : string
    +priority : importance + freshness
    +lastCrawl : timestamp
    +depth : int · trap guard
  }
  Shard "1" --> "many" PostingsList : every term for its docs
  PostingsList "1" --> "many" Posting : holds
  Posting "many" --> "1" Document : points to
  CrawlURL "1" --> "0..1" Document : fetched as

Lifecycle#

DiagramA page's state machine, from link to results
%% title: A page's state machine, from link to results
stateDiagram-v2
  [*] --> DISCOVERED: link extracted
  DISCOVERED --> QUEUED: new URL, deduplicated
  QUEUED --> FETCHED: crawled, polite rate
  FETCHED --> FAILED: error or robots.txt
  FETCHED --> REJECTED: spam, duplicate or trap
  FETCHED --> FRESH_TIER: breaking news
  FETCHED --> INDEXED: segment built
  FRESH_TIER --> SERVING: searchable in minutes
  INDEXED --> SERVING: segment pushed to shards
  SERVING --> QUEUED: recrawl due
  FAILED --> [*]
  REJECTED --> [*]
  class SERVING ok
  class FAILED,REJECTED bad
  class QUEUED queue

Decisions#

Decision Chose Over Because
Index partitioning By document By term even load; common terms would make hot shards
Serving more QPS More replicas per shard More shards every query hits every shard anyway
Tail latency Stage budgets, deadlines, hedged requests Wait for every shard 0.99^1000 ≈ 0.004 % all fast
Crawl and index Async pipelines feeding finished segments Updates on the query path slow crawl = staler, never slower
Freshness Small in-memory fresh tier Rebuilding the main index more often merging hundreds of TB takes hours
Index consistency Eventual, stale within hours Strong across shards strict agreement would slow every query
Text relevance BM25, then PageRank and other signals Raw TF-IDF term-frequency saturation, length normalization

When it breaks#

Failure What users see Contain it with
Breaking news, 10× spike fast, slightly stale results short-TTL result cache, more replicas for hot shards
One shard slow normal speed, rarely a missing hit per-shard deadline, hedged request to a replica
Shard unavailable results silently missing a slice critical alert even though queries still return
Crawler or indexer stalls new pages show up late freshness alert; serving unaffected
Link farms and spam pages junk in results link graph analysis, content fingerprints, ML classifiers
Crawl trap (endless URLs) crawl budget wasted trap URL patterns, depth limits, unique-content ratio

Say it in the interview#