network: per-host pressure in the navigation rate limiter - #3266
krichprollsch wants to merge 4 commits into
Conversation
ad4b222 to
0e5c53b
Compare
0e5c53b to
6c9d8a2
Compare
|
I think this discourages using rate-limiting. Running main: 10103ms I don't understand why a user would opt into this. And not just because it's slower. Imagine a user wants to crawl a site every 1 second (maybe it's their own network and they're doing testing, maybe they know the site advertises with a 1 - don't use the rate limiter |
| const start = @max(now, h.tat -| self.tau); | ||
| h.tat = @max(h.tat, start) + interval; | ||
| h.pressure = @min(h.pressure + 1, self.pressure_max); | ||
| log.debug(.rate_limit, "navigation reserved", .{ |
There was a problem hiding this comment.
Maybe we should take out the log.debug(.http, "navigation delayed", ....) in HttpClient then.
|
My idea was to find a way to set the rate limiter by default. I get your point, it's not very compatible w/ the 100ms delay you configure. If we decide to enable the rate limter by default, maybe we could disable the dynamic adjustment if you set explicitly the delay, so setting it to 100ms would keep the same as now, setting it to 0 would disable rate limiter. |
|
I agree with this. The issue is that it tramples the configured setting. I also agree that I'm not sure you need this AND #3332. If you can get the right feedback, then I think this only gets in the way. |
|
Actually, I asked Claude to math this out for me. On my httpbin example in #3332 it says I'd need a pressure of 180 before it serves any purpose. It's the It's one or the other. A fast site being crawled ignores the ttfb. A slow site ignores the pressure. It suggested making them additive (using a different formula, mind you). |
c845fda to
90b2383
Compare
|
I changed the PR:
|
| // }); | ||
| if (wait > 0) { | ||
| const level: lp.log.Level = if (self.mode == .adaptive) .warn else .debug; | ||
| lp.log.log(.rate_limit, level, "navigation delayed", .{ |
There was a problem hiding this comment.
This is my only complaint. RUNS=100 node puppeteer/cdp.js prints this message 97 times. Obviously, your call, but you do have 2 other options
- use the new
logConfigTipsinmain.zig - look at log.warnDisabledWorker for a first-time-only
(you could use both approaches, a tip + first time it actually happens).
There was a problem hiding this comment.
I added a bool into Host to notify once per host the message when delayed, I have already hold the lock.
I prefer logging only when the rate limit applies, I expect it to be invisible for most usages.
5f4bf4b to
8027f4e
Compare
The interval between two top-level navigations to the same host now grows with the number of navigations sent to it: one extra base interval every burst*10 navigations, capped at 60x. Pressure cools down by one unit for every 10 base intervals the host is left alone. --http-nav-delay is now a minimum, not a fixed spacing.
0462c1d to
79b4604
Compare
The goal of the PR is to space out top-level navigations to the same host. The delay starts
near zero, grows while you keep hitting the host, and comes back down over
time. Only top-level navigations are affected. Iframes, scripts, images, XHR
and fetch are never delayed, and
localhost,127.0.0.1and[::1]areexempt.
The bucket is keyed by hostname, ignoring port and scheme.
The rule
Each host carries a
pressurecounter. Pressure sets the spacing:Each navigation books the next free slot:
tatis the time the host's booked slots run out.tauis the burstallowance,
(--http-nav-burst - 1) * 20ms, which is 180ms by default. It letsan idle host run ahead before anything waits.
What you actually see
The first 10 navigations to an idle host go out back to back. After that the
spacing appears and grows:
It stops at 100ms and stays there. That is not the cap, it is where the two
sides balance: one navigation adds a unit, 100ms of elapsed time removes one.
So a sustained crawl of one host runs at 10 navigations per second, the
same rate
--http-nav-delay 100gives you.The burst allowance is not a one time budget. A host left alone long enough
gets its 10 free navigations again.
What raises the delay
A
Retry-Afterheader on a 429 or 503 pushes the next slot directly, honouredup to 60s. Only the delay-seconds form is read; an HTTP-date is ignored.
Pressure stops at 590, so the spacing never exceeds 1200ms. You reach that
with twelve consecutive 429s, or by submitting hundreds of navigations at once.
What lowers it
One unit of pressure is forgiven every 100ms. This counts wall clock, not idle
time: a host under constant load drains too, which is why the spacing settles
instead of climbing to the cap.
Draining is computed lazily, when the host is next touched, and the clock moves
in whole 100ms steps so nothing is lost to rounding.
Measured
400 navigations per phase against one host, with
demo/rate-limit:Phase 2 runs after 8s of no traffic and repeats phase 1 exactly. The spacing
falls back to zero and climbs the same curve again.
The gaps the server saw at the start of a phase, in ms:
Eleven go at page load speed rather than the ten the arithmetic predicts,
because the allowance refills while you spend it.
Flags
--http-nav-delay 0turns the limiter off.--http-nav-delay Ngives a fixedN ms spacing with no ramp and no response feedback. Leaving it unset gives the
adaptive behaviour above.
--http-nav-burstsets how many navigations an idle host absorbs at once,default 10. It buys a head start and nothing else: the ramp and the sustained
rate are the same at any burst setting.
Constants
In
src/network/RateLimiter.zig:ADAPTIVE_INTERVAL_MSRAMP_BURSTSCOOLDOWN_INTERVALSMAX_INTERVALSOVERLOAD_INTERVALSFAILURE_INTERVALSRETRY_AFTER_MAX_MSRetry-Afterhonoured