CMD Guide
HomeSystem DesignSystem Design Problems

Web Crawler Politeness & Priority — Front Queues, Back Queues, Router, Traced

"Use a queue" is not an answer — a crawler needs two

Model the web as a directed graph: pages are nodes, hyperlinks are edges. Crawling is graph traversal, so the instinct is to reach for BFS with a FIFO queue. DFS is a poor fit immediately — the depth can be effectively unbounded, so a depth-first crawler wanders into one site forever. So BFS with a FIFO queue it is.

Except a single FIFO queue has two independent defects, and this is the part usually skipped:

These two problems pull in opposite directions. Politeness wants to slow down and spread across hosts; priority wants to rush the valuable URLs, which are often concentrated on a few big hosts. You cannot satisfy both in one queue with one ordering rule — which is exactly why the URL frontier is built as two stacked queue layers with a translation step between them.

The URL frontier as two queue layers. On the left, front queues handle prioritization: URLs enter a prioritizer that scores them by PageRank and traffic, feeding high, medium and low priority queues f1 to f3, from which a selector picks with a bias toward high priority. On the right, back queues handle politeness: a queue router uses a host-to-queue mapping table to place each URL into the one back queue for its host, b1 for wikipedia.org, b2 for amazon.com, b3 for apple.com, and a second selector binds each back queue to exactly one worker thread that sleeps a delay between fetches.
The URL frontier as two queue layers. On the left, front queues handle prioritization: URLs enter a prioritizer that scores them by PageRank and traffic, feeding high, medium and low priority queues f1 to f3, from which a selector picks with a bias toward high priority. On the right, back queues handle politeness: a queue router uses a host-to-queue mapping table to place each URL into the one back queue for its host, b1 for wikipedia.org, b2 for amazon.com, b3 for apple.com, and a second selector binds each back queue to exactly one worker thread that sleeps a delay between fetches.

Layer 1: front queues enforce priority

The prioritizer takes URLs and computes a priority from measures of usefulness — PageRank, website traffic, update frequency. Each front queue f1…fn holds one priority band.

The front queue selector then chooses which band to draw from: it picks randomly, with a bias toward higher-priority queues. That word "random" is doing real work. A strict "always drain the highest queue first" rule would starve the low-priority bands forever, and a crawler that never revisits low-value pages eventually has a stale index of exactly the long tail that makes a search engine comprehensive. Biased-random sampling gives the high band most of the throughput while guaranteeing the low band a nonzero share.

Layer 2: back queues enforce politeness

The rule to implement is: download one page at a time from any given host, with a delay between consecutive fetches. Three components achieve it:

Trace the invariant: host → exactly one queue → exactly one worker → serialized fetches with a delay. Because the mapping is one-to-one at every hop, two workers can never be in flight to the same host simultaneously — politeness is a structural property of the data layout, not a rate check someone has to remember to call. That is what makes this design worth learning: the correctness argument is "there is only one worker that can touch this host," which is far stronger than "we check a counter first."

So the full frontier is: front queues manage prioritization, back queues manage politeness, and the router between them is where a priority-ordered stream is reshuffled into per-host streams.

Where the frontier actually lives

In a real search-engine crawl the frontier holds hundreds of millions of URLs. Neither extreme works: memory is not durable and not large enough; pure disk is too slow and becomes the crawl's bottleneck.

The answer is hybrid — the majority of URLs live on disk, with in-memory buffers for enqueue and dequeue that are periodically flushed. This is the same pattern as the key-value store's commit log plus memtable, for the same reason: buffer the random small operations in RAM, pay the disk in large sequential batches.

Freshness: the frontier's third job

Pages are added, edited and deleted continuously, so crawled data goes stale and pages must be recrawled. Recrawling everything is prohibitively expensive, so two strategies apply: recrawl based on a page's update history (a page that has never changed in a year does not need weekly visits), and recrawl important pages first and more often. Note that this reuses the priority machinery rather than adding new machinery — freshness is just priority with a time term.

The HTML downloader, and being a good citizen

The HTML downloader fetches pages over HTTP, and before fetching anything from a site it must consult robots.txt — the Robots Exclusion Protocol, by which a site declares what crawlers may download. A real fragment from Amazon's file:

