Redis-backed, horizontally scalable rate limiting for distributed systems
Project description
distributed-rate-limiter
A production-grade, Redis-backed, horizontally scalable Python rate limiting library.
Features
- Library-first: Self-hosted, open-source, no SaaS dependencies
- Framework-agnostic: Works with FastAPI, Flask, or any Python application
- Horizontally scalable: Stateless design with Redis as the single source of truth
- Atomic operations: Lua scripts ensure correctness under concurrency
- Sync + Async: Native support for both synchronous and asynchronous code
- Security-first: Hashed identities, no PII storage, configurable failure modes
- Zero dependencies: Core library only requires Redis
Installation
pip install distributed-rate-limiter
Optional dependencies
# For async support
pip install distributed-rate-limiter[async]
# For FastAPI integration
pip install distributed-rate-limiter[fastapi]
# For Flask integration
pip install distributed-rate-limiter[flask]
# Install all extras
pip install distributed-rate-limiter[all]
Quick Start
Basic Usage
from distributed_rate_limiter import RateLimiter
# Initialize the rate limiter
limiter = RateLimiter(
redis_url="redis://localhost:6379",
rate=100, # 100 requests
period=60, # per 60 seconds
namespace="myapp"
)
# Check if request is allowed
identity = "user:12345" # or IP address, API key, etc.
result = limiter.allow(identity)
if result.allowed:
print(f"Request allowed. Remaining: {result.remaining}")
else:
print(f"Rate limited. Retry after: {result.reset_timestamp}")
Async Usage
from distributed_rate_limiter import AsyncRateLimiter
limiter = AsyncRateLimiter(
redis_url="redis://localhost:6379",
rate=100,
period=60
)
result = await limiter.allow_async("user:12345")
FastAPI Integration
from fastapi import FastAPI, Request, HTTPException
from distributed_rate_limiter.middleware import FastAPIRateLimitMiddleware
app = FastAPI()
app.add_middleware(
FastAPIRateLimitMiddleware,
redis_url="redis://localhost:6379",
rate=100,
period=60,
identity_extractor=lambda request: request.client.host
)
@app.get("/api/data")
async def get_data():
return {"message": "Hello, World!"}
Flask Integration
from flask import Flask
from distributed_rate_limiter.decorators import rate_limit
app = Flask(__name__)
@app.route("/api/data")
@rate_limit(
redis_url="redis://localhost:6379",
rate=100,
period=60,
identity_extractor=lambda: request.remote_addr
)
def get_data():
return {"message": "Hello, World!"}
Algorithms
Token Bucket (Default)
The Token Bucket algorithm provides smooth rate limiting with burst control.
limiter = RateLimiter(
redis_url="redis://localhost:6379",
algorithm="token_bucket", # default
rate=100,
period=60,
burst=20 # allow bursts up to 20 requests
)
Characteristics:
- O(1) Redis operations
- O(1) memory per identity
- Smooth token refill
- Configurable burst capacity
- Clock-skew resistant (uses Redis TIME)
Sliding Window (Coming Soon)
A sliding window algorithm will be available in a future release.
Configuration
Redis Connection
# Basic connection
limiter = RateLimiter(redis_url="redis://localhost:6379")
# With password
limiter = RateLimiter(redis_url="redis://:password@localhost:6379")
# Redis Cluster
limiter = RateLimiter(redis_url="redis://localhost:6379", cluster_mode=True)
# Custom Redis client
import redis
client = redis.Redis(host="localhost", port=6379, db=0)
limiter = RateLimiter(redis_client=client)
Failure Modes
Configure how the limiter behaves when Redis is unavailable:
# Fail open: allow traffic when Redis is down (default)
limiter = RateLimiter(
redis_url="redis://localhost:6379",
fail_strategy="open"
)
# Fail closed: block traffic when Redis is down
limiter = RateLimiter(
redis_url="redis://localhost:6379",
fail_strategy="closed"
)
Namespacing
Use namespaces to isolate rate limits for different services or endpoints:
api_limiter = RateLimiter(
redis_url="redis://localhost:6379",
namespace="api"
)
auth_limiter = RateLimiter(
redis_url="redis://localhost:6379",
namespace="auth",
rate=10,
period=60
)
Observability
Add hooks to monitor rate limiting behavior:
def on_allow(identity: str, info: dict):
print(f"Allowed: {identity}, remaining: {info['remaining']}")
def on_block(identity: str, info: dict):
print(f"Blocked: {identity}, reset at: {info['reset_timestamp']}")
def on_error(exception: Exception):
print(f"Error: {exception}")
limiter = RateLimiter(
redis_url="redis://localhost:6379",
on_allow=on_allow,
on_block=on_block,
on_error=on_error
)
Security
Identity Hashing
All identities are SHA-256 hashed before being stored in Redis:
# Raw identity never stored in Redis
identity = "user@example.com" # Hashed to SHA-256
result = limiter.allow(identity)
No PII Storage
- Identities are hashed with SHA-256
- No personally identifiable information stored in Redis
- No request body inspection
- No secrets logged or stored
Redis Key Format
<namespace>:<algorithm>:<sha256(identity)>
Example:
myapp:token_bucket:a7b3c9d1e2f4...
Performance
- O(1) Redis operations per request
- O(1) memory per unique identity
- Atomic Lua scripts prevent race conditions
- Auto-expiring keys minimize memory usage
- Horizontally scalable with stateless Python processes
Architecture
┌─────────────────┐
│ Your App │
│ (Sync/Async) │
└────────┬────────┘
│
▼
┌─────────────────┐
│ RateLimiter │
│ Public API │
└────────┬────────┘
│
▼
┌─────────────────┐
│ Algorithm │
│ (Token Bucket) │
└────────┬────────┘
│
▼
┌─────────────────┐
│ Redis Backend │
│ (Lua Scripts) │
└────────┬────────┘
│
▼
┌─────────────────┐
│ Redis Server │
│ (Single Truth) │
└─────────────────┘
Design Principles
- Redis is the single source of truth: No in-memory state in Python processes
- Atomic operations: All state changes use Lua scripts
- Clock-skew resistant: Uses Redis TIME, not Python time
- Fail-fast: No retries, no backoff, clear failure modes
- Framework-agnostic: Core library has zero framework dependencies
- API stability: Backward compatibility is a priority
Testing
# Install development dependencies
pip install -e ".[dev]"
# Run tests
pytest
# Run tests with coverage
pytest --cov=distributed_rate_limiter
# Run tests against Redis
docker run -d -p 6379:6379 redis:7-alpine
pytest
Requirements
- Python 3.9+
- Redis 5.0+ (self-hosted)
- redis-py 4.0+
Contributing
Contributions are welcome! Please read our Contributing Guide for details.
Development Setup
git clone https://github.com/yourusername/distributed-rate-limiter.git
cd distributed-rate-limiter
pip install -e ".[dev]"
License
MIT License - see LICENSE file for details.
Roadmap
- Token Bucket algorithm
- Sync and async support
- FastAPI middleware
- Flask decorators
- Sliding Window algorithm
- Prometheus metrics integration
- OpenTelemetry tracing support
- Rate limit headers (RateLimit-* RFC)
FAQ
Why Redis only?
Redis provides atomic operations via Lua scripts, which is essential for correctness under horizontal scaling. We intentionally keep the backend simple and reliable.
Why no in-memory fallback?
In-memory state breaks horizontal scaling correctness. When Redis is unavailable, you should either fail open (allow traffic) or fail closed (block traffic), not fall back to incorrect rate limiting.
Can I use this with Redis Cluster?
Yes, but be aware that Lua scripts must operate on a single node. Use consistent hashing or the {hash_tag} feature to ensure related keys are on the same node.
Does this support distributed tracing?
Not built-in, but you can add tracing via the observability hooks (on_allow, on_block, on_error).
How do I handle multi-region deployments?
Deploy separate Redis instances per region and configure your rate limiter accordingly. This library does not handle cross-region synchronization.
Support
- Issues: GitHub Issues
- Discussions: GitHub Discussions
- Security: Report security issues to work.ayushkumardubey@gmail.com
Acknowledgments
Built with inspiration from:
- Redis INCR-based rate limiting patterns
- Token bucket algorithm implementations
- Production rate limiting experiences
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file distributed_rate_limiter-0.1.1.tar.gz.
File metadata
- Download URL: distributed_rate_limiter-0.1.1.tar.gz
- Upload date:
- Size: 13.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.8.10
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
669beef609a0c7dbc0fcdf24961a28b937b8242cb3b0e4fb32cc830a19c69ea4
|
|
| MD5 |
bb665de1e7e6aa64f4d1e26771e6097e
|
|
| BLAKE2b-256 |
bda48052d8db01b21cd459c892026c8cd62ee492c840abb8e07f98f026a315cf
|
File details
Details for the file distributed_rate_limiter-0.1.1-py3-none-any.whl.
File metadata
- Download URL: distributed_rate_limiter-0.1.1-py3-none-any.whl
- Upload date:
- Size: 16.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.8.10
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b7c8c0845b32e154942fd7f3d60908dfcfae650d4b703ee24cfcaf4dca989d17
|
|
| MD5 |
18ba8e14cd7478eb2eba7a8219cb65e5
|
|
| BLAKE2b-256 |
0ea393e40bee9c5ed26ead81180e46f4133787c062da7f7c5ceff7248297c0c5
|