Rate Limiter
Overview
The Rate Limiter is a resilience pattern used to control the frequency of operations, such as API requests or function calls, over a period of time. It is essential for:
- Protecting downstream services from being overwhelmed.
- Preventing resource starvation and ensuring fair usage.
- Enforcing API usage quotas for different clients or user tiers.
The Athomic rate limiter is a flexible, policy-based system that allows you to declaratively apply rate limits to any asynchronous function using the @rate_limited decorator.
Key Features
- Policy-Based: Define named, reusable rate limit policies (e.g.,
"100/hour","10/second") in your configuration. - Multiple Backends & Strategies: Supports multiple provider backends, including a highly flexible one based on the
limitslibrary that allows for different strategies (e.g.,fixed-window,moving-window). - Context-Aware: Automatically generates unique keys based on the current context, enabling per-user or per-tenant rate limiting.
- Live Configuration: Rate limit policies can be adjusted at runtime without restarting the application, allowing you to respond to traffic spikes dynamically.
- Adaptive Throttling: Can integrate with an advanced Adaptive Throttling engine to automatically adjust limits based on real-time system health metrics.
How It Works
- Decorator: You apply the
@rate_limited(policy="...")decorator to anasyncfunction. RateLimiterService: When the function is called, the decorator invokes the centralRateLimiterService.- Key Generation: The service uses the
ContextKeyGeneratorto create a unique key for the operation based on the policy name, function name, and the current execution context (liketenant_idoruser_id). - Provider Check: The service then asks the configured provider (e.g., the
limitsprovider) if a request for that key is allowed under the specified rate limit string. If the request is denied, aRateLimitExceededexception is raised.
Available Providers
limitsProvider: The default and most flexible provider. It is an adapter for the powerfullimitslibrary and can be configured to use different storage backends (in-memory for testing, Redis for distributed state) and different algorithms (fixed-window, moving-window).redisProvider: A lightweight, custom implementation that uses native Redis commands to enforce a simple fixed-window algorithm. It reuses the application's mainKVStoreclient.
Usage Example
Applying a rate limit to a function is as simple as adding the decorator.
from nala.athomic.resilience.rate_limiter import rate_limited, RateLimitExceeded
@rate_limited(policy="api_heavy_usage")
async def generate_large_report(user_id: str):
# This function can now only be called according to the
# "api_heavy_usage" policy defined in the settings.
# ...
pass
async def handle_request(user_id: str):
try:
await generate_large_report(user_id)
except RateLimitExceeded:
# Handle the case where the user has exceeded their quota
print("Too many requests, please try again later.")
Configuration
You define your rate limiting backend and policies in settings.toml under the [resilience.rate_limiter] section.
[default.resilience.rate_limiter]
enabled = true
backend = "limits" # Use the 'limits' library provider
# --- Default settings for the 'limits' provider ---
[default.resilience.rate_limiter.provider]
backend = "limits"
# Use Redis for distributed state
storage_backend = "redis"
redis_storage_uri = "redis://localhost:6379/4"
# Use a more accurate strategy
strategy = "moving-window"
# --- Policy Definitions ---
# A default limit to apply if no specific policy is requested
default_policy_limit = "1000/hour"
# A dictionary of named, reusable policies
[default.resilience.rate_limiter.policies]
api_light_usage = "100/minute"
api_heavy_usage = "10/minute"
login_attempts = "5/hour"
Live Configuration
Because RateLimiterSettings is a LiveConfigModel, you can change the limit strings for any policy in your live configuration source (e.g., Consul) and the changes will take effect immediately without a restart. This is extremely useful for mitigating traffic spikes or abuse in real-time.
API Reference
nala.athomic.resilience.rate_limiter.decorators.rate_limited
rate_limited(policy)
Decorator that applies the Rate Limiting pattern to an asynchronous function.
It delegates the entire enforcement logic to the central RateLimiterService.
If the limit is exceeded, it raises a specific exception instead of executing
the decorated function.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
policy
|
str
|
The name of the rate limit policy to apply (e.g., 'default', 'heavy_users'), as defined in the configuration. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
Callable |
Callable[..., Awaitable[Any]]
|
The decorator function. |
Raises:
| Type | Description |
|---|---|
RateLimitExceeded
|
If the request is blocked by the rate limiter. |
nala.athomic.resilience.rate_limiter.service.RateLimiterService
Bases: BaseService
Orchestrates rate limiting logic, integrating provider, configuration, key generation, and detailed observability.
__init__(settings=None, provider=None)
Initializes the RateLimiterService.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
provider
|
Optional[RateLimiterProtocol]
|
Allows injecting a specific provider, primarily for testing. If None, the default provider is created via the factory. |
None
|
check(policy, *key_parts)
async
Performs a rate limit check with integrated, detailed observability.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
policy
|
str
|
The name of the rate limit policy to apply. |
required |
*key_parts
|
str
|
Logical parts of the key (e.g., function name, resource ID). |
()
|
Returns:
| Type | Description |
|---|---|
bool
|
True if the request is allowed, False if it is blocked. |
nala.athomic.resilience.rate_limiter.protocol.RateLimiterProtocol
Bases: Protocol
Defines the interface for a context-aware rate limiter. All implementations must respect the allow/clear/reset contract.
allow(key, rate, policy=None)
async
Checks if the given key is allowed under the specified rate limit and consumes a token if allowed.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
str
|
The identifier for the entity being rate limited (e.g., user ID, IP). |
required |
rate
|
str
|
The rate limit string (e.g., "5/second", "100/minute"). |
required |
policy
|
Optional[str]
|
An optional policy identifier to apply specific rate limits. |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
bool |
bool
|
True if the request is allowed, False if the rate limit has been exceeded. |
clear(key, rate)
async
Resets the rate limit counters specifically for the given key and rate limit rule.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
str
|
The identifier whose rate limit counters to reset. |
required |
rate
|
str
|
The rate limit string rule associated with the key to clear (needed to identify the correct counter(s)). |
required |
get_current_usage(key, rate)
async
Optional: Returns the current usage count for the given key under a specific rate. May not be supported by all implementations or strategies.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
str
|
The identifier to query. |
required |
rate
|
str
|
The rate limit string rule to check usage against. |
required |
Returns:
| Type | Description |
|---|---|
Optional[int]
|
Optional[int]: Number of requests used in the window, if supported. |
reset()
async
Resets all rate limit counters managed by this storage instance. WARNING: Use with extreme caution, especially with shared storage like Redis, as this will affect all keys managed by this limiter instance.
nala.athomic.resilience.rate_limiter.exceptions.RateLimitExceeded
Bases: Exception