Web Crawler System Design Template
Polite distributed crawler: URL frontier with per-host queues, RFC 9309 robots.txt, adaptive delay, URL-seen and simhash dedup, recrawl scheduling, trap budgets
How to use: Use it when designing or interviewing on a web crawler for search, archiving or a corpus. Fill the crawl purpose and the politeness policy first, then size the fetch rate and the URL-seen set. The main pitfall is treating bandwidth as the limit when per-host politeness and traps decide what you can crawl.
Preview
System Design: [Web crawler name]
Reference design for a polite, distributed web crawler that fetches and refreshes [1 billion] pages a month for a search index or a research corpus. It covers the URL frontier with priority and per-host politeness queues (after the Mercator design), URL and content deduplication including simhash near-duplicate detection, robots.txt handling under the Robots Exclusion Protocol (RFC 9309, September 2022), sitemaps, recrawl scheduling with HTTP conditional requests, and defences against crawler traps and spam (after IRLbot). It follows the structure of the System Design Document template, adds a 45-minute interview path, access-pattern and failure-mode tables and a cited "How real systems do it" box, and is pre-filled with a worked design to adapt. Replace every
[bracketed]value and every row marked Example; values marked Example are assumptions, not benchmarks. Crawling, storing and reusing content can raise copyright, privacy and terms-of-service questions; this is a design format, not legal advice, so check the current official text and a qualified professional.
Document control
| Field | Value |
|---|---|
| Document ID | [SDD-CRAWL-001] |
| System / feature | [Web crawler for the search index] |
| Version | [0.1] |
| Status | [Draft / In review / Approved / Implemented / Superseded] |
| Owner | [@owner] |
| Author(s) | [@author] |
| Reviewers | [@reviewer-1], [@reviewer-2] |
| Last updated | YYYY-MM-DD |
| Classification | [Internal] |
| Difficulty | Intermediate |
| User-agent product token | [ExampleBot] |
| Crawler information page | [https://example.com/bot] |
| Related | [Search indexing pipeline], [Legal review of crawl purpose], [ADR: URL-seen store] |
Approvals
| Role | Name | Decision | Date |
|---|---|---|---|
| Tech lead | [@name] | [Approved / Changes requested] | YYYY-MM-DD |
| Staff / principal engineer | [@name] | [Decision] | YYYY-MM-DD |
| SRE / operations | [@name] | [Decision] | YYYY-MM-DD |
| Legal and privacy | [@name] | [Decision] | YYYY-MM-DD |
| Security | [@name] | [Decision] | YYYY-MM-DD |
Revision history
| Version | Date | Author | Change summary |
|---|---|---|---|
| 0.1 | YYYY-MM-DD | [@name] | First draft for design review |
| 0.2 | YYYY-MM-DD | [@name] | Example: Replaced fixed 1 s host delay with adaptive delay based on response time |
| 0.3 | YYYY-MM-DD | [@name] | Example: Added per-domain crawl budgets after a link-farm incident |
| 1.0 | YYYY-MM-DD | [@name] | Example: Approved |
Contents
1. Overview
1.1 Problem statement
[Example: The search index is fed by a single-machine crawler that fetches 40 million pages a month, revisits pages on a fixed 60-day cycle and keeps its URL set in memory. Index freshness complaints are rising, the crawler restarted from scratch twice last quarter after crashes, and one site owner blocked our address range after we sent bursts of parallel requests to a small server.]
1.2 Proposed solution
[Example: A distributed crawler partitions hosts across crawl nodes by a hash of the host name. On each node a URL frontier holds priority queues in front and per-host politeness queues behind, so no host ever sees more than one request at a time from us and the gap between requests adapts to how fast the host answers. Fetchers check a robots.txt cache that follows RFC 9309, use conditional requests for recrawls and store raw responses in WARC files. Extracted links pass a URL-seen check before entering the frontier; content passes exact and near-duplicate checks before indexing. A scheduler recrawls each page at an interval learned from how often it changes, and per-domain budgets stop traps and link farms from eating the crawl.]
1.3 Decisions requested from reviewers
| ID | Question | Recommended option | Section |
|---|---|---|---|
| Q-01 | Example: How are URLs partitioned across nodes? | By hash of host name, so each host's politeness state lives on one node | 7.1 |
| Q-02 | Example: How fast may we crawl one host? | One connection per host; wait max(1 s, 10 x last response time) | 7.3 |
| Q-03 | Example: Exact or approximate URL-seen set? | Exact fingerprint set on disk with a Bloom filter in front | 7.4 |
| Q-04 | Example: What happens when robots.txt returns 5xx? | Treat the site as fully disallowed, as RFC 9309 requires | 7.2 |
| [Q-nn] | [Question] | [Option] | [Section] |
1.4 45-minute interview path
Use this order when the document serves an interview or a short design review. The written sections below are the long form of each step.
| Minutes | Cover | Output on the whiteboard |
|---|---|---|
| 0 to 5 | Scope: purpose, HTML only or more, freshness, politeness; below-the-line items (2.3) | Purpose and three constraints |
| 5 to 10 | Estimates: pages per second, bandwidth, storage, URL-seen set size (section 3) | "Politeness, not bandwidth, is the limit" consequence |
| 10 to 15 | Interfaces: seed and control API, output to the indexer (section 4) | Two interfaces |
| 15 to 25 | High-level design: frontier, fetcher, robots cache, parser, dedup, store (section 6) | The crawl loop diagram |
| 25 to 40 | One deep dive: frontier and politeness (default, 7.1 and 7.3), or deduplication (7.4), or recrawl scheduling (7.5) | Options, choice, reason |
| 40 to 45 | Failures (node loss, DNS outage, trap), the 10x row in section 10 | Two failure rows |
Start the default deep dive with the naive answer, one first-in first-out queue shared by all fetchers. Show that relative links make long runs of URLs from the same host, so many fetchers hit one server at once. Then introduce the back queues with one host each and a heap ordered by the next allowed contact time.
2. Requirements
2.1 Core functional requirements
| ID | Requirement | Priority |
|---|---|---|
| FR-01 | Example: Crawl HTML pages over HTTP and HTTPS starting from seed lists and sitemaps | Must |
| FR-02 | Example: Obey robots.txt as specified in RFC 9309 for our product token | Must |
| FR-03 | Example: At most one open connection per host; adaptive delay between requests | Must |
| FR-04 | Example: Never fetch the same URL twice in one crawl cycle unless it is due for recrawl | Must |
| FR-05 | Example: Detect exact and near-duplicate content and keep one canonical copy | Must |
| FR-06 | Example: Recrawl pages at an interval based on their observed change rate | Must |
| FR-07 | Example: Store raw responses and extracted text for the indexer | Must |
| FR-08 | Example: Honour noindex and nofollow robots meta tags and the X-Robots-Tag header in the indexer | Should |
| FR-09 | Example: Let operators block a host or path within 10 minutes | Must |
| [FR-nn] | [Requirement] | [Must / Should / Could] |
2.2 Non-functional requirements
| ID | Quality | Target | Measured as |
|---|---|---|---|
| NFR-01 | Throughput | Example: 1 billion successful page fetches per 30 days | Fetch counter |
| NFR-02 | Politeness | Example: Zero hosts with two concurrent connections from us | Connection audit per host |
| NFR-03 | Freshness | Example: Median age of the indexed copy of top 10 million pages under 3 days | Sampled recrawl comparison |
| NFR-04 | Recoverability | Example: A node crash loses at most 10 minutes of crawl progress | Chaos test |
| NFR-05 | Duplicate waste | Example: Under 5% of fetches return content already stored | Content-hash hit rate |
| NFR-06 | Complaints | Example: Every site-owner complaint answered within 1 business day | Ticket system |
| [NFR-nn] | [Quality] | [Target] | [Measurement] |
2.3 Below the line (out of scope)
Name these explicitly so reviewers and interviewers do not pull the design toward them.
- [Example: rendering JavaScript-heavy pages in a headless browser (separate render queue, later release)]
- [Example: indexing, ranking and serving search results (see
system-design/design-full-text-search)] - [Example: crawling behind logins or paywalls (never done by this crawler)]
- [Example: images, video and PDF files (link recorded, not fetched)]
- [Item deferred, with the release that will take it]
3. Capacity estimation
3.1 Assumptions
| Input | Symbol | Value | Source |
|---|---|---|---|
| Pages fetched per 30 days | Example: 1 billion | Index plan | |
| Peak to average ratio | Example: 2 | Scheduler smoothing | |
| Average HTML size, uncompressed | Example: 100 KB | Sample crawl | |
| Stored size after compression | Example: 25 KB | Sample crawl, 4:1 | |
| Overhead for robots.txt, redirects, retries | Example: 20% | Sample crawl | |
| Links extracted per page | Example: 50 | Sample crawl | |
| Known URLs (seen set) | Example: 10 billion | Link discovery estimate | |
| Average fetch time including connection | Example: 1 s | Sample crawl | |
| Parse CPU per page | Example: 5 ms | Profiling | |
| Months of history kept | Example: 6 | Index requirement |
3.2 Fetch rate and bandwidth
Consequence: one data-centre uplink of a few Gbit/s is enough. Bandwidth is not the hard constraint.
3.3 Concurrency and politeness
By Little's law, fetches in flight at peak are . Because each host gets at most one connection, at least 772 different hosts must be ready to fetch at any moment. Following the Mercator rule of thumb of three back queues per fetch slot, each crawl cluster keeps about active host queues.
The per-host limit also caps what one site can get: at one request per second, a site of 10 million pages needs days for a full pass, longer than the 30-day cycle. At a 10 s gap (a response time of 1 s times 10) a host gets at most 8 640 pages a day. Consequence: large sites are crawled by priority and sitemaps, not exhaustively, and the frontier must hold a long backlog per host on disk.
3.4 Storage
Frontier: about 150 bytes per queued URL with metadata; a backlog of URLs is about 150 GB, so the frontier lives on disk with only the head of each queue in memory.
3.5 Deduplication sets
URL-seen set as exact 8-byte fingerprints: , partitioned by host across nodes and kept on SSD.
A Bloom filter in front with a 1% false-positive rate needs bits per URL and about 7 hash functions, so about 12 GB in memory. A false positive means a new URL is wrongly treated as seen and skipped, so the filter only short-cuts "seen" answers that the exact set then confirms (7.4).
Link checks: lookups/s on average (38 600 at peak), about 1.67 billion a day. Consequence: batch lookups per node (sort and merge against the on-disk set, as IRLbot's DRUM does) instead of one random disk read per link.
Near-duplicate fingerprints: 64-bit simhash per page, per table; four permuted tables for a Hamming distance of 3 need 32 GB (7.4).
3.6 Summary
| Quantity | Estimate | Design consequence |
|---|---|---|
| Fetch rate | ~386 pages/s average, ~772 peak | A small fetcher fleet with asynchronous I/O |
| Bandwidth | ~370 Mbit/s average, ~741 peak | Not the bottleneck |
| Hosts in flight | at least ~772, ~2 316 active queues | Per-host back queues with a ready-time heap |
| Large sites | ~116 days for 10 million pages at 1 request/s | Priority and sitemaps, not full coverage |
| Storage | ~25 TB/month, ~225 TB for 6 months stored | Compressed WARC in object storage |
| URL-seen | 80 GB exact, 12 GB Bloom filter | Host-partitioned, batched checks |
| Parse CPU | ~3.9 cores at peak | Parsing is cheap; scale on I/O |
4. API design
4.1 Control and output interfaces
| Interface | Operation | Purpose |
|---|---|---|
| Control API | POST /v1/seeds | Add seed URLs or sitemap URLs with a priority |
| Control API | PUT /v1/hosts/{host}/policy | Block, slow down or set a budget for a host |
| Control API | GET /v1/hosts/{host}/status | Queue length, last fetch, robots state for support |
| Control API | POST /v1/recrawl | Force recrawl of a URL list (operator only) |
| Output stream | crawl.fetched events | One event per stored response for the indexer |
| Output stream | crawl.removed events | URL gone (404, 410) or newly disallowed |
4.2 Fetch request sent to sites
The crawler identifies itself and uses conditional headers on recrawl (RFC 9110):
GET /news/2026/10/a-story HTTP/1.1
Host: www.example.org
User-Agent: Mozilla/5.0 (compatible; ExampleBot/1.0; +https://example.com/bot)
Accept: text/html,application/xhtml+xml
Accept-Encoding: gzip, br
If-None-Match: "a7c3-5f1e"
If-Modified-Since: Tue, 27 Oct 2026 08:12:00 GMT
A 304 Not Modified answer costs a few hundred bytes instead of the page.
A 429 or 503 with Retry-After pauses the host for at least that time.
4.3 Output event
Each stored response produces one event; the hash below is shortened for the example.
{"url": "https://www.example.org/news/2026/10/a-story", "canonical_url": "https://www.example.org/news/2026/10/a-story", "fetched_at": "2026-10-30T09:14:05Z", "status": 200, "content_type": "text/html", "warc_ref": {"file": "crawl-20261030-0912-n17.warc.gz", "offset": 18273645}, "content_sha256": "3b1f9c0d7e2a4b6c", "simhash": "9f3a5c7e01d2b4a6", "duplicate_of": null, "robots_meta": ["index", "follow"], "links_out": 47}
5. Data model
5.1 Entities
Host state drives politeness; URL state drives scheduling; content records point to raw bytes in WARC files.
5.2 Storage choices
| Data | Store | Key | Main queries | Why |
|---|---|---|---|---|
| Frontier queues | Disk-backed queues per node, heads in memory | Priority level; host | Next URL for the next ready host | 150 GB backlog cannot live in memory |
| Host state and robots cache | In-memory map per node, checkpointed | host | Is this path allowed, when may we fetch next | Needed on every fetch |
| URL-seen set | Sorted fingerprint files on SSD per node, Bloom filter in memory | url_fp (8 bytes) | Batch check and insert | 80 GB exact set, batched access |
| URL state for recrawl | Example: Distributed key-value store | url_fp | Due URLs by time, change history | Survives node moves |
| Raw responses | WARC files in object storage | File and offset | Indexer reads, reprocessing | Standard archive format, cheap storage |
| Content and simhash index | Key-value store and in-memory permuted tables | content_sha256; simhash blocks | Exact and near-duplicate checks | Fast lookups |
5.3 Access patterns
| Access pattern | Frequency | Key or index that serves it | Partition key rationale |
|---|---|---|---|
| Pick the next URL to fetch | ~772/s at peak | Heap of host queues by next allowed time | Hash of host: one node owns each host, so politeness needs no coordination |
| Check robots rules for a URL | Every fetch | Host map, rules cached up to 24 h | Same host partition |
| Check if a link is new | ~19 300/s average | Bloom filter, then batched merge against url_fp files | Link is routed to the node that owns its host; most links are same-host |
| Find duplicate content | Every 200 response | content_sha256, then simhash tables | Global, sharded by hash prefix |
| Find URLs due for recrawl | Continuous | next_due_at per host | Host partition keeps due URLs next to their politeness state |
| Block a host on request | Rare | Host map plus control API | Push to the owning node within minutes |
6. High-level design
| Component | Responsibility | Scales by |
|---|---|---|
| URL frontier | Priority front queues, per-host back queues, ready-time heap | Hosts per node |
| Fetchers | Asynchronous HTTP, conditional requests, timeouts | Connections per node |
| Robots cache | Fetch, parse and cache robots.txt per host (7.2) | With host partition |
| DNS resolver | Asynchronous resolution with a local cache | Resolver instances |
| Parser and link extractor | Extract links, canonical tags, robots meta | CPU cores |
| URL filter and normaliser | Normalise, drop out-of-scope and trap-like URLs | CPU cores |
| Host splitter | Route each URL to the node that owns its host | Nodes |
| URL-seen check | Bloom filter and batched exact set | Disk per node |
| Content dedup | Exact hash, simhash near-duplicate search | Memory for tables |
| Recrawl scheduler | Compute next due time per URL (7.5) | With URL state store |
7. Detailed design
The deep dives in this section, each with options, choice and reason:
| Deep dive | Options considered | Choice | Reason | Section |
|---|---|---|---|---|
| Frontier and partitioning | One global queue; partition by URL hash; partition by host hash | Host hash, front and back queues per node | Politeness state local to one node; most links are same-host | 7.1 |
| Politeness | Fixed delay; delay from robots extensions; adaptive delay | Adaptive: max(1 s, 10 x last response time), back off on errors | Fast sites crawl faster, slow sites are protected | 7.3 |
| URL-seen | In-memory set; Bloom filter only; exact disk set with batching | Bloom filter plus batched exact disk set | No silent loss of new URLs; bounded memory | 7.4 |
| Recrawl | Fixed cycle; sitemap only; change-rate model | Change-rate model plus sitemap hints and conditional requests | Spend fetches where content changes | 7.5 |
7.1 Frontier
- Front queues: one first-in first-out queue per priority level (Example: 5 levels). Priority comes from source (sitemap, seed, discovered link), estimated page importance and whether it is a recrawl.
- Back queues: each holds URLs of exactly one host. A host-to-queue table maps hosts to queues; a heap orders queues by the time their host may next be contacted.
- Getting a URL: a fetch slot pops the heap root (waiting if its time is in the future), takes the head URL of that queue and fetches it. When the fetch ends, the host's next allowed time is computed (7.3) and the queue goes back into the heap. When a back queue empties, it is refilled from the front queues with a bias toward high priority.
- Partitioning: host name hash picks the crawl node. A link to another host is sent to its owner node in batches.
7.2 Robots.txt under RFC 9309
| Situation | Behaviour in this design | RFC 9309 basis |
|---|---|---|
| Before the first fetch on a host | Fetch /robots.txt for that scheme, host and port | Section 2.3 |
| Group selection | Match our product token case-insensitively; fall back to the * group | Section 2.2.1 |
| Allow and disallow conflict | The rule with the longest matching path wins; on a tie, allow wins | Section 2.2.2 |
| Special characters | Support * (any sequence) and $ (end of path); # starts a comment | Section 2.2.3 |
| Redirects for robots.txt | Follow at least 5; more than 5 means treat as unavailable | Section 2.3.1.2 |
| 4xx (unavailable) | Crawl allowed | Section 2.3.1.3 |
| 5xx or network error (unreachable) | Treat as complete disallow; keep a cached copy if one exists and retry | Section 2.3.1.4 |
| Caching | Reuse for at most 24 hours unless the file is unreachable | Section 2.4 |
| File size | Parse at least the first 500 KiB | Section 2.5 |
Crawl-delay and Sitemap lines | Not defined by RFC 9309; Sitemap used as a hint, Crawl-delay respected as a minimum gap by policy | Section 2.2.4 allows other records |
RFC 9309 also notes that robots.txt is not a form of access authorisation. Respecting it is the crawler's policy and commitment, so log every decision.
7.3 Politeness and adaptive delay
| Rule | Example value |
|---|---|
| Connections per host | 1 |
| Gap after a fetch | max(1 s, 10 x the last response time), the factor that Mercator reported using |
| On 429 or 503 | Wait for Retry-After if present, else double the gap up to 1 hour |
| On repeated timeouts | Double the gap; pause the host for 1 day after 10 failures in a row |
| Per-IP limit | Hosts that share one IP address share one politeness budget |
| Operator override | Block or set a slower gap per host within 10 minutes (FR-09) |
With a 0.4 s response the next allowed time is 4 s later; with a 0.1 s 304 response it is the 1 s minimum.
7.4 Deduplication
- URL normalisation before the seen check: lowercase scheme and host, remove default ports and fragments, resolve dot segments, apply known safe rules per host (for example removing session-ID parameters that the host is known to ignore). Normalise only in ways that keep the meaning of the URL, and keep the original URL for fetching.
- URL-seen: the Bloom filter answers "definitely new" cheaply; a "maybe seen" answer and every new URL go into a per-node batch that is sorted and merged against the exact fingerprint files every few minutes. New URLs go to the frontier; the files get the new fingerprints.
- Exact content: SHA-256 of the normalised body; a hit links the URL to the existing content record and skips indexing.
- Near-duplicate content: 64-bit simhash of the page text. Split the 64 bits into four 16-bit blocks; if two fingerprints differ in at most 3 bits, at least one block is identical, so four tables, each sorted by one block, find every candidate. With pages each table returns about candidates per probe, about 61 000 for four, checked with XOR and bit counting in well under a millisecond.
7.5 Recrawl scheduling
- Estimate a change rate per URL from past fetches (changed or not, by content hash). Set the next interval so the expected number of missed changes stays small, bounded between 1 day and 90 days.
- Raise priority for URLs that sitemaps list with a recent
lastmod; treatchangefreqandpriorityas hints only. Sitemaps hold at most 50 000 URLs and 50 MB uncompressed per file. - Recrawls always send
If-None-MatchandIf-Modified-Sincewhen the previous response had anETagorLast-Modified. - URLs that return 404 or 410 twice are removed and a
crawl.removedevent is emitted; a newly disallowed URL is removed at once.
7.6 Traps, spam and budgets
| Problem | Signal | Defence |
|---|---|---|
| Infinite calendars and faceted URLs | Many URLs per host with the same path pattern | Cap URLs per path pattern; cap query parameters |
| Session IDs in URLs | Same content under many URLs | Learned parameter removal per host; content hash dedup |
| Very long or deep URLs | Length, depth from seed | Drop URLs over 2 048 characters or 16 levels deep |
| Link farms and spam domains | Many hosts under one domain with few links from outside | Budget per registered domain in proportion to links from other domains (IRLbot's approach) |
| Soft 404 pages | 200 status with "not found" template | Compare with a probe for a random URL on the host |
7.7 How real systems do it
How real systems do it (paraphrased, not copied). Heydon and Najork's Mercator papers (World Wide Web journal, 1999, and the Compaq SRC report "High-Performance Web Crawling", 2001) describe a frontier of prioritised front queues feeding back queues that each hold one host, with a heap ordered by when each host may be contacted again. The 2001 report says the team first allowed only one thread per host, still received complaints about unbroken request streams, and moved to waiting about ten times the last download time, with three times as many back queues as threads. It also reports that DNS lookups took 70% of each thread's elapsed time until a custom parallel resolver cut it to 14%, and that about 80% of links are relative, so host-based partitioning keeps most URLs on the node that found them. Lee, Leonard, Wang and Loguinov's IRLbot paper (WWW 2008, extended in ACM Transactions on the Web, 2009) reports 6.3 billion valid HTML pages in 41 days from a single server, at about 1 789 pages per second. Its DRUM structure batches URL-seen checks through disk buckets, and its STAR and BEAST methods give each domain a page budget based on links from other domains, so spam cannot snowball. Manku, Jain and Das Sarma (WWW 2007) report that 64-bit simhash fingerprints with up to 3 differing bits work for about 8 billion pages. Read the originals in 12.3; this box keeps only the general idea.
8. Scalability and performance
| Concern | Approach | Limit before redesign |
|---|---|---|
| Fetch rate | Asynchronous fetchers, many hosts in flight | Example: ~2 000 concurrent connections per node |
| Host diversity | Enough ready hosts in the heap | Crawls focused on few sites are politeness-bound, not hardware-bound |
| URL-seen | Batched merges per node | Merge time grows with the set; add nodes and re-split hosts |
| DNS | Local caching resolver, asynchronous lookups | Upstream resolver rate limits |
| Storage | Append-only WARC in object storage | Cost, not technology |
Per-fetch time budget (target: a fetch slot is busy for about 1 s on average):
| Step | Budget |
|---|---|
| Heap pop and robots check (memory) | 0.1 ms |
| DNS (cached in most cases) | 5 ms average |
| TCP and TLS connection (reused when possible) | 150 ms |
| Server response and transfer | 800 ms |
| Write WARC record and hand off to parser | 10 ms |
| Headroom | about 35 ms |
9. Reliability, security and operations
9.1 Failure modes
| Component | Failure | Detection | Blast radius | Mitigation | RTO |
|---|---|---|---|---|---|
| Crawl node | Crash | Heartbeat | Hosts owned by that node pause | Frontier and seen files on durable disk, checkpoint every 10 min; hosts reassigned by consistent hashing | Example: 10 min |
| DNS resolver | Upstream outage | Resolution error rate | All new hosts | Serve from cache with extended TTL; pause hosts that cannot resolve | Example: 30 min |
| Robots cache | robots.txt unreachable (5xx) | Status counters | One host | Full disallow per RFC 9309; retry with backoff; use cached copy if present | Host-dependent |
| Object storage | Write failures | Error rate | Fetch results not stored | Local spill buffer for 1 h; pause fetchers after that | Example: 1 h |
| Trap or spam domain | Explosive URL growth on one domain | URLs per domain per hour | Crawl budget wasted | Domain budget, path-pattern caps, operator block | Example: 1 h |
| Site owner complaint | Overload or unwanted crawl | Complaint channel | Reputation, IP blocks | Block or slow the host in 10 min; review the delay policy | Example: 1 business day |
| [Component] | [Failure] | [Signal] | [Scope] | [Mitigation] | [Time] |
9.2 Security, privacy and legal questions
| Threat or duty | Control |
|---|---|
| Server-side request forgery through crafted links (internal IP ranges, metadata endpoints) | Resolve then block private, loopback and link-local addresses; fetch from an isolated network |
| Malicious content (parser exploits, decompression bombs) | Size limits before and after decompression, sandboxed parser, timeouts |
| Personal data in crawled pages | Purpose limitation, retention limits and deletion on request, reviewed with privacy counsel |
| Copyright and terms of service | Legal review of the crawl purpose and reuse of stored content before launch |
| Impersonation of our crawler by others | Publish verification method (reverse DNS or published IP ranges) on the crawler page |
| [Threat] | [Control] |
Questions to answer with legal and privacy reviewers (not legal advice): Which jurisdictions' data protection laws apply to the pages we store? Does the purpose (search, archive, AI training) require an opt-out mechanism beyond robots.txt? How long may raw copies be kept?
9.3 Observability
| Signal | Metric or log | Alert threshold |
|---|---|---|
| Throughput | Successful fetches per hour | Below 80% of plan for 1 h |
| Politeness | Max concurrent connections per host | Any value above 1 |
| Error mix | Share of 4xx, 5xx, timeouts | 5xx share doubling vs. 7-day baseline |
| Duplicate waste | Exact plus near-duplicate share | Above 5% |
| Frontier health | Ready hosts in the heap | Below 3 x fetch slots |
| Complaints | Tickets per week | Any unanswered after 1 business day |
9.4 Cost estimate
| Item | Unit cost | Quantity | Monthly cost |
|---|---|---|---|
| Crawl nodes (fetch, frontier, seen set) | [$/node-month] | Example: 8 | [$] |
| Bandwidth | [$/TB] | Example: about 120 TB transferred per month (1 billion x 100 KB x 1.2) | [$] |
| Object storage | [$/TB-month] | Example: 225 TB at steady state | [$] |
| Key-value store for URL state | [$/month] | [Nodes] | [$] |
| Total | [$] |
10. Trade-offs and alternatives
| Decision | Options | Chosen | Why | What we give up |
|---|---|---|---|---|
| Partitioning | By URL hash; by host hash | By host hash | Politeness without coordination; local links stay local | Uneven load when one host is huge |
| URL-seen | Bloom only; exact in memory; exact on disk with batching | Bloom plus batched exact disk set | No silent loss, bounded memory | Minutes of delay before new URLs enter the frontier |
| Politeness | Fixed delay; adaptive | Adaptive with backoff | Fair to slow servers, fast on fast ones | Harder to predict per-host throughput |
| Rendering | Render all pages; HTML only | HTML only now, render queue later | 10x to 100x cheaper per page | Misses content built by JavaScript |
| Storage | Extracted text only; raw WARC | Raw WARC plus text | Reprocessing without refetching | 4x more storage than text alone |
| What changes at 10x / 0.1x | 10x (10 billion pages a month, about 3 860 pages/s average): about 7 700 hosts in flight at peak, an 800 GB exact seen set and about 2.25 PB stored for 6 months with erasure coding, so more nodes, a dedicated URL-seen service and tiered storage; politeness still caps each host. 0.1x (100 million a month, about 39 pages/s): one or two nodes with an in-memory seen set (8 GB at 1 billion URLs) are enough | Review at each doubling of the target | Host diversity and the seen set grow first | [Note] |
| [Decision] | [Options] | [Chosen] | [Why] | [Cost] |
11. Delivery plan and risks
11.1 Milestones
| Milestone | Scope | Exit criterion | Target date |
|---|---|---|---|
| M1 | Single-node crawler with frontier, robots, WARC output; crawler page published | RFC 9309 test suite passes; 1 million pages without a complaint | YYYY-MM-DD |
| M2 | Host-partitioned cluster, batched seen set | 100 million pages in 30 days; node crash drill passes | YYYY-MM-DD |
| M3 | Content dedup and recrawl scheduler | Duplicate waste under 5%; freshness target met for top 10 million | YYYY-MM-DD |
| M4 | Full rate, budgets and trap defences | 1 billion pages in 30 days | YYYY-MM-DD |
11.2 Rollout and rollback
[Example: Raise the global fetch rate in steps of 2x per week while watching complaints and error mix. Any step can be undone by lowering the global rate limit; per-host blocks apply at once.]
11.3 Risks
| ID | Risk | Likelihood | Impact | Mitigation | Owner |
|---|---|---|---|---|---|
| R-01 | Example: IP ranges blocked by large hosting providers after complaints | Medium | High | Conservative delays, fast response to complaints, published verification | [@name] |
| R-02 | Example: Legal challenge to storing or reusing content | Low | High | Legal review before M1; retention and deletion process | [@name] |
| R-03 | Example: Trap consumes a large share of the crawl | Medium | Medium | Domain budgets and caps from M2 | [@name] |
| [R-nn] | [Risk] | [L / M / H] | [L / M / H] | [Mitigation] | [@name] |
12. Appendices
12.1 Open questions
| ID | Question | Owner | Due |
|---|---|---|---|
| OQ-01 | Example: Do we honour AI-specific opt-out signals beyond robots.txt, and which ones? | [@name] | YYYY-MM-DD |
| OQ-02 | Example: Which share of pages needs JavaScript rendering to be useful? | [@name] | YYYY-MM-DD |
| [OQ-nn] | [Question] | [@name] | YYYY-MM-DD |
12.2 Glossary
| Term | Meaning |
|---|---|
| Frontier | The set of URLs waiting to be fetched, with their order |
| Politeness | Limits on how often and how concurrently we contact one host |
| Back queue | A queue that holds URLs of one host only |
| URL-seen test | Check whether a URL has been discovered before |
| Simhash | Fingerprint where similar documents get fingerprints that differ in few bits |
| WARC | Web ARChive file format for raw HTTP responses (WARC 1.1, ISO 28500:2017) |
| Crawler trap | URL space that generates unlimited distinct URLs |
| [Term] | [Meaning] |
12.3 References
| Reference | Use |
|---|---|
| RFC 9309, Robots Exclusion Protocol, Koster, Illyes, Zeller and Sassman, September 2022 (Standards Track) | 7.2 |
| RFC 9110 HTTP Semantics | Conditional requests, Retry-After, 4.2 |
| Sitemaps XML format, sitemaps.org | Limits and hints in 7.5 |
| Heydon and Najork, "Mercator: A scalable, extensible Web crawler", World Wide Web 2(4):219-229, 1999, doi:10.1023/A:1019213109274 | 7.1, 7.7 |
| Najork and Heydon, "High-Performance Web Crawling", Compaq SRC Research Report 173, 26 September 2001 | Frontier details, delay factor, DNS, 7.7 |
| Lee, Leonard, Wang and Loguinov, "IRLbot: Scaling to 6 billion pages and beyond", WWW 2008, doi:10.1145/1367497.1367556; extended in ACM TWEB 3(3), 2009, doi:10.1145/1541822.1541823 | DRUM, budgets, 7.6, 7.7 |
| Manku, Jain and Das Sarma, "Detecting near-duplicates for web crawling", WWW 2007, doi:10.1145/1242572.1242592 | Simhash parameters in 7.4 |
| Bloom, "Space/time trade-offs in hash coding with allowable errors", CACM 13(7):422-426, 1970, doi:10.1145/362686.362692 | Bloom filter in 3.5 |
| IIPC, The WARC Format 1.1; ISO 28500:2017, ISO catalogue | Storage format |
Full-Text Search Engine System Design (system-design/design-full-text-search) | Indexer that consumes the output |
Job Scheduler System Design (system-design/design-job-scheduler) | Recrawl scheduling patterns |
Distributed Key-Value Store System Design (system-design/design-distributed-kv-store) | URL state store |
Back-of-Envelope Estimation and Capacity Model (system-design/back-of-envelope-estimation) | Method for section 3 |
Standard and licence note
Based on the Robots Exclusion Protocol (RFC 9309, September 2022), HTTP Semantics (RFC 9110), the sitemaps.org protocol and the published crawler research cited in 12.3. RFC section numbers are cited and the rules are paraphrased; no specification or paper text was copied, and all capacity numbers are illustrative examples. ISO 28500:2017 (WARC) is a paid standard and is named only; official text: ISO, purchase or licensed access at https://www.iso.org/standard/68004.html (the IIPC publishes WARC 1.1 openly). Check the current versions before relying on them. This is not legal advice; check the current official text and a qualified professional. Original text written for this collection. Template licence: MIT.
Related templates
Ad Click Aggregation System Design Template (Reference Design)
45-minute path, click dedupe, event-time windows, exactly-once counting, exact and Count-Min top-K, batch reconciliation for billing, failure modes, 10x row
System DesignZanzibar-Style Authorization Service System Design Template
Relationship-based authorization service: relation tuples, namespace config, Check/Expand/List/Watch APIs, consistency tokens, caching, sharding, failure modes
System DesignCDC Pipeline System Design Template
CDC pipeline: log-based capture, watermark chunked snapshot, per-key order, schema evolution, outbox, search, cache and warehouse sinks, replay