Skip to content

feat: multi-dimension rate limiting (model, team, member) - #267

Merged
nic-6443 merged 4 commits into
mainfrom
feat/multi-dimension-rate-limit
May 13, 2026
Merged

feat: multi-dimension rate limiting (model, team, member)#267
nic-6443 merged 4 commits into
mainfrom
feat/multi-dimension-rate-limit

Conversation

@jarvis9443

@jarvis9443 jarvis9443 commented May 13, 2026

Copy link
Copy Markdown
Contributor

Extends rate limiting beyond per-API-key to support model, team, and member dimensions. All layers use AND logic — every active layer must pass or the request gets a 429.

What changed

ApiKey struct (aisix-core): added team_id, owner_id, team_rate_limit, owner_rate_limit fields (all optional, serde defaults).

MultiReservation (aisix-ratelimit): wraps N reservations so commit_tokens and Drop apply to all layers at once. keys() returns owned key list for post-stream token accounting.

quota.rs (aisix-proxy): rewritten to enforce 4 layers in order:

  1. API key (existing)
  2. Model — keyed by model:<name>, uses model.rate_limit
  3. Team — keyed by team:<id>, uses apikey.team_rate_limit
  4. Member — keyed by member:<id>, uses apikey.owner_rate_limit

If any layer denies, earlier reservations are dropped (releasing concurrency permits).

chat.rs: refactored to use quota::enforce_rate_limit, streaming path uses MultiReservation::keys() for multi-key post-stream accounting.

All other endpoints (messages, embeddings, completions, audio, images, rerank, responses, passthrough): updated to pass model rate limit to enforce().

Design decisions

  • DP stays identity-unaware: team_id/owner_id are opaque bucket keys
  • Empty string IDs treated as absent (avoids shared-bucket collision)
  • Rate limit applies to virtual/requested model name, not routing targets
  • RateLimit::is_unrestricted() skips no-op layers

Tests

  • 4 new unit tests for MultiReservation (commit, drop, keys, partial failure)
  • 2 new ApiKey serde tests (round-trip with new fields)
  • All existing tests pass

What's next (CP PR)

The control-plane PR will populate these fields via schema changes + CRUD + kine sync. Until then, all new fields default to None so behavior is unchanged.

Part of api7/AISIX-Cloud#269

Summary by CodeRabbit

  • New Features

    • API keys now carry optional team- and owner-scoped rate-limit metadata.
    • Public multi-layer reservation type exported for layered rate-limit handling.
  • Refactor

    • Proxy endpoints (audio, chat, completions, embeddings, images, messages, responses, rerank, passthrough) now apply model-specific and multi-layer rate limits before dispatch.
  • Tests

    • Added tests for API key JSON handling and multi-reservation behavior.

Review Change Stack

Extend rate limiting beyond per-API-key to support model, team, and
member dimensions. All layers use AND logic — every active layer must
pass or the request is rejected with 429.

Changes:
- ApiKey struct: add team_id, owner_id, team_rate_limit, owner_rate_limit
- MultiReservation: wraps N reservations, commit_tokens/drop applies to all
- quota.rs: multi-layer enforce (api_key → model → team → member)
- chat.rs: refactored to use quota::enforce_rate_limit, streaming path
  uses multi-key post-stream token accounting
- All non-chat endpoints updated to pass model rate limit to enforce()
- Unit tests for MultiReservation (commit, drop, keys, partial failure)

Part of api7/AISIX-Cloud#269
Copilot AI review requested due to automatic review settings May 13, 2026 07:56
@coderabbitai

coderabbitai Bot commented May 13, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 700a650b-8da1-4cc0-97cf-e3f7eec08dfe

📥 Commits

Reviewing files that changed from the base of the PR and between f7a10d5 and 07b2f3c.

📒 Files selected for processing (2)
  • crates/aisix-proxy/src/quota.rs
  • crates/aisix-ratelimit/src/limiter.rs
🚧 Files skipped from review as they are similar to previous changes (2)
  • crates/aisix-ratelimit/src/limiter.rs
  • crates/aisix-proxy/src/quota.rs

📝 Walkthrough

Walkthrough

Rate limiting moves from single API-key reservations to multi-layer enforcement (API key, model, team, owner). Adds ModelRateLimit and MultiReservation, refactors quota.enforce/enforce_rate_limit, extends ApiKey with optional team/owner fields, and updates proxy endpoints to pass model-specific limits.

Changes

