Adaptive back-off for rate-limited hosts via the queue stream#1984
Open
dpol1 wants to merge 4 commits into
Open
Adaptive back-off for rate-limited hosts via the queue stream#1984dpol1 wants to merge 4 commits into
dpol1 wants to merge 4 commits into
Conversation
The bolt is about to regulate queues beyond blocking them: adaptive back-off for headerless rate limits first, robots crawl-delay pacing next (#867). Give it a name that covers the role. No behaviour change.
Headerless 429/503 responses (and optionally fetch exceptions) now block the host's frontier queue for a growing duration: base 60s doubled per incident up to 24h, forgiven after 1800s of quiet counted from the end of the block. Explicit Retry-After keeps being honoured and feeds the same per-host state: a block is only ever extended, suppressed signals re-assert it so a lost fire-and-forget call converges, and a jitter fraction avoids synchronized expiries at the cap. The trigger status codes are configurable via urlfrontier.backoff.status.codes. Closes #1106
rzo1
reviewed
Jul 9, 2026
| public static final String URLFRONTIER_MAX_RETRY_AFTER_KEY = "urlfrontier.max.retry.after"; | ||
|
|
||
| public static final int URLFRONTIER_MAX_RETRY_AFTER_DEFAULT = 86400; | ||
|
|
Contributor
There was a problem hiding this comment.
All of these need to land int the docs :)
Contributor
|
Thanks for the PR. Aside from the docs thing already mentioned, I have two things while skimming through the PR:
|
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.
Stage 2 of #867, closes #1106. #1973 handled the explicit case, a 429/503 carrying a Retry-After header. This covers the one we probably see most in the wild: servers that rate-limit without sending any header. Today we just fetch the host again and collect another 429.
A headerless 429/503 arriving on the queue stream now blocks the host's queue in URLFrontier for a growing duration: 60s the first time, doubled per new incident, capped at a day, forgiven after 30 quiet minutes. Same curve as Nutch since 1.19 (NUTCH-2946), but the state lives with the queue owner: a blocked host never reaches the fetcher at all, so the stale fetching Nutch had to patch around (NUTCH-3114) can't happen here.
Everything from the #867 discussion is in:
urlfrontier.backoff.jitter, default 0.1) so hosts blocked on the same schedule don't all come back at once. Thanks @sebastian-nagel, the daily error spike was exactly the failure mode I had dismissed.urlfrontier.backoff.status.codes, default 429 and 503; 403 stays out since it so often just means "forbidden"). They now gate the Retry-After path of Block the URLFrontier queue on Retry-After via the queue stream #1973 as well, with one behaviour change: a malformed or zero Retry-After escalates as a headerless incident instead of being ignored.urlfrontier.backoff.on.exceptions, off by default: a timeout is a noisier signal than a clean 429, I'd flip the default once this has seen some real crawls.One rename against what I posted on #867: the config prefix is
urlfrontier.backoff.*, notfetcher.backoff.*. The fetcher plays no part in this, and it sits next to Phase 1'surlfrontier.max.retry.after.How it's built:
HostBackoff, same split asRetryAfterParserin Block the URLFrontier queue on Retry-After via the queue stream #1973; the bolt keeps the RPC and nothing else.HostBlockBoltbecomesQueueRegulatorBoltin a separate mechanical commit: with Propagate robots.txt crawl-delay to URLFrontier via setDelay #1979 lined up next, blocking is one of the actions this bolt takes on a queue, not the only one.metadata.persistcaveat as Block the URLFrontier queue on Retry-After via the queue stream #1973 (fetch.statusCode, plusfetch.exceptionwhen the flag is on); the bolt warns at startup when they wouldn't survive the filter.Tests: escalation math against a controllable ticker and a seeded RNG for the jitter, regressions for the burst and only-extend guards, and two Testcontainers runs against a real frontier (explicit Retry-After and headerless 429).
For all changes
#XXXXwhereXXXXis the issue number you are tryinmvn git-code-format:format-code -Dgcf.globPattern="**/*" -Dskip.format.code=false?For code changes
mvn clean verify?