User-agent: Googlebot
Disallow: /creatorhub/*
Disallow: /rss/people/*/reviews
Disallow: /gp/pdp/rss/*/reviews
Disallow: /gp/cdp/member-reviews/
Disallow: /gp/aw/cr/

Fetching robots.txt before every request would double your traffic and be its own kind of rudeness, so cache the file and refresh it periodically. Note the interaction with politeness: the robots.txt fetch itself must go through the same per-host serialization, or your politeness accounting is off by a factor of two.

Performance optimizations

Robustness and extensibility

Consistent hashing distributes load across downloaders so servers can be added or removed without reshuffling the whole space. Saving crawl state and data lets an interrupted crawl resume from checkpoint rather than restart. Exception handling and data validation keep one malformed page from killing a worker. For extensibility, the design admits new modules at the same seam — a PNG downloader for images, or a web-monitor module watching for copyright violations — because the frontier only produces URLs and does not care what consumes them.

Problematic content to detect and skip

Which politeness mechanism, when

MechanismGuaranteeWhy choose itWhen it is wrong
Single FIFO queueNoneTrivial to build; fine for a one-site scrapeAny multi-host crawl — floods one host and ignores value
Global rate limiterTotal requests/secOne knob protects you from overloadDoes not protect any individual host — all your budget can still land on one server
Per-host token bucketPer-host rateNo queue-per-host bookkeeping; flexible burstsConcurrent workers can still overlap on one host unless you also lock; correctness depends on a check
Host→queue→worker bindingOne in-flight request per host, structurallyPoliteness cannot be violated by a forgotten checkWorker count is bounded by host count — crawling few hosts leaves workers idle

That last row is the honest cost of the frontier design: throughput is capped by host diversity. If you crawl 10 hosts you cannot usefully run 1,000 workers, no matter how much hardware you own. A crawler for a narrow domain may legitimately prefer per-host token buckets and accept the weaker guarantee.

Pitfalls

Cost model — what dominates the bill

A crawler's cost is unusual in that the expensive resource is not compute or storage but politeness-bound wall-clock time, and the bill follows from how long the fleet must stay up.

Rough BOTE at the classic scale: 1 billion pages per month is about 1,000,000,000 / (30 × 24 × 3600) ≈ 400 pages/second average, so provision for a peak around 800/s. At ~500 KB per page that is ~500 TB/month of fetched content, and ingress is typically free while storage accumulates — at roughly $0.02/GB-month for warm object storage, 500 TB is about $10,000/month, growing every month you keep crawling. Five years of retention is ~30 PB, which is the line item that actually decides the architecture.

Now the politeness interaction: if courtesy allows one fetch per host per second, then sustaining 400 pages/second requires at least 400 distinct hosts in flight, and therefore at least that many back queues and workers. The fleet is sized by host concurrency, not by CPU — each worker is mostly idle, waiting on network and on its own politeness delay. This is why crawler workers are cheap, numerous, and I/O bound.

Dominant line item: long-term storage of crawled content (and its indexes), which compounds monthly. Second: the many small, mostly-idle worker instances kept alive to hold host concurrency.

Levers: deduplicate before storing — with roughly 30% of the web duplicated, content-hash dedup is a direct ~30% cut to the dominant line; tier aged crawls to cold storage; and let update-history-driven recrawl scheduling stop re-fetching (and re-storing) pages that never change, which cuts both bandwidth and storage growth at once.

Operability: the fingerprints of an impolite or stalled crawler

The failures here are distinctive because most of them show up as someone else's complaint. HTTP 429s and 403s clustered on a single host mean the host→queue mapping has broken for that host — usually the router keyed on something other than the bare hostname, so two queues now serve one site. Throughput far below target while every worker looks busy is the politeness ceiling being hit: you have fewer distinct hosts in flight than your target pages/second requires, and adding machines will change nothing. One back queue growing without bound means its worker is blocked — a hung connection with too generous a timeout, and that entire host's crawl has silently stopped while overall metrics look healthy.

Oldest-URL age climbing in the low-priority front queues is the starvation fingerprint, and it is invisible in queue-depth dashboards — depth can be stable while the bottom never moves, so track age, not just size. Fetch volume rising while unique-content volume stays flat means you are re-downloading duplicates or walking a spider trap; the giveaway for a trap specifically is growing URL length or path depth from one host. And a sudden drop in pages/second across all hosts with normal network health usually points at DNS — a cold or failing resolver cache turns every fetch into a resolution.

Signals worth having: per-host in-flight count (should never exceed one), 4xx/5xx rate grouped by host, per-back-queue depth and worker-blocked duration, oldest-URL age per priority band, unique-content ratio, DNS cache hit rate, and URL-depth distribution per host.


Re-authored for this guide from the Alex Xu Vol. 1 web-crawler chapter (URL frontier after the Mercator line of work); frontier diagram hand-authored as SVG. Deep dive complementing the existing "Designing a Web Crawler", "Designing a Web Crawler — Frontier, Dedup & Politeness, Traced" and "Web Crawler Freshness" pages — this one covers the two-layer queue mechanism itself.

🤖 Don't fully get this? Learn it with Claude

Stuck on Web Crawler Politeness & Priority — Front Queues, Back Queues, Router, Traced? Open Claude, copy a block below, and it'll teach you this exact concept — visually and interactively.

🎨 Explain it visually

Build the mental picture, not memorization.

I just read a lesson on **Web Crawler Politeness & Priority — Front Queues, Back Queues, Router, Traced** (System Design) and want to truly understand it. Explain Web Crawler Politeness & Priority — Front Queues, Back Queues, Router, Traced from first principles using ONE vivid real-world analogy and a visual mental model — draw it as ASCII art or a clear step-by-step diagram — with a concrete example using real numbers. Then ask me one question to check I got the mental picture, and wait for my reply. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🤔 Walk me through it (interactive)

Socratic — adapts to where you're stuck.

Teach me **Web Crawler Politeness & Priority — Front Queues, Back Queues, Router, Traced** interactively. Ask me ONE guiding question at a time, wait for my answer, and adapt to my confusion — build the idea with me step by step instead of explaining it all at once. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧪 Quiz me & fix my gaps

Active recall exposes what you missed.

Quiz me on **Web Crawler Politeness & Priority — Front Queues, Back Queues, Router, Traced** with 5 questions, easy to tricky, ONE at a time. Tell me if each answer is right; at the end, explain clearly what I got wrong and why. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧠 Make it stick

Intuition + hook + flashcards for long-term memory.

Help me remember **Web Crawler Politeness & Priority — Front Queues, Back Queues, Router, Traced** for the long term: give the one-sentence intuition, a memorable hook/mnemonic, a tiny worked example, and 3 active-recall flashcards (Q -> A). If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.

📝 My notes