feat: add a qualified preview request-limiting profile - #18
Merged
Merged
Conversation
Apply three independent budgets to a served tree: request rate, concurrent connections, and per-connection bandwidth. A deployment that sets only one leaves the others unbounded. Both limits answer 429 rather than the NGINX default 503, which is indistinguishable from an outage and invites clients to retry harder against a server already shedding load. The health endpoint sits outside every limit: a limited liveness probe turns a traffic spike into a restart, removing capacity exactly when it is needed. The limit outcome is logged; the limit key is not. Recording the outcome supports capacity and abuse analysis, while recording the raw client address per request would add a personal identifier to an access stream that is otherwise free of them. Documents the two ways these limits silently stop working: keyed on the direct peer address they become a global cap behind a proxy, and keyed on a client-controlled header they cease to exist while still looking configured. Three findings from running the scenario: - `limit_req` is evaluated before `limit_conn`, so a rate-rejected request records the connection limit as NOT_EVALUATED. The connection budget is therefore measured before the rate budget is spent, and the field meaning is documented so NOT_EVALUATED is not misread as "allowed". - A response that fits the socket buffer is handed to the kernel immediately and never holds a connection, so no concurrency builds up however slowly the client reads. The payload now exceeds `limit_rate_after` and the profile's own bandwidth limit paces it. - The served directory came from `mktemp -d`, which is 0700 and cannot be traversed by the container identity, so every request answered 403 before any limit applied. Log assertions can now require a field value and, where a scenario issues identical requests on purpose, be satisfied by any one of the matching events.
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.
Third of the remaining Package 3 example profiles. Preview/unqualified.
Profile
examples/profiles/rate-limited/nginx.confapplies three independent budgets: request rate, concurrent connections, and
per-connection bandwidth. Setting only one leaves the others unbounded, which
is the usual way a "rate limited" service still falls over.
Both limits answer 429, not the NGINX default 503. A 503 is
indistinguishable from an outage and invites clients to retry harder against a
server that is already shedding load.
The health endpoint is outside every limit. A limited liveness probe turns a
traffic spike into a failed probe and a restart, removing capacity exactly when
it is needed. The test asserts
/healthzstill answers while the client'srequest budget is exhausted.
The two ways these limits silently stop working
Both are documented in the config rather than left to be discovered:
cap the moment there is a load balancer or ingress in front, because every
client presents the proxy's address. A deployment in that position needs a
key derived from a forwarded address it actually trusts.
still looking configured — the client picks a fresh bucket per request.
Logging
Outcomes are logged; the limit key is not. Recording the outcome supports
capacity and abuse analysis. Recording the raw client address on every request
would put a personal identifier into an access stream that is otherwise free of
them.
Three findings from actually running this
Each would have produced a test that passed while proving nothing, or a
mystifying failure.
limit_reqruns beforelimit_conn. Once the rate limit is rejecting, theconnection limit is never evaluated and records
NOT_EVALUATED— notPASSED. My first version tested connections after exhausting the ratebudget, so every rejection came from the rate limiter and the connection limit
was never exercised. The connection budget is now measured first, and the field
semantics are documented so
NOT_EVALUATEDis not misread as "allowed".A response that fits the socket buffer never holds a connection. The first
payload was 64 KB; nginx handed it to the kernel immediately and considered the
request done, so no concurrency accumulated however slowly the client read.
--limit-rateon the client cannot change that. The payload now exceedslimit_rate_afterso the profile's own bandwidth limit paces the responseserver-side — which is also why
limit_ratebelongs in this profile.mktemp -dis 0700. The served directory could not be traversed by thecontainer identity, so every request answered 403 before any limit applied.
Same class as the TLS key-permission issue in #15.
Five consecutive green local runs on rootless Podman.
Validator
Log assertions can now require a field value (
--require-field NAME=VALUE),and a scenario that issues identical requests on purpose can opt into repeated
identities (
--allow-repeated), where the assertion is satisfied by any one ofthe matching events. That is what lets the connection test assert "at least one
of these was rejected by the connection limit specifically" when the same batch
also contains rate rejections and successes.
Not qualified
Workload tuning, zone sizing against real client populations, behaviour once a
zone is exhausted, and interaction with an upstream rate limiter.
Roadmap
Removes rate and connection limits from the two Package 3 items that listed
them. Extended health/readiness and ClickHouse remain.
🤖 Generated with Claude Code