Do now
- Set a timer.
- Attempt the problem. Stop at the attempt line. Do not scroll.
- Then read.
Time box
40 minutes.
| Minutes | Do this |
|---|---|
| 0–12 | Attempt. Polite frontier, dedupe, freshness. Do not melt one host. |
| 12–32 | Read. robots.txt, per-host budgets, URL normalization. |
| 32–40 | Say frontier structure, politeness, and re-crawl policy. |
Intent
Facing the public web as input, leave able to run a polite frontier with dedupe and freshness so one host is not melted.
Problem
Design a web crawler.
You discover URLs, fetch pages, extract links, and keep content reasonably fresh. You must not overwhelm any single host.
Attempt before reading
12 minutes. Do not scroll.
Write:
- Frontier: prioritized URL queue.
- Per-host rate limit / politeness delay.
- Dedupe: URL canonicalization + content hash.
- Freshness: revisit schedule.
- Failure: soft 404, traps, infinite calendars.
Stop. Host-aware frontier. Canonical dedupe. Budgeted politeness.
Requirements
In. Billions of URLs over time; continuous crawl. Honor robots.txt. Per-host QPS caps. Store raw + extracted links. Recrawl by change rate.
Assumptions. Fetch capacity 100k/s global; per host default 1/s unless crawl-delay. Seed set large.
Out. Full search ranking (day 57 is product catalog; web search ranking out), JS rendering farm deep dive (mention as optional tier), malware detonation.
Estimates
100k fetches/s × 50 KB = 5 GB/s ingest — pipeline and storage plan. Frontier size >> RAM; persist prioritized queues per host.
API and data
Internal: frontier lease URL → fetcher → store → parser → enqueue links.
URL key: canonical form. Host key: pay-level domain / host.
robots cache per host.
Design
Frontier
Per-host queues + global scheduler that picks next host with available budget (token bucket). Prefer important URLs (seed, high inlink — simple score, not a model lecture).
Politeness
Default gap; robots crawl-delay; back off on 5xx/429. Never stampede a host with 1k parallel from many workers — central host budget.
Dedupe
Normalize scheme/host/trailing slash/query allow-list. Content hash: skip store if unchanged; still may refresh discover time.
Traps
Cap path depth, query params, calendar patterns; cloaking detection thin.
Freshness
Adaptive revisit: change often → hours; static → weeks. Priority queue by next_fetch_at.
Staff arithmetic: politeness is capacity
Crawl QPS is bounded per host, not globally — one huge site would starve the frontier if you only have a global QPS. Frontier is host-aware queues; politeness delay from robots/crawl-delay; budget per domain. Canonicalization + content hash dedupe stop the same page via many URLs from exploding storage. Recrawl priority ≠ BFS forever; freshness budgets matter. 100M URLs × politeness is a scheduling problem, not a wget loop.
Diagrams
flowchart LR
seeds[Seeds] --> front[Per-host frontier]
front --> bud[Host token bucket]
bud --> fetch[Fetchers]
fetch --> store[Content store]
fetch --> parse[Link extract]
parse --> front
Caption: “Scheduler is host-aware. Workers never bypass the bucket.”
Failure the user sees
Banned by a major host. Too aggressive; missing that site’s pages. Politeness and shared IP reputation matter.
Duplicate near-identical pages. Storage and ranking pollution — canonical + hash dedupe.
Frontier explosion. Redirect/calendar traps; budget per host and max depth/URL patterns.
Stale corpus. Recrawl never revisits important hosts — freshness policy missing.
robots.txt ignored. Legal/product failure; cache robots with TTL and obey.
Trade-offs
BFS vs prioritized freshness. Priority by importance/change rate for production; BFS for teaching.
Name the refusal inside each alternative. Against global-only rate limit: you refuse host bans and unfairness. Against storing every URL variant: you refuse dedupe death. Against ignoring robots: you refuse policy violation. Against unbounded JS-rendered crawl as default: you refuse a second browser farm unless required. Against one queue for all hosts: you refuse head-of-line blocking by one slow host.
10× URLs. More frontier workers; per-host caps unchanged.
Talking points
If they use one global QPS. “Per-host politeness or you get banned and you starve small hosts.”
If they ask dedupe. “Canonical URL + content hash. Redirect chains resolve before enqueue.”
If they ask JS rendering. “Optional expensive path for allow-listed hosts; not default.”
If they ask what pages. “Per-host error/ban rate, frontier size growth, robots fetch failures.”
If they ask seeding. “Seed set + sitemaps; not a single homepage forever.”
Say this in the room
A crawler is a host-aware frontier with per-host politeness budgets, not a global wget loop — one huge domain must not starve the rest or get us banned. We canonicalize and content-hash to dedupe, obey robots.txt with a cached copy, and recrawl by freshness priority rather than blind BFS forever. Redirect and calendar traps get depth/budget caps. Rendering JS is an allow-listed expensive path, not the default.
Staff depth: host-aware frontier
Per-host queues + crawl-delay. Global QPS alone is insufficient.
Dedupe. Canonical + hash.
What staff sounds like. Politeness as capacity; refuse trap explosion; robots obeyed.
More probes, with the answer
“What pages?” Name the user-visible lag or error metric from Failure — not only CPU. “What do you refuse?” Pick one refusal from Trade-offs and say the lie it prevents. “What is the sensitive assumption?” The estimate that flips the design if wrong by 10×.
Kit artifact
| Follow-up | You answer with |
|---|---|
| “Politeness?” | Per-host token bucket + robots + backoff; centralized budget. |
Design log
One line: how you prevented many workers from hitting one host in parallel.
Next: Day 74 — Order book. Single sequence matcher; rebuild after crash.