SystemDesign.website
Track 10 ยท Case Study 02โฑ๏ธ ~35 min readAlex Xu Ch. 9 / Googlebot

Design a Distributed Web Crawler (Googlebot)

Learn how search engines index the entire World Wide Web: Discover the 8-stage crawl pipeline, URL Frontier priority vs. politeness queues, DNS resolution caches, SimHash near-duplicate detection, and spider trap defenses.

01

Requirements & Scale Estimation

Functional Requirements
  • Scalability: Crawl 1 Billion web pages every month (or 10B pages total index cycle).
  • URL Frontier: Store and prioritize billions of discovered URLs without duplicate crawling.
  • Politeness: Never spam a single target host. Strictly respect robots.txt and domain rate limits.
  • Extensibility: Pluggable parser modules for HTML, PDFs, images, and semantic metadata.
Non-Functional & Robustness
  • Fault Tolerance: Crash-resilient worker fleets; state checkpoints in persistent queues.
  • Deduplication: 29% of web pages are mirrors/copies. Detect near-duplicates in real time.
  • Anti-Traps: Detect infinite dynamic calendar links and recursion loops (/a/b/a/b/).
  • DNS Bottleneck Protection: Async local DNS cache to avoid overloading global DNS servers.
โšก Back-of-the-Envelope Math (1 Billion Pages / Month)
CRAWL THROUGHPUT
~385 pages / sec
1B รท (30d ร— 86,400s). Peak @ 2x: ~800 QPS
STORAGE REQUIRED
500 TB / month
1B ร— 500 KB avg HTML size = 500 TB/mo (30 PB / 5 yrs)
NETWORK BANDWIDTH
1.54 Gbps
385 QPS ร— 500 KB = 192.5 MB/s (1.54 Gbps)
02

The 8-Stage Distributed Crawl Pipeline

A crawler begins with a set of Seed URLs (e.g. top Wikipedia categories or university domains). As pages are fetched, outbound links are extracted and filtered, continuously feeding the pipeline in a massive distributed graph traversal.

Planetary Crawler Component Flow
Seed URLsCurated Seed ListURL FrontierPriority & Politeness2-Queue MatrixHTML DownloaderDistributed WorkersAsync I/O PoolDNS Cache / Resolver< 1ms In-Memory HitContent ParserExtract <a href> LinksDOM SanitationContent Seen? (SimHash)Hamming Distance ≤ 3Discard Exact & Near-DupesURL NormalizerStrip #anchors & paramsFilter Blacklisted URLsURL Seen? (Bloom Filter)Zero-Disk RAM FilterPrevent Infinite CyclesFeed Unseen URLs
03

Deep Dive: The URL Frontier (Politeness vs. Priority)

๐Ÿ’ก Analogy: The Polite Mail Carrier

If a crawler pulls URLs using a simple FIFO queue, 1,000 concurrent threads might all attempt to download pages from wikipedia.org at the exact same millisecond. This constitutes a Denial of Service (DoS) attack on the host server. The URL Frontier solves this using a 2-Queue Matrix:

1. Front Queues (Priority Filter)

Prioritizes high-value domains (e.g. major news outlets, top PageRank nodes, frequently updated pages) over low-reputation spam blogs.

โ€ข Queue F1 (High): Breaking news, CNN, Wikipedia
โ€ข Queue F2 (Med): Personal blogs, corporate sites
โ€ข Queue F3 (Low): Unindexed archive directories
2. Back Queues (Politeness Manager)

Each target host domain (e.g. apple.com) is routed to its own dedicated queue. A worker thread is assigned to that host and enforces a mandatory delay (e.g. 500msโ€“2s) between downloads.

โ€ข Host Queue Table: host_id → queue_id
โ€ข Delay Heap: Next allowed timestamp for host
โ€ข Rule: Only 1 thread downloads from 1 host at a time
Staff Engineer Takeaway: The URL Frontier stores its active queues in memory (e.g. Redis Cluster), but persists overflow backlogs to distributed NVMe SSD storage (Kafka partitions or RocksDB) so crashes never lose discovered graph state.
04

Deep Dive: Content Deduplication (SimHash) & Bloom Filters

SimHash (Near-Duplicate Detection)

