Redis Caching: The Cache-Aside Pattern
Cache-aside is the most common Redis caching pattern: check the cache first, fall back to the database on a miss, store the result with a TTL, and invalidate it on writes. Here's how it works and how to implement it.
When to reach for a cache
If the same data is read far more often than it changes - a product page, a user profile, a config blob - and the query is expensive, a cache turns a 300ms database round-trip into a sub-millisecond Redis lookup. The standard pattern is cache-aside (a.k.a. lazy loading).
How cache-aside works
- Read: look in Redis first. Hit -> return it. Miss -> query the database, store the result in Redis with a TTL, return it.
- Write: update the database, then delete (invalidate) the cache key so the next read repopulates it.
Implementation (Python + Redis)
import json, redis
r = redis.Redis(host="localhost", port=6379, decode_responses=True)
CACHE_TTL = 30 # seconds
def get_product(product_id: int) -> dict:
key = f"product:{product_id}"
cached = r.get(key)
if cached: # cache hit
return json.loads(cached)
product = query_product_from_db(product_id) # cache miss -> DB
r.setex(key, CACHE_TTL, json.dumps(product)) # store with TTL
return product
def update_product(product_id: int, data: dict):
save_product_to_db(product_id, data)
r.delete(f"product:{product_id}") # invalidate so next read is fresh
The decisions that actually matter
- TTL is your safety net: even if an invalidation is missed, stale data self-heals when the key expires. Pick a TTL you can tolerate being stale for.
- Invalidate on write, don't try to update the cache in place - deleting the key is simpler and avoids races.
- Cache the miss too (a short TTL on "not found") if a missing key is hammered, to avoid a stampede on the database.
- Key naming:
entity:id(e.g.product:42) keeps keys predictable and easy to bust.
Cache-aside vs other patterns
- Read-through / write-through: the cache library talks to the DB for you - less code, less control.
- Write-behind: writes go to cache first, flushed to the DB async - fast, but risks data loss. Cache-aside is the safe default for most apps.
Want to try it hands-on? HeyDevJob gives you this exact setup in a live cloud workspace in your browser - edit it, run it, and see it work. Free, nothing to install.
Try it in a workspace →What you'll practice
- Implementing read-through caching with a TTL
- Invalidating cache keys correctly on writes
- Measuring the latency difference a cache makes on a hot path
FAQ
What is the cache-aside pattern?
The app checks Redis first; on a miss it reads the database, stores the result in Redis with a TTL, and returns it. On writes it deletes the key so the next read repopulates fresh data.
How do I keep a Redis cache from going stale?
Invalidate (delete) the key whenever the underlying data changes, and always set a TTL as a backstop so stale data self-heals even if an invalidation is missed.
Should I update the cache or delete it on a write?
Delete it. Re-deriving the value on the next read is simpler than updating in place and avoids race conditions between concurrent writers.
What are the main caching strategies?
There are five common ones. Cache-aside (lazy loading) - the app manages the cache, used here. Read-through - the cache loads from the DB itself on a miss. Write-through - writes go to cache and DB together. Write-behind (write-back) - writes hit the cache and flush to the DB later. Refresh-ahead - the cache refreshes hot keys before they expire. Cache-aside is the most common.
What is the difference between LRU and TTL?
TTL (time to live) expires a key after a set time, so stale data self-heals. LRU (least recently used) is an eviction policy - when memory is full, Redis drops the least-recently-used keys to make room. You typically use both: a TTL per key plus an LRU eviction policy (maxmemory-policy allkeys-lru).
Keep learning
Learn it by doing. Open this in a live cloud workspace, make the change yourself, and keep a record of the work you can share.
Open the workspace →