All articles
PythonPerformanceAPI

Rate Limiting Without Redis: A Local Token Bucket in Python

PyForge Team 6 min read2026-10-14

Rate limiting tutorials start with Redis because that is what planet-scale systems use. But a single-process tool calling a third-party API has a much simpler problem: do not exceed N requests per window. You can solve that in-process, for free.

The token bucket in twenty lines

A bucket holds tokens that refill at a fixed rate. Every request takes a token; when the bucket is empty you wait or reject. Bursts are absorbed up to capacity, the average rate stays capped — exactly the behavior APIs expect.

python
import time

class TokenBucket:
    def __init__(self, rate: float, capacity: int):
        self.rate, self.capacity = rate, capacity
        self.tokens = capacity
        self.updated = time.monotonic()

    def allow(self) -> bool:
        now = time.monotonic()
        self.tokens = min(
            self.capacity,
            self.tokens + (now - self.updated) * self.rate,
        )
        self.updated = now
        if self.tokens >= 1:
            self.tokens -= 1
            return True
        return False

Persisting state across restarts

An in-memory bucket forgets everything when the process restarts — and APIs count your usage regardless. Snapshot the counters to SQLite every few seconds and reload on boot: the limiter survives deployments without an external service.

python
def save(conn, key, tokens, updated):
    conn.execute(
        "INSERT OR REPLACE INTO buckets VALUES (?, ?, ?)",
        (key, tokens, updated),
    )
    conn.commit()

Concurrency gotchas

Guard the bucket with a lock if threads share it, and remember the limit is per process: two replicas each get their own budget. If you truly run a fleet, then — and only then — graduate to a shared store.

Simple, local, persisted. That is the whole pattern — and it is exactly what our RateShield asset packages with tests and configuration already done.