feat(token_rate_limit): sliding-window/token-bucket rate limiting (M1/M2/M6) - #796
Conversation
|
Unsigned commits: e156a1a, 47f6876, 82eab8e. Please sign your commits. |
d3c7813 to
c644536
Compare
praxis-bot
left a comment
There was a problem hiding this comment.
Review: feat(token_rate_limit): per-header rate-limit bucket keys (ai#129)
Well-structured PR with thorough test coverage (112 unit tests, integration tests for all three example configs, both algorithms, both backends). The reserve/reconcile design, fail-closed Valkey behavior, and per-key budget isolation are all solid. Two issues worth addressing before merge.
| Severity | Count |
|---|---|
| Critical | 0 |
| Large | 2 |
| Medium | 1 |
All comments are inline.
nerdalert
left a comment
There was a problem hiding this comment.
Thanks for putting this together. The reservation/reconciliation split and shared backend direction are solid. I found four areas where tightening the trust boundary and hot-path behavior would make the implementation safer to operate.
|
Reviewed the full diff, correct proposal alignment (M1/M2/M6), and both algorithms working with pluggable backends. On Brent's review comments: Approving, let's get this in. |
|
Two commits landed after your approval, flagging since the diff changed:
Trust-boundary and probe-traffic are unchanged, still open per your note above and Let me know if you want another look given the new commits. |
@asaadbalum wfm! Added them to the post merge tracking issue. praxis-proxy/grid#101 @jordigilh the additions look good. Lets get CI passing and the review issues resolved and once @jland-redhat gives a 👍 we will get it queued for merge, ty! |
|
LGTM |
|
So, just for my understanding here, is this meant to become the TRLPolicy enforcing mechanism at some point? |
Thanks for looking @alexsnaps! I think experimental makes a lot of sense! It's currently opt-in but making it explicitly experimental wfm since there is a lot of follow up work. WDYT @jordigilh? We should track something for how it ties back to Tagging in cc/ @jland-redhat @asaadbalum ty! Jamie already flagged the |
|
Moving this forward in experimental is perfectly fine with me, to be clear. But the concerns wrt backwards compatibility are a big one to me, let's make sure we're (BU?) are all aligned on this. If we then move forward with this implementation, we need to create follow up work on a few things (don't want to slow a PoC down tho, hence the experimental proposal, but HA, clustered Redis/sharded redis-alikes, and failure scenarios need to be addressed and probably consolidated - this currently suffer from a few pathological "edge cases"). |
And just as a note from the MaaS and MaaSSubscription perspective at least. My hope is that we can make keep backwards with existing subscriptions that we can just translate differently on the backend. While also exposing this new functionality. Basically an existing subscription should just be a Sliding Window config with 0 reserved tokens |
|
Feature gate's in: TRLPolicy relationship: Will open a spike shortly and link it here once filed. HA/clustered Valkey: Will open a spike to enumerate the failure modes before committing to a design. Will link here too. |
praxis-bot
left a comment
There was a problem hiding this comment.
PR Re-Review
Summary: Previous Large findings (TCP connection churn, phantom doc fields) have been partially addressed. The Valkey connection is now cached correctly. However, the documentation still has a critical gap: the algorithm field (required discriminator for sliding_window vs token_bucket) is completely absent from the configuration reference table.
| Severity | Count |
|---|---|
| Critical | 0 |
| Large | 1 |
| Medium | 0 |
04da692 to
cac0838
Compare
Implements the agreed M1/M2/M6 core of the token rate limiting proposal (00121_token-rate-limiting.md in praxis-proxy/enhancements, tracked by epic ai#121): sliding-window and token-bucket algorithms, per-rule request/response matching, and a pluggable backend (in-memory by default, Valkey for shared state across replicas). Gated behind the token-rate-limit-filter Cargo feature (disabled by default, mirroring the azure-ad/gcp-adc/http-callout pattern) so nothing changes for consumers who don't opt in while the remaining epic milestones (M3+) are still being designed. Signed-off-by: Jordi Gil <jgil@redhat.com>
Covers single-rule and mixed-algorithm (sliding_window + token_bucket) setups end to end, including the Valkey-backed path that isolates budgets across gateway replicas. Also adds the runnable example configs referenced from the filter's docs. Signed-off-by: Jordi Gil <jgil@redhat.com>
The Valkey-backed tests were silently skipping in CI because nothing set TOKEN_RATE_LIMIT_VALKEY_URL. Adds a dedicated workflow (path-scoped so it only runs when relevant files change) plus Makefile targets that spin up Valkey and run the gated test suite against it. Signed-off-by: Jordi Gil <jgil@redhat.com>
generate-filter-docs dropped the discriminator field for any #[serde(flatten)]'d struct whose type was an internally-tagged enum (#[serde(tag = "...")]) -- e.g. rules[].algorithm on the new token_rate_limit filter never made it into the generated table. Synthesizes the missing row instead of silently omitting it, with a regression test. Signed-off-by: Jordi Gil <jgil@redhat.com>
cargo xtask generate-filter-docs output for the new filter, including the algorithm field now that the generator handles flattened, internally-tagged enums correctly. Signed-off-by: Jordi Gil <jgil@redhat.com>
ebb9591 to
28ce5c3
Compare
|
Force pushed after rebasing and removed all commits that were not part of this PR. All comments were already addressed prior to the force push, but feel free to re-review again at your discretion. |
Summary
Implements the uncontested MVP core of the token rate limiting proposal (
00121_token-rate-limiting.mdin praxis-proxy/enhancements, epic ai#121):rules:, each an optional static header-value match condition bound to its own budget (catch-alldefaultrule when nomatch:is given)token_count'stoken.total) once the response completesX-RateLimit-*-Tokensheaders on hard denyTwo admission algorithms, chosen per rule via
algorithm:(sliding_window|token_bucket, per the maintainer framing on ai#789/praxis#551 that this is "a per-rule choice, similar to shadow/enforcement-action knobs elsewhere"), both behind a pluggable backend: in-process by default, or a shared Valkey backend (backend: {kind: valkey}) for state shared across gateway instances/replicas.Descoped from this PR: M5 (per-header/per-app bucket keys, ai#129) has been pulled out. It conflicted with the quota-key design ai#790 already established for the Valkey-backed shared quota work (
namespace + rule + principal + canonical model, not an arbitrary header value), and ai#790 itself isn't approved yet. Every rule now resolves to a single shared budget regardless of request headers; ai#129 tracks per-key budgets separately pending the quota-key question being resolved with ai#790's stakeholders.Also deliberately deferred (see
filters/src/token_rate_limit/mod.rsmodule doc for the full list and rationale): configurable estimation (M3), token-type-aware weighting (M4), multiple budgets/soft-limit tiers per rule, observability, and metering.Additional fixes included
reserve/reconcilecall.token_rate_limit'sAlgorithmConfigenum collided with an unrelated struct of the same name in a sibling filter, producing phantom fields in generated docs; renamed toRuleAlgorithm. Also fixed the generator leaking Rust'sr#raw-identifier prefix into thematchfield name.Testing
filters/src/token_rate_limit/, covering both algorithms, both backends, config validation, and reservation/reconciliation edge cases (expired-reservation reaping, lost-request handling, capacity denials on all three configured bounds).examples/configs/token-rate-limit.yaml,examples/configs/token-rate-limit-mixed-algorithms.yaml).make lint(clippy, fmt,cargo doc -D warnings, separator/filter-doc/example-test xtask lints) passes clean.Also fixed: every Valkey-backed test is gated on
TOKEN_RATE_LIMIT_VALKEY_URL, but CI never set it, so they silently no-opped instead of running. Added.github/workflows/valkey.yaml(same shape aspostgres.yaml) to run them for real, which surfaced a few more gaps now closed too:ValkeyEval's invalidate-on-failure path, the in-process backends' unusedreconcile/enqueue_reconciletrait methods, and two no-op paths inmod.rs.Supersedes the closed draft #773, which called out the sliding-window-vs-token-bucket question as unresolved; this PR resolves it by supporting both, per rule.