Use Case 11: Proper Cache Lifecycle in FastAPI
Scenario: A microservice uses an in-memory (or Redis) cache backend and must start the cleanup task on app start, then shut it down cleanly on termination.
๐ฆ What's New?
| Component | Description |
|---|---|
ModelRegistry.set_cache_backend(backend) |
Registers the single shared backend. |
ModelRegistry.initialize_cache() |
Starts the backend (spawns the in-memory TTL cleanup task). Raises RuntimeError if no backend was registered. |
ModelRegistry.shutdown_cache() |
Cancels the cleanup task, closes pub/sub, clears the backend. |
| If a repo uses a backend directly | Same instance must be registered so initialize_cache starts its task. |
๐ Example
Python
from contextlib import asynccontextmanager
from fastapi import FastAPI
from mongo_ops import ModelRegistry, MongoConnectionManager
from mongo_ops.cache import InMemoryCacheBackend
cache = InMemoryCacheBackend(max_entries=10_000, default_ttl=300)
ModelRegistry.set_cache_backend(cache) # before any cache-backed repo is used
@asynccontextmanager
async def lifespan(_app: FastAPI):
async with MongoConnectionManager.lifespan(
uri="mongodb://localhost:27017", db_name="mydb"
):
await ModelRegistry.initialize_all() # create indexes (idempotent)
await ModelRegistry.initialize_cache() # start the TTL cleanup task
yield
await ModelRegistry.shutdown_cache() # cancel task + close cleanly
app = FastAPI(lifespan=lifespan)
๐ What Actually Happens
InMemoryCacheBackend.initialize()spawns anasyncio.Taskthat evicts expired entries everycleanup_intervalseconds. Without a shutdown, the event loop flags the dangling task on exit โshutdown_cache()cancels it and awaits it.shutdown_cache()also NULs the registry cache backend and closes the Redis pub/sub handle (if Redis).- If
initialize_cache()is called beforeset_cache_backend(), it raises:
๐ก Tips
- Per-repository toggling is independent of the lifecycle:
CacheConfig(enabled=False)bypasses the cache for that repo even afterinitialize_cache(). - The cleanup task uses
cleanup_intervalseconds for scans; entries also expire on access via the TTL heap (default_ttl=0expires immediately). - Register the backend before constructing any
CachedBaseRepositorythat references it โ otherwise a repo may hold an uninitialized backend (no cleanup task, no Redis pub/sub).