1. Redis cache-aside pattern
Start with the contract: make inputs, outputs and failure behavior explicit before adding infrastructure. This keeps the feature easy to reason about and gives tests a stable boundary.
2. Safe invalidation and TTLs
Apply the pattern through a small vertical slice. Keep framework wiring at the edge and business decisions in focused functions or services that can be tested without starting the whole application.
async def get_task(task_id: int):
key = f"task:{task_id}"
if cached := await redis.get(key):
return TaskOut.model_validate_json(cached)
task = await repository.get(task_id)
await redis.set(key, TaskOut.model_validate(task).model_dump_json(), ex=60)
return task3. Query profiling, pools and background tasks
Treat failure paths as part of the design. Add bounded resource usage, meaningful errors and a verification step so the behavior remains dependable under real production conditions.
4. Hands-on Exercise
Build the feature
Cache task reads, invalidate on writes, then measure query count and response latency before and after the change.
Definition of done
- The happy path works through the real HTTP boundary.
- At least one failure path is handled and tested.
- Configuration and secrets stay outside source code.
- The README explains how to run and verify the result.
5. Knowledge Check
Why is cache invalidation usually performed after a successful database commit?
Show answer
Invalidating earlier can expose stale or inconsistent states if the transaction later rolls back.