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) andRedisCacheBackend(JSON‑serialised values, pub/sub invalidation). - Cache configuration via
CacheConfig(enabled,backend,default_ttl, …). CachedBaseRepositoryextendsBaseRepositoryand adds:- Transparent ID‑based caching on
get_by_id. - Automatic cache population on
create. - Cache invalidation on
update/delete. - Utility methods
warm_cache(ids)andinvalidate_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.
6️⃣ Related Docs
- Core Components – see
docs/02_components.mdfor theCacheBackendabstraction. - Best Practices – remember to call
ModelRegistry.initialize_cache()after DB connection.
Feel free to adapt the TTL, max entries, and backend to your workload.