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...
limitersvc - invoked by a client (potentially another API - API is checking with limitersvc that client can make requests)
input:
{
"apiKey": myAPIKey,
"request": requestType // different endpoints can have different max rate limits
}
output:
{
"apiKey": myAPIKey,
"status": success/failure
}
configsvc
getPlanForEndpoint/thisendpoint
{
"apiKey": myAPIKey,
}
output:
{
"apiKey": myAPIKey,
"maxRequests": 3,
"minutes": 1
}
upgradePlanForEndpoint/thisendpoint - upgrading tier
{
"apiKey": myAPIKey,
"payment": cardDetails
}
output:
{
"apiKey": myAPIKey,
"maxRequests": 10,
"minutes": 1
}
cancelPlanForEndpoint/thisendpoint - upgrading tier
{
"apiKey": myAPIKey,
}
output:
{
"apiKey": myAPIKey,
"success": true
}
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.
Deep dive into 2-3 key components. Explain how they work, how they scale, discuss tradeoffs, capacity, and any relevant algorithms or data structures.
b. Cache key is api key + endpoint. Values are number of tokens + last refill time.
Cons: what if we receive two requests from the same API key+ endpoint as cache key at same millisecond, different machines? Use a locking mechanism, where one acquires a lock to write, but this is very slow. Redis has support for atomic operations. We move all logic into one atomic step inside Redis and it guarantees no race conditions and that no other request can interrupt this logic.
what if machines have different clocks? Servers can differ in time. Let's use Redis time as the single source of truth of time.
Let's say we have a billion of DAU, how do we handle high QPS? We can have a local cache in the load balancer to know overload Redis. We can periodically sync Redis and local cache in load balancer. If tokens run low for a cache key api key + endpoint, fetch/refresh from redis. Another approach is to partition redis by api key and endpoint to spread load evenly (use consistent hashing)