Define the APIs expected from the system. This is your chance to analyze and define the read and write paths so that you can come up with the high-level design...
Every request calls get_api_quota to check if the request can be processed. If not enough quota, return an appropriate error to the client.
Every request calls record_request(endpoint, user_id), which is processed by backends to increment per-endpoint and per-user counters.
// Checking the rate limit for an endpoint and user
get_api_quota(endpoint: str, user_id: str)
Response
{
endpoint_max_quota: int
endpoint_remaining_quota: int
user_max_quota: int
user_remaining_quota: int
}
// Incrementing request counters
record_request(endpoint: str, user_id: str)
Response
{
// Nothing returned.
}
Describe the overall system architecture. Identify the main components needed to solve the problem end-to-end. Use the diagramming tool to create a block diagram.
The general idea is to implement a request counter using key-value stores. For per-user quota, we'll use "endpoint:user_id" as the key, and for endpoints we'll just use "endpoint" as the key. The values will be of the form:
{
"remaining_quota": int
"last_token_refill_time": Timestamp
}
Then we can implement a token-bucket filter as follows:
Early in the request processing, e.g. in an API Gateway, we call get_api_quota() which hits a Rate Limit Service and reads from the key-value store to check if the request should be allowed. If the request is allows to process, we call record_request()
Deep dive into 2-3 key components. Explain how they work, how they scale, discuss tradeoffs, capacity, and any relevant algorithms or data structures.
The per-user rate limits have good horizontal scaling, because the storage keys containing user ID naturally distribute across database shards. We may add a caching layer, either in Rate Limit Service or even locally on the Web Server.
Per-endpoint rate limit is more challenging. Even with in-memory databases like Redis, if the entire system is reading/writing a single key (for the endpoint) this could overload the service. There are a few options to deal with this: