Skip to content

Use Case 7: Caching for High‑Performance Reads

Scenario: A read‑heavy API (e.g., product catalog) benefits from an in‑memory cache or Redis cache to reduce latency and DB load.


1️⃣ What’s New?

  • Cache back‑ends: InMemoryCacheBackend (TTL + LRU) and RedisCacheBackend (JSON‑serialised values, pub/sub invalidation).
  • Cache configuration via CacheConfig (enabled, backend, default_ttl, …).
  • CachedBaseRepository extends BaseRepository and adds:
  • Transparent ID‑based caching on get_by_id.
  • Automatic cache population on create.
  • Cache invalidation on update/delete.
  • Utility methods warm_cache(ids) and invalidate_cache(id).

2️⃣ Quick Start

Python
from mongo_ops import (
    BaseDocument,
    CachedBaseRepository,
    MongoConnectionManager,
    ModelRegistry,
    CacheConfig,
)
from mongo_ops.cache import InMemoryCacheBackend
from bson import ObjectId

# Define a model
class Product(BaseDocument):
    name: str
    price: float

# Initialise cache backend (in‑memory example)
cache = InMemoryCacheBackend(max_entries=10_000, default_ttl=300)
# Register the cache so the registry can initialise it later
ModelRegistry.set_cache_backend(cache)

# Repository with caching
class ProductRepo(CachedBaseRepository[Product]):
    def __init__(self):
        super().__init__(
            collection_name="products",
            model=Product,
            cache_backend=cache,
            config=CacheConfig(enabled=True, backend="memory")
        )

# FastAPI lifespan – initialise DB and cache
async def lifespan(app):
    async with MongoConnectionManager.lifespan(
        uri="mongodb://localhost:27017", db_name="shop"
    ):
        await ModelRegistry.initialize_all()
        await ModelRegistry.initialize_cache()  # Starts background cleanup, etc.
        yield

3️⃣ Using the Repository

Python
repo = ProductRepo()
# Create – automatically caches the new document
product = await repo.create(Product(name="Widget", price=9.99))

# Normal read – will hit the cache after the first DB fetch
fetched = await repo.get_by_id(product.id)

# Update – cache entry is refreshed
await repo.update(product.id, {"price": 8.99})

# Delete – cache entry removed
await repo.delete(product.id)

# Warm a set of IDs in advance (e.g., during a bulk load)
await repo.warm_cache([ObjectId("..."), ObjectId("...")])

4️⃣ Redis Backend (optional)

If you prefer a distributed cache, swap the backend:

Python
from mongo_ops.cache import RedisCacheBackend
from redis.asyncio import Redis

redis_client = Redis(host="localhost", port=6379)
redis_backend = RedisCacheBackend(redis_client, key_prefix="prod:")
ModelRegistry.set_cache_backend(redis_backend)

The repository code stays the same – just pass the redis_backend instance to CachedBaseRepository.


5️⃣ When to Use Caching

  • Frequently accessed documents (e.g., product details, configuration settings).
  • Low‑write‑to‑read ratios where cache invalidation cost is acceptable.
  • Distributed deployments where a shared Redis cache syncs invalidations via pub/sub.

  • Core Components – see docs/02_components.md for the CacheBackend abstraction.
  • Best Practices – remember to call ModelRegistry.initialize_cache() after DB connection.

Feel free to adapt the TTL, max entries, and backend to your workload.