Backends¶
Where the cache and every other tool keep their entries and their state.
Every class and function: the API reference.
- InMemoryBackend(max_size=10_000): per process; for development, tests and single-process
apps. Up to
max_sizecached responses, and as many keys of state (counters, revoked tokens…) in a store of their own. - RedisBackend(redis): redis-py client, standalone or cluster, created without
decode_responses=True. Works with any server that speaks the Redis protocol (Valkey, KeyDB, Dragonfly). - ValkeyBackend(valkey): the same with valkey-py (
valkey.asyncio, not the sync client). - GlideBackend(client): Valkey GLIDE, the official Valkey client with a Rust core
(
await GlideClient.create(...)orGlideClusterClient). - TieredBackend(remote, local_ttl=5): a per-process memory level in front of any of these (see "Two-level cache").
- MemcachedBackend(client): aiomcache. Can't tell the remaining time to live.
- DynamoBackend(table_name, region, endpoint_url=None): aiobotocore;
await backend.init()first. Enable TTL on thettlattribute of the table.endpoint_urlfor DynamoDB Local or LocalStack.
| Backend | Clear a namespace | Locks | Tags | Rate limiter |
|---|---|---|---|---|
| InMemory | yes | (one process) | yes | (one process) |
| Redis, Valkey, GLIDE | yes (SCAN + UNLINK) |
yes | yes | yes, one Lua script |
| Memcached | no | no | no | yes, adding and taking back what doesn't fit |
| DynamoDB | no | yes (conditional writes) | no | yes, a conditional write |
| Tiered | like its shared backend | like its shared backend | like its shared backend | in its shared backend |
Valkey example with valkey-py:
import valkey.asyncio
from fastapi_alcazaba.backends.valkey import ValkeyBackend
client = valkey.asyncio.from_url("valkey://localhost:6379/0")
FastAPICache.init(ValkeyBackend(client), prefix="myapp", signing_key=settings.cache_signing_key)
Two-level cache¶
from fastapi_alcazaba.backends.tiered import TieredBackend
FastAPICache.init(TieredBackend(ValkeyBackend(client), local_ttl=5), prefix="myapp")
TieredBackend keeps a small copy of each entry in the memory of every process, in front of the
shared backend: hits served from there skip the network (131 µs → 56 µs per hit against a Valkey
on the same machine; more across a real network). A local copy lives at most local_ttl seconds
and never longer than the shared entry.
With Redis or Valkey through redis-py/valkey-py, writes, clears and tag invalidations are also
announced on a pub/sub channel (invalidation_channel), so every process drops its copy at once:
a tag invalidation names the keys it deleted, also the copies of processes that only read those
entries (beyond 1000 keys, every process drops all its copies). A value read while an
invalidation arrives isn't kept, since it may be the old one. Copies are only kept while the
process is subscribed: if the subscription drops, or the client reconnects by itself, the local
level is emptied, and bypassed until it's back. The channel is read with its own timeout, so a
client socket_timeout doesn't end the subscription, and pinged after 5 quiet seconds: a
connection that died silently is given up within 10. With the other backends (memcached,
DynamoDB, GLIDE, a valkey-py cluster) staleness is bounded by local_ttl. If the shared backend
goes down, the local copies keep being served until they expire.