As Phil Karlton famously declared: "There are only two hard things in Computer Science: cache invalidation and naming things."
Introducing a fast in-memory cache like Redis can drop database read latency from 40ms to 0.5ms. But the moment you introduce caching, you create a dual-state system: the primary database holds persistent truth, while the cache holds an ephemeral copy.
When data changes, how do you ensure the cache doesn't serve stale, corrupt data to your users?
In this guide, we analyze the three primary cache architectures: Cache-Aside, Write-Through, and Write-Behind (Write-Back).
Strategy Comparison Matrix
| Strategy | Read Path | Write Path | Data Staleness Risk | Write Latency |
|---|---|---|---|---|
| Cache-Aside (Lazy Loading) | App checks Cache; on miss, reads DB & populates Cache. | App writes to DB, then invalidates Cache key. | Low (Eventual consistency) | Low (Only writes to DB) |
| Write-Through | App reads from Cache. | App writes to Cache, which synchronously writes to DB. | Zero (Always consistent) | High (Blocks on both writes) |
| Write-Behind (Write-Back) | App reads from Cache. | App writes to Cache; Cache asynchronously flushes to DB. | High risk of data loss on crash | Ultra-Low (Near-instant write) |
1. Cache-Aside (The Production Standard)
In 90% of web backends, Cache-Aside is the most practical choice.
Read Path:
App ---> [Check Redis] --(Hit)--> Return Data
| (Miss)
v
[Query Database] ---> [Populate Redis (with TTL)] ---> Return Data
Write Path:
App ---> [Write to Database] ---> [DELETE Key from Redis]
Crucial Nuance: DELETE vs. UPDATE
When updating a user in the database, should you update the Redis cache key or delete it?
Always DELETE the cache key!
If you try to update the cache directly, concurrent writes can cause a race condition where an older write overwrites a newer update in Redis:
Thread 1: Writes "Bob" to DB
Thread 2: Writes "Robert" to DB
Thread 2: Updates Redis with "Robert"
Thread 1: Updates Redis with "Bob" (LATE!) -> Cache now holds stale data permanently!
By deleting the key, the next read will simply fetch the true, finalized database record on demand.
Python Cache-Aside Implementation
import json
import redis
from sqlalchemy.orm import Session
r = redis.Redis(host="localhost", port=6379, decode_responses=True)
CACHE_TTL = 3600 # 1 hour
def get_user_profile(db: Session, user_id: str) -> dict:
cache_key = f"user:{user_id}"
cached = r.get(cache_key)
if cached:
return json.loads(cached)
user = db.query(User).filter_by(id=user_id).first()
if not user:
r.set(cache_key, json.dumps(None), ex=60)
return None
user_dict = {"id": user.id, "name": user.name, "email": user.email}
r.set(cache_key, json.dumps(user_dict), ex=CACHE_TTL)
return user_dict
def update_user_profile(db: Session, user_id: str, new_name: str):
cache_key = f"user:{user_id}"
user = db.query(User).filter_by(id=user_id).first()
user.name = new_name
db.commit()
r.delete(cache_key)

Top comments (0)