Skip to content

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.