Skip to main content

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.

Python 3.9+

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.1.1.tar.gz (13.7 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.1.1-py3-none-any.whl (16.6 kB view details)

Uploaded Python 3

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

Hashes for distributed_rate_limiter-0.1.1.tar.gz
Algorithm Hash digest
SHA256 669beef609a0c7dbc0fcdf24961a28b937b8242cb3b0e4fb32cc830a19c69ea4
MD5 bb665de1e7e6aa64f4d1e26771e6097e
BLAKE2b-256 bda48052d8db01b21cd459c892026c8cd62ee492c840abb8e07f98f026a315cf

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for distributed_rate_limiter-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 b7c8c0845b32e154942fd7f3d60908dfcfae650d4b703ee24cfcaf4dca989d17
MD5 18ba8e14cd7478eb2eba7a8219cb65e5
BLAKE2b-256 0ea393e40bee9c5ed26ead81180e46f4133787c062da7f7c5ceff7248297c0c5

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