Caching Strategies
TL;DR
Caching = Store frequently accessed data in fast storage (RAM) to avoid slow database queries. Cache is the #1 way to improve performance - can make system 10-100x faster.
Core Concepts
Cache Hit vs Miss
- Cache hit: Data in cache (fast, ~1ms)
- Cache miss: Query database (~100ms), then store in cache
- Performance gain: 100x faster (1ms vs 100ms)
Caching Strategies
Cache-Aside (Lazy Loading) - Most Common
def get_user(user_id):
# 1. Try cache first
cached_user = cache.get(f"user:{user_id}")
if cached_user:
return cached_user # Cache hit
# 2. Cache miss - query database
user = db.query("SELECT * FROM users WHERE id = ?", user_id)
# 3. Store in cache (TTL = 5 minutes)
cache.set(f"user:{user_id}", user, ttl=300)
return user
Write-Through Cache
Write to cache and database simultaneously.
- ✅ Cache always consistent with DB
- ❌ Slower writes (two operations)
Write-Behind (Write-Back)
Write to cache immediately, async write to database.
- ✅ Fast writes
- ❌ Risk of data loss if cache crashes
Cache Eviction Policies
| Policy | Evict | Use Case |
|---|---|---|
| LRU | Least recently used | General purpose (most common) |
| LFU | Least frequently used | Hot data stays (trending videos) |
| TTL | Expired items | Guaranteed freshness |
Cache Stampede Solutions
# Locking solution
def get_cached(key):
data = cache.get(key)
if data:
return data
if cache.set_nx(f"lock:{key}", 1, ttl=10): # Acquire lock
data = db.query(key)
cache.set(key, data, ttl=300)
cache.delete(f"lock:{key}")
return data
else:
time.sleep(0.1)
return get_cached(key) # Retry
TTL Guidelines
| Data Type | TTL |
|---|---|
| Immutable | No TTL (forever) |
| Static content | 1 year |
| User profiles | 1 hour |
| Product prices | 5 minutes |
| Real-time data | 10 seconds or don't cache |
Quick Reference
Strategies: Cache-aside (lazy), Write-through (sync), Write-behind (async), TTL (expiry)
Eviction: LRU (default), LFU (hot data), TTL (freshness)
Layers: Browser → CDN → App (in-memory) → Redis → Database