Multi-layer Rate Limiting

Layer / File(s) Summary
ApiKey data model extensions
crates/aisix-core/src/models/apikey.rs
ApiKey struct extends with optional team_id, team_rate_limit, owner_id, and owner_rate_limit fields to carry team- and owner-scoped rate-limit configuration. Test fixtures and new deserialization tests verify the optional fields default to None and serde omits them when absent.
MultiReservation type and public export
crates/aisix-ratelimit/src/limiter.rs, crates/aisix-ratelimit/src/lib.rs
New MultiReservation<'a, C> type holds multiple Reservation instances and provides commit_tokens() to apply the same token amount to all layers, keys() to return all held reservation keys, and Debug formatting. Tests verify multi-layer token commitment, concurrent permit release on drop, keys collection, and partial-failure scenarios. MultiReservation is re-exported from the crate root.
Quota enforcement refactor
crates/aisix-proxy/src/quota.rs
Core enforce() and new enforce_rate_limit() functions now accept optional ModelRateLimit and perform layered reservations across API-key, model, team, and owner limiters when applicable. enforce() checks budget first; enforce_rate_limit() performs rate-limit-only enforcement (for streaming chat where budget is handled separately). Both return MultiReservation.
Proxy endpoint integration
crates/aisix-proxy/src/{audio,chat,completions,embeddings,images,messages,passthrough,rerank,responses}.rs
All proxy endpoints derive ModelRateLimit from the request model and resolved model entry, then pass it to enforce() or enforce_rate_limit(). Chat captures all post-stream rate-limit keys and applies TPM token accounting to each key during streaming completion.

Sequence Diagram

sequenceDiagram
  participant Endpoint as Proxy Endpoint
  participant Enforcer as quota::enforce / enforce_rate_limit
  participant ApiKeyLimiter as API Key Limiter
  participant ModelLimiter as Model RateLimit
  participant TeamLimiter as Team RateLimit
  participant OwnerLimiter as Owner RateLimit
  participant MultiRes as MultiReservation
  Endpoint->>Endpoint: derive ModelRateLimit (optional)
  Endpoint->>Enforcer: enforce(state, auth, model_rl) / enforce_rate_limit(state, auth, model_rl)
  Enforcer->>ApiKeyLimiter: reserve API-key layer
  Enforcer->>ModelLimiter: reserve model layer (if present)
  Enforcer->>TeamLimiter: reserve team layer (if present)
  Enforcer->>OwnerLimiter: reserve owner layer (if present)
  Enforcer->>MultiRes: bundle reservations
  MultiRes-->>Enforcer: return MultiReservation
  Enforcer-->>Endpoint: return MultiReservation
  Endpoint->>MultiRes: commit_tokens(token_count) / commit on post-stream
  MultiRes->>ApiKeyLimiter: commit tokens
  MultiRes->>ModelLimiter: commit tokens
  MultiRes->>TeamLimiter: commit tokens
  MultiRes->>OwnerLimiter: commit tokens
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • api7/ai-gateway-stash#32: Restructures quota/rate-limiting for chat_completions and embeddings to use a hook-driven mechanism; this PR implements model-specific ModelRateLimit and multi-layer MultiReservation enforcement.
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: multi-dimension rate limiting (model, team, member)' directly and clearly summarizes the main objective of the PR, which extends rate limiting to four dimensions (API key, model, team, and member).
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
crates/aisix-proxy/src/quota.rs (1)

57-105: ⚡ Quick win

Extract shared layer-reservation flow into a single helper.

enforce and enforce_rate_limit duplicate the same 4-layer pre-commit sequence. Centralizing this avoids policy drift between paths (especially on future layer/order changes).

Also applies to: 115-159

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/aisix-proxy/src/quota.rs` around lines 57 - 105, Extract the
duplicated 4-layer pre-commit sequence into a single helper (e.g.,
reserve_layers or collect_reservations) that accepts the state.limiter, auth,
and model_rl and returns a Vec of reservations or a MultiReservation; move the
existing logic that checks key_limits (auth.key().rate_limit), model_rl.limits
(and model_rl.name), team_id/team_rate_limit, and owner_id/owner_rate_limit into
that helper and call limiter.pre_commit(...) there (preserving ProxyError
mapping), then replace the inlined blocks in both enforce and enforce_rate_limit
with a call to this helper and construct MultiReservation::new(reservations)
from its result so both paths share identical layer/order behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@crates/aisix-proxy/src/quota.rs`:
- Around line 57-105: Extract the duplicated 4-layer pre-commit sequence into a
single helper (e.g., reserve_layers or collect_reservations) that accepts the
state.limiter, auth, and model_rl and returns a Vec of reservations or a
MultiReservation; move the existing logic that checks key_limits
(auth.key().rate_limit), model_rl.limits (and model_rl.name),
team_id/team_rate_limit, and owner_id/owner_rate_limit into that helper and call
limiter.pre_commit(...) there (preserving ProxyError mapping), then replace the
inlined blocks in both enforce and enforce_rate_limit with a call to this helper
and construct MultiReservation::new(reservations) from its result so both paths
share identical layer/order behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0086f992-aa4c-46fa-8aff-5d5fd8945935

