Skip to main content

cachefence

Cache-aside for Redis without the stampede.

When a hot cache key expires, naive cache-aside lets every concurrent request miss at the same instant and pile onto your database to rebuild the same value. That's a cache stampede (a.k.a. thundering herd), and it's one of the most common ways a cache makes things worse under load.

cachefence stops it:

500 concurrent requests hit a cold key (each DB query takes 50ms)

naive cache-aside      DB hits:  500
with cachefence        DB hits:    1

Same workload, one extra import: 500 database queries become 1.

Install

pip install cachefence

Requires Python 3.11+ and a Redis server (4.2+).

Usage

from redis.asyncio import Redis
from cachefence import CacheFence

redis = Redis()
cache = CacheFence(redis)

async def get_user(user_id: int) -> dict:
    return await cache.get_or_set(
        key=f"user:{user_id}",
        ttl=60,                                  # fresh for 60 seconds
        recompute=lambda: load_user_from_db(user_id),
    )

recompute can be sync or async. It runs at most once per refresh, no matter how many requests arrive together. Invalidate manually when the underlying data changes:

await cache.invalidate(f"user:{user_id}")

How it works

cachefence layers two mechanisms so a key almost never goes cold and a cold key is never rebuilt more than once:

  1. Probabilistic early refresh (XFetch). Each read rolls a weighted dice; as the key nears expiry, one lucky request is nudged to refresh it ahead of time while everyone else keeps serving the still-valid cached value. The weighting uses how long the last recompute took, so expensive keys refresh earlier. Based on Vattani, Chierichetti & Lowenstein, "Optimal Probabilistic Cache Stampede Prevention" (VLDB 2015).

  2. Distributed rebuild lock. On a true miss, workers race for a short-lived Redis lock. The winner rebuilds; the rest wait briefly and pick up the fresh value the moment it lands, with a bounded fallback so a crashed rebuilder never hangs requests forever.

The lock is released with a compare-and-delete (Lua when the server supports it, an optimistic WATCH/MULTI transaction otherwise) so a worker can never delete a lock it no longer owns.

Configuration

cache = CacheFence(
    redis,
    beta=1.0,          # XFetch aggressiveness; higher = refresh earlier
    lock_timeout=10.0, # seconds before a rebuild lock auto-expires
    wait_for_lock=5.0, # max seconds a waiter blocks before rebuilding itself
    namespace="app:",  # optional key prefix
)

Custom serialization (default is JSON):

import pickle
cache = CacheFence(redis, serializer=pickle.dumps, deserializer=pickle.loads)
# serializer returns bytes, deserializer takes bytes

A note on connection pools

Under a genuine burst (hundreds of simultaneous coroutines), the default redis-py pool can raise MaxConnectionsError because waiters don't block for a free connection. Use a blocking pool sized for your concurrency:

from redis.asyncio import BlockingConnectionPool, Redis

pool = BlockingConnectionPool(max_connections=30, timeout=15)
redis = Redis(connection_pool=pool)

Run the demo

git clone https://github.com/bourne44/cachefence
cd cachefence
pip install -e ".[test]"
python examples/stampede_demo.py

Development

pip install -e ".[test]"
pytest

The test suite includes a 100-way concurrent-miss test asserting the recompute runs exactly once — the core guarantee of the library.

License

MIT

Metadata

Release files for cachefence 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for cachefence 0.1.0
File Size Uploaded Provenance Actions
cachefence-0.1.0.tar.gz 9.1 kB Without provenance Details for cachefence-0.1.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for cachefence 0.1.0
File Interpreter ABI Platform Actions
cachefence-0.1.0-py3-none-any.whl Python 3 none any Details for cachefence-0.1.0-py3-none-any.whl

Total release size: 17.7 kB

Release files / cachefence-0.1.0.tar.gz

Download URL cachefence-0.1.0.tar.gz
Size 9.1 kB
Tags Source
SHA-256 checksum
How to use checksums
d1b741e63d5cf7eb5f2cb2a2412c9b5ac9dfe7877ac078f2f83c6dcd1cb35663
BLAKE2b-256 checksum
How to use checksums
f0dec7304401279926be479a586c47cb9eea2c11096378831a8c93379e3cb043
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.13.5

Release files / cachefence-0.1.0-py3-none-any.whl

Download URL cachefence-0.1.0-py3-none-any.whl
Size 8.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
1d1f61a99145fb63821de3657d3cc203ce3277caf08965333c6d8e16d1f70305
BLAKE2b-256 checksum
How to use checksums
19f1baf6e5928d26fc9e27c0be8893e3b1c1cb4c51ee1166bc76ded094e8f03f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.13.5

Security for cachefence 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page