Standard MD5/SHA-256 cryptographic hashes produce completely different digests if even 1 character changes (avalanche effect). SimHash maps similar documents to 64-bit fingerprints where the Hamming Distance measures similarity:

โ€ข Tokenize HTML text into weighted n-grams
โ€ข Hash each token to a 64-bit vector
โ€ข Sum weights: if bit=1 add weight, else subtract
โ€ข Final fingerprint: if sum > 0 bit=1, else 0
โœ“ Hamming distance ≤ 3 = 95%+ Near Duplicate!
Bloom Filter (URL Deduplication)

Checking whether a URL has already been visited across 1 Billion URLs requires fast RAM lookup without executing expensive disk database queries:

โ€ข 1 Billion URLs @ 1% false positive rate = ~1.2 GB RAM
โ€ข If Bloom Filter says "NO" → 100% Never Seen (Crawl it)
โ€ข If Bloom Filter says "YES" → Confirm with KV Store
05

Spider Traps, Malicious Loops & Robots.txt

A Spider Trap is a web page designed (or accidentally configured) to cause a web crawler to fetch an infinite number of unique URLs. Common traps include:

1. Infinite Calendars
Links like /events?year=2027&month=1 generating endless future date pages.
2. Dynamic URL Nesting
Subdirectories that recursively link to themselves: /dir/dir/dir/dir/...
3. Tarpits / Slowloris
Servers that stream bytes at 1 byte/sec to exhaust crawler thread worker pools.
๐Ÿ›ก๏ธ Production Countermeasures:
  • Strict Depth Limit: Maximum crawl depth ≤ 10 from seed origin.
  • Max URL Length: Reject URLs longer than 512 characters.
  • Aggressive Socket Timeouts: Abort any fetch exceeding 5 seconds total or 1 second socket idle.
  • Robots.txt Cache: Fetch and cache /robots.txt in Redis with a 24-hour TTL per host.
06

Politeness Frontier Worker Implementation (5 Languages)

Review production implementations of the Polite Host Rate-Limiting Crawler Worker across Go, Python, Java, Rust, and TypeScript:

polite_crawler_worker
package main

import (
	"fmt"
	"net/url"
	"sync"
	"time"
)

type PoliteFrontier struct {
	mu          sync.Mutex
	lastFetch   map[string]time.Time
	minInterval time.Duration
}

func NewPoliteFrontier(interval time.Duration) *PoliteFrontier {
	return &PoliteFrontier{
		lastFetch:   make(map[string]time.Time),
		minInterval: interval,
	}
}

func (pf *PoliteFrontier) FetchURL(targetURL string) error {
	u, err := url.Parse(targetURL)
	if err != nil {
		return err
	}
	host := u.Host

	pf.mu.Lock()
	lastTime, exists := pf.lastFetch[host]
	now := time.Now()

	if exists && now.Sub(lastTime) < pf.minInterval {
		sleepDuration := pf.minInterval - now.Sub(lastTime)
		pf.lastFetch[host] = now.Add(sleepDuration)
		pf.mu.Unlock()

		time.Sleep(sleepDuration) // Polite delay enforced per host
	} else {
		pf.lastFetch[host] = now
		pf.mu.Unlock()
	}

	fmt.Printf("[GO CRAWL 200 OK] -> %s (Host: %s)\n", targetURL, host)
	return nil
}
๐ŸŽ“ CAPSTONE CHALLENGE ยท HANDS-ON STUDIO

Draw & Validate the Googlebot Architecture

Open the Interactive Whiteboard Studio. Place the URL Frontier, Distributed Fetchers, SimHash Deduplicator, and DNS Cache onto the canvas, and get graded with โœจ Evaluate Using AI!

07

FAANG Interview Scenarios Quiz

SCENARIO 1: THE RECURSIVE CALENDAR TRAP

Your distributed web crawler enters an infinite crawling loop on an e-commerce site whose dynamic URLs continually generate /products/filter?color=red&size=L&page=14592. What is the most effective architectural defense?

SCENARIO 2: DNS RESOLUTION BOTTLENECK

At 1,000 requests/sec, your crawler threads spend 80% of their execution time blocked on socket DNS lookups. How do high-performance web crawlers eliminate this latency?

โ—‹

Completed this module?

Mark as finished to update your curriculum progress tracker.