📥 Commits

Reviewing files that changed from the base of the PR and between 626db8e and 328a8f7.

📒 Files selected for processing (13)
  • crates/aisix-core/src/models/apikey.rs
  • crates/aisix-proxy/src/audio.rs
  • crates/aisix-proxy/src/chat.rs
  • crates/aisix-proxy/src/completions.rs
  • crates/aisix-proxy/src/embeddings.rs
  • crates/aisix-proxy/src/images.rs
  • crates/aisix-proxy/src/messages.rs
  • crates/aisix-proxy/src/passthrough.rs
  • crates/aisix-proxy/src/quota.rs
  • crates/aisix-proxy/src/rerank.rs
  • crates/aisix-proxy/src/responses.rs
  • crates/aisix-ratelimit/src/lib.rs
  • crates/aisix-ratelimit/src/limiter.rs

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR extends the gateway’s rate limiting from a single API-key bucket to multi-dimension enforcement across API key, model, team, and member (owner), using AND logic so any denied layer returns 429 while releasing held concurrency permits.

Changes:

  • Added MultiReservation to atomically manage commit/drop behavior across multiple rate-limit layer reservations.
  • Reworked aisix-proxy quota enforcement to optionally apply model/team/member limits in addition to API-key limits, and wired model rate-limit resolution through all LLM endpoints.
  • Extended ApiKey with optional team_id/owner_id and corresponding inherited rate-limit fields, plus serde tests.

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
crates/aisix-ratelimit/src/limiter.rs Introduces MultiReservation and adds unit tests for multi-layer reservation behavior.
crates/aisix-ratelimit/src/lib.rs Re-exports MultiReservation for downstream use.
crates/aisix-proxy/src/quota.rs Implements multi-layer rate-limit enforcement (API key + model + team + member) and adds ModelRateLimit helper.
crates/aisix-proxy/src/chat.rs Refactors chat rate-limit reservation to use multi-layer enforcement and applies multi-key post-stream token accounting.
crates/aisix-proxy/src/responses.rs Passes resolved model rate-limit info into quota enforcement.
crates/aisix-proxy/src/rerank.rs Passes resolved model rate-limit info into quota enforcement.
crates/aisix-proxy/src/passthrough.rs Updates enforcement call site for endpoints without model resolution (None model rate limit).
crates/aisix-proxy/src/messages.rs Passes resolved model rate-limit info into quota enforcement.
crates/aisix-proxy/src/images.rs Passes resolved model rate-limit info into quota enforcement.
crates/aisix-proxy/src/embeddings.rs Passes resolved model rate-limit info into quota enforcement (and commits tokens across layers when known).
crates/aisix-proxy/src/completions.rs Passes resolved model rate-limit info into quota enforcement.
crates/aisix-proxy/src/audio.rs Passes resolved model rate-limit info into quota enforcement for both multipart and speech dispatch.
crates/aisix-core/src/models/apikey.rs Adds optional team/member identifiers and rate-limit fields to ApiKey, with serde tests.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread crates/aisix-ratelimit/src/limiter.rs
Comment thread crates/aisix-proxy/src/quota.rs
Comment thread crates/aisix-proxy/src/quota.rs
Deduplicate the 4-layer pre-commit sequence between enforce() and
enforce_rate_limit() into a single reserve_layers() function.
- Rewrite multi_reservation_partial_failure test to actually exercise
  MultiReservation with multiple acquired layers
- Fix from_model docstring to mention unrestricted case
Copilot AI review requested due to automatic review settings May 13, 2026 08:09

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 13 out of 13 changed files in this pull request and generated 1 comment.

Comment thread crates/aisix-proxy/src/quota.rs Outdated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants