Redis Cache Design Patterns
Caching is one of the most effective performance boosters, but wrong patterns without failure handling introduce inconsistency and instability.
Cache-Aside Pattern
On read, check cache first; on miss, query DB and backfill. On write, update DB then delete cache (delete preferred over update to avoid concurrent dirty reads). Simple but must handle stampede, penetration, avalanche.
import redis, json, hashlib, random
from functools import wraps
from typing import Callable, Any
r = redis.Redis(host="localhost", port=6379, db=0, decode_responses=True)
def cache_aside(ttl: int = 300, jitter: float = 0.1):
def decorator(fn: Callable):
@wraps(fn)
def wrapper(*args, **kwargs):
k = f"c:{fn.__name__}:{hashlib.md5(str((args,sorted(kwargs.items()))).encode()).hexdigest()[:10]}"
cached = r.get(k)
if cached == "__NULL__": return None
if cached is not None: return json.loads(cached)
result = fn(*args, **kwargs)
t = int(ttl * (1 + random.uniform(-jitter, jitter)))
if result is None: r.setex(k, max(30, t//5), "__NULL__")
else: r.setex(k, max(1, t), json.dumps(result, default=str))
return result
return wrapper
return decorator
def write_through(key: str, value: Any, db_update: Callable, ttl: int = 300):
pipe = r.pipeline()
pipe.setex(key, ttl, json.dumps(value, default=str))
try:
db_update(value)
pipe.execute()
except Exception:
pipe.reset(); raise
bg_queue: list[tuple[str, Any]] = []
def write_behind(key: str, value: Any):
bg_queue.append((key, value))
r.setex(key, 60, json.dumps(value, default=str))
def flush_bg(db_update: Callable):
while bg_queue:
k, v = bg_queue.pop(0)
try: db_update(k, v)
except Exception: bg_queue.insert(0, (k, v)); break
@cache_aside(ttl=600)
def get_product(pid: int):
return {"id": pid, "name": f"Product {pid}", "price": 99.99}
Write-Through vs Write-Behind
Write-Through updates cache and DB synchronously with strong consistency but higher latency. Write-Behind writes cache first then flushes asynchronously in batches for maximum throughput but risks short data-loss windows.
| Pattern | Consistency | Read Perf | Write Perf | Best For |
|---|---|---|---|---|
| Cache-Aside | Eventual | High | Medium | Read-heavy |
| Write-Through | Strong | High | Low latency | Strict consistency |
| Write-Behind | Weak/Eventual | High | Very High | Logging/stats write-heavy |
| Refresh-Ahead | Eventual | Very High | Medium | Predictable hot keys |
Best Practices
Add +-10% random jitter to TTL against avalanches. Use mutex/logical expiration for hot keys against stampedes. Cache null values for non-existent keys against penetration.