Skip to content

Commit 39cd2e2

Browse files
missBergclaude
andcommitted
docs: add QuotaPolicy capability guide
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent d6323d0 commit 39cd2e2

1 file changed

Lines changed: 285 additions & 0 deletions

File tree

Lines changed: 285 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,285 @@
1+
---
2+
id: quota-policy
3+
title: Quota Policy
4+
sidebar_position: 6
5+
---
6+
7+
# Quota Policy
8+
9+
QuotaPolicy enables token-based quota management for AI inference services in Envoy AI Gateway.
10+
When a service's quota is exceeded, requests are rejected with a `429` status code. If the
11+
`AIGatewayRoute` defines multiple backends, traffic automatically fails over to the next available
12+
backend whose quota has not been exhausted.
13+
14+
:::note
15+
QuotaPolicy manages **total consumption budgets** (e.g., 100 000 tokens per hour). This is distinct
16+
from [usage-based rate limiting](./usage-based-ratelimiting.md), which controls **request velocity**
17+
(e.g., requests per second). Use QuotaPolicy when you need to cap cumulative token spend across a
18+
time window.
19+
:::
20+
21+
## Overview
22+
23+
Key features of QuotaPolicy:
24+
25+
- **Service-wide token quotas** -- set a single token budget that applies to all traffic hitting an AIServiceBackend.
26+
- **Per-model quota configuration** -- assign different token limits to individual models served by the same backend.
27+
- **CEL expressions for custom cost calculation** -- weight input, output, and cached tokens differently using CEL.
28+
- **Exclusive vs Shared bucket modes** -- control whether matching quota buckets are charged independently or together.
29+
- **Client-selector based quota rules** -- carve out per-tenant or per-header quotas using bucket rules.
30+
- **Shadow mode for testing without enforcement** -- dry-run quota evaluation without rejecting traffic.
31+
32+
## How It Works
33+
34+
1. A `QuotaPolicy` resource is created and attached to one or more `AIServiceBackend` resources via `targetRefs`.
35+
2. For each completed request, the token cost is computed using the configured cost expression (defaults to `total_tokens`).
36+
3. The token cost is charged against the matching quota bucket (service-wide, per-model default, or a matching bucket rule).
37+
4. When the quota for a bucket is exceeded, subsequent requests receive a `429 Too Many Requests` response.
38+
5. If the `AIGatewayRoute` lists multiple `backendRefs`, traffic fails over to the next backend whose quota is still available.
39+
40+
## Configuration
41+
42+
### Service-Wide Quota
43+
44+
The simplest configuration applies a single token budget to all requests routed to the target backend.
45+
46+
```yaml
47+
apiVersion: aigateway.envoyproxy.io/v1alpha1
48+
kind: QuotaPolicy
49+
metadata:
50+
name: my-quota-policy
51+
spec:
52+
targetRefs:
53+
- group: aigateway.envoyproxy.io
54+
kind: AIServiceBackend
55+
name: my-backend
56+
# Quota applied across all models and all clients.
57+
serviceQuota:
58+
quota:
59+
limit: 100000 # Maximum tokens allowed in the window.
60+
duration: "1h" # Sliding window duration.
61+
```
62+
63+
### Custom Cost Expression
64+
65+
By default, cost is measured using `total_tokens`. You can override this with a CEL expression
66+
that weights token types differently. The following variables are available in the expression:
67+
`input_tokens`, `cached_input_tokens`, and `output_tokens`.
68+
69+
```yaml
70+
serviceQuota:
71+
# Cached input tokens cost 10% of regular input tokens;
72+
# output tokens cost 6x regular input tokens.
73+
costExpression: "input_tokens + cached_input_tokens * 0.1 + output_tokens * 6"
74+
quota:
75+
limit: 50000
76+
duration: "1h"
77+
```
78+
79+
:::tip
80+
Use a custom cost expression when different token types have significantly different costs with
81+
your provider. For example, output tokens are typically more expensive than input tokens.
82+
:::
83+
84+
### Per-Model Quotas
85+
86+
Use `perModelQuotas` to assign different budgets to individual models served by the same backend.
87+
Per-model quotas override the service-wide quota for the specified model.
88+
89+
```yaml
90+
perModelQuotas:
91+
- modelName: gpt-4
92+
quota:
93+
mode: Exclusive
94+
defaultBucket:
95+
limit: 10000 # Strict limit for expensive model.
96+
duration: "1h"
97+
- modelName: gpt-3.5-turbo
98+
quota:
99+
mode: Exclusive
100+
defaultBucket:
101+
limit: 100000 # Higher limit for cost-effective model.
102+
duration: "1h"
103+
```
104+
105+
### Bucket Modes
106+
107+
The `mode` field on a `QuotaDefinition` controls how the default bucket and bucket rules interact
108+
when a request matches one or more rules.
109+
110+
- **Exclusive** -- the request is charged to matching `bucketRules` **or** the `defaultBucket`, but
111+
not both. If no bucket rule matches, the default bucket is used. The request is denied only when
112+
all matching buckets are exhausted.
113+
- **Shared** -- the request is charged to **all** matching `bucketRules` **and** the `defaultBucket`.
114+
The request is allowed only if quota is available in every matching bucket.
115+
116+
```yaml
117+
perModelQuotas:
118+
- modelName: gpt-4
119+
quota:
120+
mode: Shared # Charge both default and matching rule buckets.
121+
defaultBucket:
122+
limit: 10000
123+
duration: "1h"
124+
bucketRules:
125+
- clientSelectors:
126+
- headers:
127+
- name: x-tenant-id
128+
type: Distinct
129+
quota:
130+
limit: 2000 # Per-tenant limit within the shared budget.
131+
duration: "1h"
132+
```
133+
134+
:::warning
135+
In `Shared` mode a request is rejected if **any** of the matching buckets is exhausted, even if
136+
other buckets still have remaining quota. Choose `Exclusive` mode when you want independent,
137+
non-overlapping budgets.
138+
:::
139+
140+
### Client Selectors with BucketRules
141+
142+
Bucket rules let you carve out dedicated quotas for specific clients identified by request headers.
143+
This is useful for multi-tenant deployments where each tenant needs its own token budget.
144+
145+
```yaml
146+
perModelQuotas:
147+
- modelName: gpt-4
148+
quota:
149+
mode: Exclusive
150+
defaultBucket:
151+
limit: 10000 # Fallback quota for unmatched tenants.
152+
duration: "1h"
153+
bucketRules:
154+
- clientSelectors:
155+
- headers:
156+
- name: x-tenant-id
157+
type: Exact
158+
value: premium-tenant
159+
quota:
160+
limit: 50000 # Premium tenant gets a larger budget.
161+
duration: "1h"
162+
```
163+
164+
You can also use `type: Distinct` to create a separate bucket for every unique value of a header:
165+
166+
```yaml
167+
bucketRules:
168+
- clientSelectors:
169+
- headers:
170+
- name: x-tenant-id
171+
type: Distinct # One bucket per unique tenant ID.
172+
quota:
173+
limit: 5000
174+
duration: "1h"
175+
```
176+
177+
### Shadow Mode
178+
179+
Shadow mode lets you test quota rules without actually rejecting traffic. When `shadowMode` is
180+
enabled on a bucket rule, all quota checks are performed (cache lookups, counter updates, telemetry
181+
generation), but the outcome is never enforced -- the request always succeeds even if the quota is
182+
exceeded.
183+
184+
```yaml
185+
bucketRules:
186+
- clientSelectors:
187+
- headers:
188+
- name: x-tenant-id
189+
type: Distinct
190+
quota:
191+
limit: 5000
192+
duration: "1h"
193+
shadowMode: true # Evaluate but do not enforce.
194+
```
195+
196+
:::tip
197+
Use shadow mode when rolling out new quota rules. Monitor the telemetry to verify that limits are
198+
set correctly before switching to enforcement by removing `shadowMode` (or setting it to `false`).
199+
:::
200+
201+
## Duration Format
202+
203+
The `duration` field on `QuotaValue` accepts a number followed by a unit suffix:
204+
205+
| Suffix | Unit | Example |
206+
|--------|---------|---------|
207+
| `s` | Seconds | `"30s"` |
208+
| `m` | Minutes | `"15m"` |
209+
| `h` | Hours | `"1h"` |
210+
211+
If no suffix is provided, the value is interpreted as seconds.
212+
213+
## Full Example
214+
215+
The following `QuotaPolicy` combines service-wide and per-model quotas, bucket rules with client
216+
selectors, custom cost expressions, and shadow mode.
217+
218+
```yaml
219+
apiVersion: aigateway.envoyproxy.io/v1alpha1
220+
kind: QuotaPolicy
221+
metadata:
222+
name: full-quota-policy
223+
spec:
224+
targetRefs:
225+
- group: aigateway.envoyproxy.io
226+
kind: AIServiceBackend
227+
name: my-backend
228+
229+
# Service-wide fallback quota (applies to models without a per-model entry).
230+
serviceQuota:
231+
costExpression: "input_tokens + output_tokens * 3"
232+
quota:
233+
limit: 200000
234+
duration: "1h"
235+
236+
# Per-model quotas with bucket rules.
237+
perModelQuotas:
238+
- modelName: gpt-4
239+
quota:
240+
costExpression: "input_tokens + cached_input_tokens * 0.1 + output_tokens * 6"
241+
mode: Exclusive
242+
defaultBucket:
243+
limit: 10000
244+
duration: "1h"
245+
bucketRules:
246+
# Premium tenant gets a dedicated, higher quota.
247+
- clientSelectors:
248+
- headers:
249+
- name: x-tenant-id
250+
type: Exact
251+
value: premium-tenant
252+
quota:
253+
limit: 50000
254+
duration: "1h"
255+
# Track per-tenant usage in shadow mode (no enforcement).
256+
- clientSelectors:
257+
- headers:
258+
- name: x-tenant-id
259+
type: Distinct
260+
quota:
261+
limit: 5000
262+
duration: "1h"
263+
shadowMode: true
264+
265+
- modelName: gpt-3.5-turbo
266+
quota:
267+
mode: Shared
268+
defaultBucket:
269+
limit: 100000
270+
duration: "1h"
271+
bucketRules:
272+
- clientSelectors:
273+
- headers:
274+
- name: x-tenant-id
275+
type: Distinct
276+
quota:
277+
limit: 20000
278+
duration: "1h"
279+
```
280+
281+
## References
282+
283+
- [API Reference](../../api/api.mdx)
284+
- [Usage-Based Rate Limiting](./usage-based-ratelimiting.md)
285+
- [Provider Fallback](./provider-fallback.md)

0 commit comments

Comments
 (0)