Implement a Distributed Rate Limiter
OpenAISoftware Engineer · Technical ScreenLast updated
Implement a distributed token bucket rate limiter backed by a key-value store with optimistic concurrency control (CAS), handling clock skew, concurrent updates, and token replenishment.
Problem Statement
In a distributed infrastructure, multiple worker nodes must enforce API usage quotas against a shared backend. Design and implement a token bucket rate limiter that operates against a distributed key-value store supporting atomic Compare-And-Swap (CAS) operations.
The rate limiter tracks token consumption per key (e.g., customer or IP). Each key has a fixed maximum token capacity and replenishes at refill_rate_per_sec tokens per second. When a request arrives, the limiter computes accrued tokens since the last recorded timestamp, checks whether enough tokens are available, and updates the shared store via CAS.
Shared Key-Value Store Interface
You are provided an abstract KeyValueStore with the following operations:
class KeyValueStore:
def get(self, key: str) -> tuple[dict | None, int]:
"""
Retrieves the value and current version for the key.
Returns (None, 0) if the key does not exist.
"""
pass
def cas(self, key: str, expected_version: int, new_value: dict, ttl_seconds: int) -> bool:
"""
Atomically updates key to new_value if the current version equals expected_version.
Returns True on success, False if a version mismatch occurs.
"""
pass
Implementation Requirements
Implement the DistributedRateLimiter class:
class DistributedRateLimiter:
def __init__(
self,
store: KeyValueStore,
capacity: int,
refill_rate_per_sec: float,
max_cas_retries: int = 3,
ttl_seconds: int = 3600,
):
pass
def allow_request(self, key: str, timestamp_ms: int, tokens: int = 1) -> bool:
pass
- Token Bucket Behavior: Tokens replenish continuously:
new_tokens = min(capacity, current_tokens + (elapsed_sec * refill_rate_per_sec)). - Clock Skew Handling: If
timestamp_ms < last_updated_ms(due to out-of-order events or local clock drift), assumeelapsed_sec = 0and do not reduce available tokens or roll backlast_updated_ms. - Concurrency Control: If another node modifies the key between
getandcas,casreturnsFalse. The limiter must re-fetch the latest state and retry up tomax_cas_retriestimes. If all retries fail due to contention, returnFalse. - If available tokens after replenishment are strictly less than
tokens, immediately returnFalsewithout attempting CAS.
Constraints
1 <= capacity <= 10^60.001 <= refill_rate_per_sec <= 10^61 <= tokens <= capacity0 <= max_cas_retries <= 10timestamp_msis a non-negative 64-bit integer.
Examples
Example 1
store = InMemoryStore()
limiter = DistributedRateLimiter(store, capacity=5, refill_rate_per_sec=1.0)
# At t=0 ms: Key is uninitialized; bucket starts full (5 tokens). Consumes 3 tokens.
assert limiter.allow_request("user_1", timestamp_ms=0, tokens=3) == True
# At t=1000 ms: 1 second elapsed -> +1 token. Tokens = min(5, 2 + 1) = 3 tokens.
# Requesting 4 tokens fails (3 < 4). Store is not updated.
assert limiter.allow_request("user_1", timestamp_ms=1000, tokens=4) == False
# At t=1000 ms: Requesting 2 tokens succeeds. Remaining tokens = 1.
assert limiter.allow_request("user_1", timestamp_ms=1000, tokens=2) == True
Example 2: Clock Skew
# Continuing from Example 1 (last_updated_ms = 1000, tokens = 1.0)
# Out-of-order request arrives with timestamp_ms = 800
assert limiter.allow_request("user_1", timestamp_ms=800, tokens=1) == True
# Elapsed time is treated as 0 (not negative). Consumes 1 token, 0 remain.
# last_updated_ms remains 1000 in the store.
Hints
Hint 1
When initializing a key for the first time, treat the initial bucket state as having full capacity at the incoming timestamp.
Hint 2
Be sure to calculate token fractions as floats during replenishment, and only clamp elapsed time when `timestamp_ms < last_updated_ms`.
Hint 3
If CAS fails, the loop must execute `get` again to reload both the updated version and the latest token state before reapplying replenishment.
Follow-up questions
- How would you modify this design to support local in-memory token reservations to reduce KV store read/write QPS by 90%?
- If the shared KV store becomes completely unreachable, how would you implement a graceful degraded mode (e.g., local rate limiting fallback)?
- How does the behavior of optimistic concurrency (CAS) compare to Redis Lua scripts in terms of network roundtrips and high-contention throughput?
Solution walkthrough is for members
Get the full walkthrough with reference code and complexity analysis, plus Kitty Coach at 300 messages a day. Hints, the code runner and community answers stay free.