Error Handling & Security¶
Error Handling¶
Django Smart Ratelimit provides robust error handling to ensure your application remains stable even if the rate limiting backend fails.
Handling Strategy¶
- Fail-Closed (Default): If the backend fails, requests are denied to protect your application.
- Fail-Open: Configure
RATELIMIT_FAIL_OPEN = Trueto allow requests when the backend is unavailable. - Circuit Breaker: Automatically detects backend failures and temporarily disables rate limiting to prevent cascading failures.
- Custom Handlers: Define custom exception handlers to return specific responses (e.g., JSON) when limits are exceeded.
Security: Fail-Open Mechanism¶
The library supports a "fail-open" security model. This means that if the rate limiting backend becomes unavailable (e.g., database connection failure, deadlock), the system can be configured to:
- Log the error: The failure is logged with full traceback for debugging.
- Allow the request: The request is allowed to proceed to avoid blocking legitimate traffic during infrastructure issues.
- Degrade gracefully: The application continues to function, albeit without rate limiting protection for that specific request.
This behavior is controlled via the RATELIMIT_FAIL_OPEN setting (default: False). Set it to True to enable fail-open behavior.
# settings.py
RATELIMIT_FAIL_OPEN = True # allow requests when the backend is unavailable
Custom Exception Handler¶
When a backend raises an error and fail-open is disabled, the request is routed
through an exception handler. By default this logs the error (with traceback)
and returns a 429 response. Override it with RATELIMIT_EXCEPTION_HANDLER, set
to the dotted import path of a callable.
The handler receives the request and the raised exception and must return an
HttpResponse:
# myapp/handlers.py
from django.http import JsonResponse
def ratelimit_exception_handler(request, exception):
return JsonResponse(
{"error": "rate limiting temporarily unavailable"},
status=503,
)
# settings.py
RATELIMIT_EXCEPTION_HANDLER = "myapp.handlers.ratelimit_exception_handler"
A separate RATELIMIT_RESPONSE_HANDLER (also a dotted path, or a template name)
customizes the response returned when a request is actually rate limited, as
opposed to when the backend errors.
Circuit Breaker Protection¶
Automatic failure detection and recovery for backend operations to ensure system reliability.
Circuit Breaker States¶
- CLOSED: Normal operation, requests pass through
- OPEN: Too many failures, requests fail fast (no backend calls)
- HALF_OPEN: Testing recovery with limited requests
Configuration¶
Tune the circuit breaker with RATELIMIT_CIRCUIT_BREAKER (a dict). The keys
below map to CircuitBreakerConfig; values shown are the defaults:
# settings.py
RATELIMIT_CIRCUIT_BREAKER = {
"failure_threshold": 5, # failures before opening the circuit
"recovery_timeout": 60, # seconds before attempting HALF_OPEN
"reset_timeout": 300, # seconds of success before clearing failures
"half_open_max_calls": 1, # probe calls allowed in HALF_OPEN
"exponential_backoff_multiplier": 2.0,
"exponential_backoff_max": 300, # cap on backoff (seconds)
}
State storage is selected separately:
# settings.py
RATELIMIT_CIRCUIT_BREAKER_STORAGE = "memory" # "memory" (default) or "redis"
RATELIMIT_CIRCUIT_BREAKER_REDIS_URL = "redis://localhost:6379/0"
When the storage is set to "redis" but no usable Redis URL/client is
available, the breaker logs a warning and falls back to in-memory state.
Known Limitations¶
- Circuit Breaker Persistence: With the default
"memory"storage the circuit breaker state (failure counts, open/closed status) is reset if the application process restarts, and in a multi-worker environment (e.g., Gunicorn/uWSGI) each worker maintains its own independent state. SetRATELIMIT_CIRCUIT_BREAKER_STORAGE = "redis"to share state across processes.
Multi-Backend Fallback¶
When you configure several backends, the multi-backend dispatcher decides how to
route around an unhealthy one. The strategy is set with
RATELIMIT_MULTI_BACKEND_STRATEGY:
"first_healthy"(default): try backends in order, using the first healthy one."round_robin": rotate across healthy backends.
# settings.py
RATELIMIT_MULTI_BACKEND_STRATEGY = "first_healthy"
Choosing an Algorithm Safely¶
Error-handling configuration is independent of the rate limiting algorithm, but
the algorithm choice is accepted as a raw string or, equivalently, the
type-safe Algorithm enum. Using the enum avoids typos that would otherwise
surface only at request time:
from django_smart_ratelimit.enums import Algorithm
# settings.py
RATELIMIT_ALGORITHM = Algorithm.SLIDING_WINDOW # same as "sliding_window"
Algorithm is a StrEnum, so its members are interchangeable with their string
values (Algorithm.SLIDING_WINDOW, Algorithm.FIXED_WINDOW,
Algorithm.TOKEN_BUCKET, Algorithm.LEAKY_BUCKET) anywhere an algorithm string
is accepted.