Skip to main content

Recipes

A shared cache does more jobs in a real app than caching. It elects a worker, counts requests, holds sessions and one-time tokens, remembers what was invalidated, and stands in for Redis wherever Redis was only ever there to hold a counter. Each recipe below starts with the moment you’d reach for one, then shows the pattern, then says where it stops working. The examples with output under them execute against diskcache-oxide every time these docs are built, so they’re known to work, not merely believed to.

Caching

Caching expensive calls. The cache-aside pattern, and a small decorator that turns any function into a cached one, with keys built safely from its arguments.

Serving stale values while refreshing. Keep a slow page or report available while one worker rebuilds it in the background, or refresh the hot keys ahead of time from a job, so readers never wait and the thundering herd is one worker.

Caching responses from other services. Cache an upstream API’s answers, honor its max-age, revalidate with ETags, and keep serving the last good answer when it’s down.

Invalidating groups of keys. Clear everything cached for one user, one product or one page at once: delete by key prefix, or bump a generation number so stale entries are never read again.

Storing your own types. UUIDs, enums, timedeltas, Paths, dataclasses and Pydantic models in the cache with one registration each, and no pickle.

Coordinating workers

Running a job once across workers. Eight gunicorn workers each schedule the same hourly job, and you want it to run once. add() is atomic across processes, so exactly one of them claims it.

Rate limiting. Count requests per client in fixed time windows with add() and incr(), shared by every worker on the machine, with no Redis.

Counting things. View counts, download totals and usage quotas with incr() and decr(), flushed to the database with pop() so no count is lost or doubled.

Single-use keys. Reset links, verification codes and idempotency keys: pop() hands a value to exactly one caller, and add() accepts a key exactly once.

State

Server-side sessions. Sessions every worker can read, with a sliding idle timeout from touch(), an absolute lifetime, and log-out-everywhere by key prefix.

Deployment and testing

Sharing a cache across web workers. Where to put the cache directory, when to open it with gunicorn, uvicorn, FastAPI, Flask, Django and Celery, how to keep a busy cache from slowing requests, and what to do at shutdown.

Using the cache from asyncio. Read inline, write off the loop, and keep transactions on one thread, in FastAPI, Starlette or aiohttp, until AsyncCache lands.

Testing code that uses a cache. pytest fixtures for a real cache in a temporary directory, a NullCache for tests that shouldn’t cache at all, and a fake clock for testing expiry without sleeping.

Coming later

More recipes will arrive with the features they need: memoize with stampede protection, tags, cross-process locks and throttles, and AsyncCache for asyncio apps. FanoutCache and transact() are here already and have their own guide pages. See the roadmap.