A rate limit is a mechanism that restricts the number of requests a client can make to a server within a specific time frame, preventing abuse and ensuring fair resource usage.
Top 3 Benefits of Using a Rate Limiter in an App
Protect Resources: Prevents resource exhaustion and ensures fair usage.
Mitigate Abuse: Safeguards against malicious attacks and spam.
Improve Performance: Optimises resource utilisation and enhances overall system performance.
Lets implements fixed-window rate limiting using Redis for real-time tracking and PostgreSQL for configuration management. By applying fixed-time windows, we limit the number of requests that a user can make within specific intervals, enabling precise control over usage patterns and protecting against abuse.
System Components
PostgreSQL: Stores rate limit configurations per user and API endpoint.
Redis: Tracks API usage in real-time with a TTL-based structure.
Annotations: Assigns aliases to API endpoints, allowing flexible configuration by mapping multiple endpoints to the same alias.
This combination provides both efficient tracking and easy configurability for scalable, fine-grained rate limiting.
PostgreSQL for Rate Limit Configurations
PostgreSQL stores rate limit settings per user and API alias, simplifying configuration management. We define the following tables:
users
: Stores user information (id
,email
, etc).api_identifier
: Assigns unique aliases to API endpoints and tracks endpoint details.api_rate_limit_config
: Links specific rate limits to each user and API alias.user_id
: Links tousers.id
.api_alias
: Alias assigned to the endpoint via annotations.time_window_in_sec
: Duration of the time window.limit
: Maximum allowed requests within the time window.
CREATE TABLE users (
id SERIAL PRIMARY KEY,
email VARCHAR(255) NOT NULL
);
CREATE TABLE api_identifier (
id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
name VARCHAR(255) NOT NULL, -- A unique alias for each API (used in annotations)
endpoint_path VARCHAR(255) NOT NULL, -- The actual path of the API endpoint
http_method VARCHAR(10) NOT NULL, -- HTTP method (GET, POST, PUT, DELETE)
description TEXT -- Optional description of the API endpoint
);
CREATE TABLE api_rate_limit_config (
id SERIAL PRIMARY KEY,
api_id BIGINT REFERENCES api_identifier(id) NOT NULL,
time_window_in_sec INT NOT NULL,
limit INT NOT NULL,
UNIQ ( api_id, time_window_in_sec )
);
Redis for Real-Time Rate Tracking
Redis tracks requests in real time using a TTL-based key structure. Keys are defined as
user_id:api_id:time_window
, allowing efficient usage tracking per endpoint per user.Tracks the number of requests made in the current time window.
Each key has a TTL set to
time_window_in_sec
Redis Commands:
Check if key exists:
EXISTS user_id:api_id:time_window
Increment usage count:
INCR user_id:api_id:time_window
Set TTL for time-based reset:
EXPIRE user_id:api_id:time_window time_window_in_sec
Annotations for Endpoint Aliasing
Annotations are often used in languages like Java and Python to simplify functionality and make code cleaner and more modular. They allow you to define behaviours that are then applied or "triggered" at runtime.
Annotations provide a flexible mechanism to alias API endpoints, enabling administrators to group functionally similar endpoints under a single alias. This approach simplifies rate limit configuration and management, as changes to the rate limit for an alias apply across all endpoints sharing it.
Here’s how to create and use annotations for rate limiting in Java and Python.
Define Annotation: Use a custom annotation, e.g.,
@ApiIdentifier("GET_USER_INFORMATION")
, to specify an alias for each endpoint or method in the controller.@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface ApiIdentifier { String value(); // to store the identifier like "GET_USER_INFORMATION" }
from functools import wraps def api_identifier(identifier): def decorator(view_func): @wraps(view_func) def wrapped_view(*args, **kwargs): # Print the identifier for demonstration print(f"API Identifier: {identifier}") return view_func(*args, **kwargs) wrapped_view.api_identifier = identifier # Attach the identifier to the view return wrapped_view return decorator
Apply Annotation: Assign a unique alias to each endpoint. This alias is then used as the api_identifier.name in both PostgreSQL and Redis.
@RestController public class UserController { @ApiIdentifier("GET_USER_INFORMATION") @GetMapping("/api/user/info") public String getUserInfo() { return "User information data"; } }
from django.http import JsonResponse from django.views import View # Function-based view example @api_identifier("GET_USER_INFORMATION") def get_user_info(request): return JsonResponse({"message": "User information data"})
Configuration Retrieval: On each request, look up the
api_id
inapi_rate_limit_config
to fetch the appropriate rate limit.@Component public class ApiIdentifierInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (handler instanceof HandlerMethod) { Method method = ((HandlerMethod) handler).getMethod(); ApiIdentifier apiIdentifier = method.getAnnotation(ApiIdentifier.class); if (apiIdentifier != null) { String identifier = apiIdentifier.value(); System.out.println("API Identifier: " + identifier); // Use identifier for logging, rate limiting, etc. } } return true; } }
from django.utils.deprecation import MiddlewareMixin class ApiIdentifierMiddleware(MiddlewareMixin): def process_view(self, request, view_func, view_args, view_kwargs): # Check if the view has an `api_identifier` attribute identifier = getattr(view_func, 'api_identifier', None) if identifier: print(f"API Identifier: {identifier}") # Here, you could implement rate limiting, logging, etc., based on identifier. return None
Redis Validation: Check Redis using the alias-based key structure. Increment the request count or deny access if the rate limit is reached.
Response Header
To inform clients of their usage and reset times, include the following headers in the response:
X-RateLimit-Limit
: Shows the maximum number of requests allowed within the time window.X-RateLimit-Remaining
: Indicates the remaining requests in the current window.X-RateLimit-Reset
: Provides the timestamp when the rate limit will reset, in Unix epoch seconds.
Summary
This setup combines Redis’s efficiency in real-time usage tracking with PostgreSQL’s reliability for storing configuration. By using annotations for aliasing, you can manage multiple endpoints under shared limits, creating a powerful, flexible rate-limiting solution that scales easily:
Redis: Efficiently tracks request counts and resets automatically per
time_window_in_sec
.PostgreSQL: Holds rate limit settings per user and API alias, making configurations easily manageable.
Annotations: Streamline rate-limiting by mapping each API endpoint to an alias, allowing consistent configurations across the system.