crawler: enforce Crawl-delay, back off on 429/503, surface politeness counters#30
Open
andreimarinescu wants to merge 3 commits into
Open
crawler: enforce Crawl-delay, back off on 429/503, surface politeness counters#30andreimarinescu wants to merge 3 commits into
andreimarinescu wants to merge 3 commits into
Conversation
… counters Two field incidents drove this: a temporary IP ban from news.ycombinator.com (robots asks Crawl-delay: 30; we fetched at 1/s regardless) and a 424-response 429 storm from huggingface.co (an 823-item feed crawled with no backoff). - Crawl-delay was parsed but effectively unenforced: the delay was slept per-worker AFTER the host-gate slot was reserved, so concurrent workers still received slots at per_host_delay_ms spacing and the per-host request rate ignored robots entirely. robots.txt is now consulted before the gate and the delay widens the slot reservation itself (hostGate.WaitFor). Fractional Crawl-delay values parse, and a new crawler.max_crawl_delay_ms (default 2 min) clamps hostile/misconfigured values. - 429 (and 503 with Retry-After) no longer error the frontier entry: the host's next slot is deferred by Retry-After (or an exponential fallback, capped at 5 min) and the URL is requeued up to 3 times before failing. Rate-limited responses stay out of hostStats so the success-ratio blacklist can't be poisoned by a healthy-but-busy server. - Claim-time allowlist drops were invisible (imports report success while every item is discarded): now counted, logged once per host, and exposed together with deferral counts via /stats (crawl_dropped_disallowed, crawl_rate_limited_deferrals) and /metrics. - /admin/crawl-enqueue accepts an optional "lane" field (empty keeps the historical discovered default) so bulk feeds don't compete with discovery. - Tests: gate WaitFor/Defer timing under concurrency, fractional/negative Crawl-delay parsing, first fetchRSS coverage (RSS2/Atom/failure paths), 429 requeue-recover and give-up flows, enqueue lane routing.
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
- robots Crawl-delay pacing through the gate under concurrent workers, fractional value in anger, and the max_crawl_delay_ms clamp - exponential fallback when a 429 carries no Retry-After; 503 semantics (throttle only with the header) - claim-time drop counter via frontier entries that predate an allowlist change - /stats and /metrics politeness counters (present with the hook, absent without); gate.Defer no-op guard
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two field incidents motivated this: a temporary IP-level ban from news.ycombinator.com (robots.txt asks
Crawl-delay: 30; the crawler fetched at ~1/s regardless) and a 429 storm from huggingface.co (an 823-item feed crawled with no backoff — 424 throttle responses in one pass, roughly half the feed lost to terminal errored entries).Crawl-delay was parsed but effectively unenforced. The delay was slept per-worker after the host-gate slot was reserved, so concurrent workers still received slots at
per_host_delay_msspacing and the effective per-host rate ignored robots.txt entirely. robots.txt is now consulted before the gate and the delay widens the slot reservation itself (hostGate.WaitFor). Fractional values (Crawl-delay: 0.5) parse, and a newcrawler.max_crawl_delay_ms(default 2 min) clamps hostile robots.txt values.429 (and 503 with Retry-After) no longer error the frontier entry. Errored entries are terminal, so every throttled URL was lost until a manual recrawl. Now the host's next slot is deferred by Retry-After (or an exponential fallback, capped at 5 min) and the URL is requeued up to 3 times before failing. Rate-limited responses also stay out of hostStats so the success-ratio blacklist can't be poisoned by a healthy-but-busy server.
Politeness observability. Claim-time allowlist drops were silent — imports report success while every item is discarded. They're now counted, logged once per host, and exposed together with deferral counts via
/stats(crawl_dropped_disallowed,crawl_rate_limited_deferrals) and/metrics./admin/crawl-enqueueaccepts an optionallane(empty keeps the historical discovered default) so bulk feeds don't have to compete with link discovery.Replaying the huggingface feed on a fresh index: 429s received 424 → 85 (each one deferring the host), URLs lost ~half → 1 of 823, no blacklisting. Tests cover the gate timing under concurrency (the HN failure mode), robots parsing, the previously untested fetchRSS paths, both 429 flows (recover and give-up), and lane routing; the suite is green with
-race.