Database Caching
Why Cache?
Caching reduces database load and improves response times. A good caching strategy can reduce database queries by 90% or more.
Caching Strategies
Cache-Aside
Application checks cache first, loads from database on miss:
def get_user(user_id):
cache_key = f"user:{user_id}"
cached = redis.get(cache_key)
if cached:
return json.loads(cached)
user = db.query("SELECT * FROM users WHERE id = %s", [user_id])
if user:
redis.setex(cache_key, 3600, json.dumps(user))
return user
Read-Through
Cache sits between application and database. The cache itself loads from the database on miss.
Write-Through
Data is written to cache first, then to database. Ensures cache is always consistent.
Write-Behind
Data is written to cache and asynchronously batched to database. Fastest writes but risk of data loss.
Cache Invalidation
| Strategy | Description | Best For | |----------|-------------|----------| | TTL | Automatic expiry | Most cases | | Key deletion | Delete on update | Write-through | | Versioned keys | Include version | Schema changes |
Redis Integration
import redis
class CacheManager:
def __init__(self):
self.redis = redis.Redis(host='localhost', port=6379, decode_responses=True)
def get_or_compute(self, key, compute_func, ttl=3600):
cached = self.redis.get(key)
if cached:
return json.loads(cached)
value = compute_func()
self.redis.setex(key, ttl, json.dumps(value))
return value
Cache Stampede Prevention
When a popular key expires, many requests may try to recompute simultaneously:
def get_with_mutex(key, compute_func, ttl=3600):
value = redis.get(key)
if value:
return json.loads(value)
# Try to acquire lock
lock_key = f"lock:{key}"
if redis.setnx(lock_key, "1"):
redis.expire(lock_key, 10)
value = compute_func()
redis.setex(key, ttl, json.dumps(value))
redis.delete(lock_key)
return value
# Wait for the other thread
import time
time.sleep(0.1)
return json.loads(redis.get(key))
Conclusion
Use cache-aside as the default pattern. Set appropriate TTLs. Implement mutex locking for cache stampede prevention. Monitor cache hit rates. Always have a fallback when cache is unavailable.