Knowledge Guide
HomeSystem DesignSystem Design Problems

hard Web Crawler Freshness: re-crawl scheduling & change detection

Freshness is a scheduling problem, not a crawling problem

A crawler that re-fetches every page on the same clock is wrong twice over: too slow for the pages that change by the hour (the index goes stale, users see dead deals and yesterday's headlines) and too fast for the pages that never change (wasted bandwidth, wasted CPU, and a politeness budget burned on fetches that can only ever return the same bytes). The fix is to treat each page's change rate as something you estimate from its own history, schedule its next re-crawl proportional to that rate, and — before paying for a full re-fetch — ask the cheapest possible question first: “did anything actually change?”

Loop diagram: change history feeds a Poisson change-rate estimate, which sets the next re-crawl interval; the scheduler enqueues a conditional GET; a 304 extends the interval, a 200-with-change shrinks it, and either outcome updates the history and re-enters the loop
Loop diagram: change history feeds a Poisson change-rate estimate, which sets the next re-crawl interval; the scheduler enqueues a conditional GET; a 304 extends the interval, a 200-with-change shrinks it, and either outcome updates the history and re-enters the loop

Estimating change rate: a Poisson process per URL

Model each page's edits as a Poisson process with rate λ (expected changes per day). If Δt has passed since the last crawl, the probability the page has changed by now is P(changed) = 1 − e^(−λ·Δt). You never know λ up front, so you estimate it online with an exponential backoff-and-advance rule (the approach behind Cho & Garcia-Molina's freshness studies and production crawlers like Googlebot):

This converges without ever needing to know the “true” λ: a fast-changing page keeps getting its interval squeezed until it's checked about as often as it actually changes; a static page's interval keeps stretching until it's checked rarely. The schedule chases the evidence.

Traced: four pages, four schedules

PageInterval beforeCheck resultNew interval
news.example.com/home1 hourconditional GET → 200, changedstays 1 hour (already at the floor)
blog.example.com/post-427 daysconditional GET → 304 (3rd unchanged check in a row)extends to 14 days
docs.example.com/faq30 daysconditional GET → 304extends to 60 days
shop.example.com/deal-1930 daysconditional GET can't be trusted (host omits ETag) → full fetch, hash changed (price update)shrinks to 15 days; if the very next check also shows a real change, shrinks again to 7 days

Notice the news page never leaves the floor, the docs page keeps drifting toward the cap, and the deal page oscillates downward while it's actively being edited — three very different schedules, each earned by that page's own history, with no global constant anywhere.

Cheap change detection before paying for a full re-fetch

Priority: freshness value × change probability, bounded by politeness and budget

Two pages with the same λ don't deserve the same crawl slot: a heavily-linked homepage costs more to leave stale than an obscure archive page with identical change frequency. So the frontier doesn't just sort by “interval elapsed” — it ranks by freshness value × P(changed since last crawl), and only the top of that ranking gets a fetch slot once the crawl budget (finite fetches/day) and per-host politeness (robots.txt crawl-delay, rate limits) are accounted for. A page whose “ideal” interval says “check me now” still waits if checking it would blow the host's rate limit or the day's total fetch budget.

Pitfalls

Selection & trade-offs

Fixed interval vs adaptive re-crawl. A single global interval is simple — one config value, nothing to estimate or store per URL — and is fine for a small, homogeneous corpus where every page really does change at about the same rate. It stops working the moment the corpus is heterogeneous (which real crawls always are): adaptive scheduling costs extra state per URL (λ estimate, last ETag/hash, current interval) but spends a fixed crawl budget where staleness actually costs something, instead of spreading it evenly across pages that don't need it.

Conditional GET vs full-fetch+hash vs push. Conditional GET is the cheapest per-check cost when the server implements it honestly — use it as the default. Full fetch + content hash is the fallback for hosts that don't support (or lie about) conditionals; it costs a full transfer but still avoids treating boilerplate churn as a real change. Push (sitemap lastmod, RSS, WebSub) is cheapest of all when a site offers it — you're told, you don't have to ask — but coverage is partial and some feeds are stale, so it augments the polling floor rather than replacing it. A production crawler runs all three: push where available, conditional GET as the default poll, full-fetch+hash as the fallback for uncooperative hosts.

Takeaways


Synthesized for this guide from the freshness/re-crawl literature — Cho & Garcia-Molina, “Estimating Frequency of Change” (the online λ-estimation and backoff-and-advance scheduling model), HTTP conditional-request semantics (RFC 7232 ETag/If-None-Match), and production crawler practice (Googlebot's crawl-rate/crawl-demand model, sitemap lastmod). Diagram hand-authored as SVG. Complements the “Designing a Web Crawler — Frontier, Dedup & Politeness, Traced” page, which flags freshness as a hard part without covering the scheduling mechanism — this page is that mechanism.

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

Stuck on Web Crawler Freshness: re-crawl scheduling & change detection? 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 Freshness: re-crawl scheduling & change detection** (System Design) and want to truly understand it. Explain Web Crawler Freshness: re-crawl scheduling & change detection 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 Freshness: re-crawl scheduling & change detection** 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 Freshness: re-crawl scheduling & change detection** 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 Freshness: re-crawl scheduling & change detection** 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