Valkey rate limiting
Two independent sliding-window counters are enforced on every POST /chat/message request, both keyed on user_id. If either limit is exceeded the server returns HTTP 429.
Relevant file: server/rate_limit.py, server/routes/chat.py
Configuration
| Setting | Default | Env variable |
|---|---|---|
| Max requests per window | 20 | TURINGDB_RATE_LIMIT_REQUESTS |
| Window duration | 600 s (10 min) | TURINGDB_RATE_LIMIT_WINDOW_SECONDS |
| Max tokens per window | 500 000 | TURINGDB_RATE_LIMIT_TOKENS_PER_WINDOW |
Relevant file: server/config.py
Key schema
| Key pattern | Valkey type | TTL | Description |
|---|---|---|---|
rate_limit:{user_id} | List | 10 min (rolling) | Request timestamps for the sliding-window request counter |
token_limit:{user_id} | Sorted Set | 10 min (rolling) | Token-usage entries for the sliding-window token budget |
TTL is refreshed on every write so the key expires automatically after a full window of inactivity.
Request-count limiter — rate_limit:{user_id}
Algorithm: sliding window using a Valkey list of Unix timestamps.
check_rate_limit(user_id):
entries = LRANGE rate_limit:{user_id} 0 -1
recent = count entries where float(e) > (now - window_seconds)
if recent >= max_requests → return False (reject)
LPUSH rate_limit:{user_id} now
EXPIRE rate_limit:{user_id} window_seconds
LTRIM rate_limit:{user_id} 0 (max_requests - 1) # cap list size
return True (allow)
The list is trimmed to max_requests entries so it never grows unbounded.
Token-budget limiter — token_limit:{user_id}
Algorithm: sliding window using a Valkey sorted set. Each member encodes {timestamp}:{token_count}:{uuid} with the timestamp as its score, enabling efficient range-based removal of expired entries.
Checking the budget (before the request)
check_token_budget(user_id):
ZREMRANGEBYSCORE token_limit:{user_id} -inf (now - window_seconds)
entries = ZRANGE token_limit:{user_id} 0 -1
used = sum(int(e.split(":")[1]) for e in entries)
return used < max_tokens
Recording usage (after the request)
Token counts for the intent router and the selected handler are summed and recorded as a single entry:
record_token_usage(user_id, tokens):
member = f"{now}:{tokens}:{uuid4().hex}"
ZADD token_limit:{user_id} {member: now}
EXPIRE token_limit:{user_id} window_seconds
The uuid4().hex suffix guarantees uniqueness when two requests finish in the same millisecond (sorted-set members must be unique).
Error responses
| Condition | HTTP status | Detail |
|---|---|---|
| Request count exceeded | 429 | Rate limit exceeded: 20 request(s) per 600s. |
| Token budget exceeded | 429 | Token budget exceeded: 500000 tokens per 600s. |
Known limitations and planned work
| Limitation | Notes |
|---|---|
| Token budget is checked before the request | Tokens consumed are not known until after the LLM responds, so the check uses the balance from the previous request. A user who repeatedly sends large requests can slightly exceed the budget before being blocked. |
| No per-endpoint granularity | Both limiters apply only to POST /chat/message; other endpoints are unrestricted. |
| No admin override / whitelist | There is no mechanism to exempt a specific user_id from rate limiting. |