Skip to main content

Redis-backed distributed rate limiter for Python

Project description

distributed-rate-limiter

A production-grade, Redis-backed, horizontally scalable Python rate limiting library.

Python 3.9+ License: MIT PyPI version

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

  1. Redis is the single source of truth: No in-memory state in Python processes
  2. Atomic operations: All state changes use Lua scripts
  3. Clock-skew resistant: Uses Redis TIME, not Python time
  4. Fail-fast: No retries, no backoff, clear failure modes
  5. Framework-agnostic: Core library has zero framework dependencies
  6. 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

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

distributed_rate_limiter-0.2.0.tar.gz (15.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

distributed_rate_limiter-0.2.0-py3-none-any.whl (19.3 kB view details)

Uploaded Python 3

File details

Details for the file distributed_rate_limiter-0.2.0.tar.gz.

File metadata

File hashes

Hashes for distributed_rate_limiter-0.2.0.tar.gz
Algorithm Hash digest
SHA256 0fe8597af59ec93ad9c4d621c5d548722e825bdb96c61483621d3319ed9ac4d7
MD5 7a7bd84afad13abbbf88b1d7f866098c
BLAKE2b-256 767bdd69297d7291a73a3646230246010d31d86f4f35b30a3d6f202ed452efcb

See more details on using hashes here.

File details

Details for the file distributed_rate_limiter-0.2.0-py3-none-any.whl.

File metadata

File hashes

Hashes for distributed_rate_limiter-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 94c87610cba6814ba8e42430467575a9a4be61af4779a1557aa733567f323fa0
MD5 c6a78f5ad07056810fb94fccbc2610aa
BLAKE2b-256 21063440443fca0af1d53c53ebfaf0031eacda23b81a1db199c892674edd0744

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page