Redis as Cache

Redis is an in-memory data store that excels as a cache due to sub-millisecond latency and rich data types.
Cache-Aside Pattern
Application checks cache first, falls back to database:
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 app and database, auto-loading on miss. Logic is in the cache layer, not the application.
Write-Through
Data written to cache first, then database:
def update_user(user_id, data):
cache_key = f"user:{user_id}"
redis.setex(cache_key, 3600, json.dumps(data))
db.execute("UPDATE users SET name = %s WHERE id = %s", [data['name'], user_id])
Write-Behind
Write to cache immediately, batch database writes asynchronously. Fastest writes but risk of data loss if cache fails.
Invalidation Strategies
| Strategy | Approach | Best For | |----------|----------|----------| | TTL | Auto-expire | Most cases | | Key deletion | Delete on update | Write-through | | Versioned | Include version in key | Schema changes | | Pub/sub | Notify all instances | Distributed caches |
Rate Limiting with Sorted Sets
def is_rate_limited(user_id, max_requests=100, window=60):
key = f"ratelimit:{user_id}"
now = time.time()
redis.zremrangebyscore(key, 0, now - window)
if redis.zcard(key) >= max_requests:
return True
redis.zadd(key, {now: now})
redis.expire(key, window)
return False
Conclusion
Use cache-aside as the default pattern. Always set TTLs to prevent memory exhaustion. Monitor cache hit rates. Implement mutex locking for stampede prevention. Pipeline batch operations for performance.
Enjoy this article? Share your thoughts, questions, or experiences in the comments below — your insights help other readers too.
Join the discussion ↓