Skip to content

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 an asyncio.Task that evicts expired entries every cleanup_interval seconds. 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 before set_cache_backend(), it raises:
Text Only
RuntimeError: No cache backend registered. Call set_cache_backend() first.

๐Ÿ’ก Tips

  • Per-repository toggling is independent of the lifecycle: CacheConfig(enabled=False) bypasses the cache for that repo even after initialize_cache().
  • The cleanup task uses cleanup_interval seconds for scans; entries also expire on access via the TTL heap (default_ttl=0 expires immediately).
  • Register the backend before constructing any CachedBaseRepository that references it โ€” otherwise a repo may hold an uninitialized backend (no cleanup task, no Redis pub/sub).