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.2.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.2-py3-none-any.whl (19.3 kB view details)

Uploaded Python 3

File details

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

File metadata

File hashes

Hashes for distributed_rate_limiter-0.2.2.tar.gz
Algorithm Hash digest
SHA256 b42f037fc96e79628cc1276c883b4d4631c8f6e0a0926a2f2fc0038b48894695
MD5 9b49207e7de9fe627c23c1cd0383f297
BLAKE2b-256 952376b3a8e23ab7b3e52a3217b33a6f5aa8cc20c3d61343a0357355721fb780

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for distributed_rate_limiter-0.2.2-py3-none-any.whl
Algorithm Hash digest
SHA256 7377301ad8adb2314d3ee82afc56fa91c6833c62e4df3132a2bffb5e4a269a8f
MD5 4b6dd416b4e43fd00449abb8278650a5
BLAKE2b-256 0ed431562dcf8d870b084e3a0f54fd1d8e0bc8fcb0fa0b99584980b9d22ed637

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