Why Cache with Redis
Redis stores data in RAM rather than on disk, so reads and writes complete in sub-millisecond time compared to the tens or hundreds of milliseconds a relational database or a downstream REST API might take under load. Caching means placing a copy of frequently requested data in Redis so repeat requests are served from memory instead of re-computing or re-fetching the same result from a slower system of record.
Cricket analogy: Think of Virat Kohli's career stats sitting on a broadcaster's in-studio graphics tablet during a live match instead of an analyst re-querying a decade of scorecards every time the commentator asks for his average against pace bowling.
Cache-Aside (Lazy Loading)
In the cache-aside pattern, the application code is responsible for checking Redis first with a GET; on a cache hit it returns the value immediately, and on a cache miss it queries the primary database, then populates Redis with a SET (usually with a TTL) before returning the result to the caller. This is the most widely used pattern because it only caches data that is actually requested, keeping memory usage proportional to real traffic, but it does mean the first request after a miss pays the full database latency.
Cricket analogy: It resembles a stadium announcer who checks a whiteboard for a batter's strike rate first; if it's not written up yet, he asks the scorers, gets the number, and then chalks it onto the board for the next time someone asks.
import redis, json, time
r = redis.Redis(host="localhost", port=6379, decode_responses=True)
def get_user_profile(user_id: str) -> dict:
cache_key = f"user:profile:{user_id}"
cached = r.get(cache_key)
if cached is not None:
return json.loads(cached) # cache hit
# cache miss: fall back to the database
profile = db.query_user_profile(user_id)
r.set(cache_key, json.dumps(profile), ex=300) # TTL = 5 minutes
return profile
Write-Through Caching
With write-through caching, every write from the application goes to Redis first, and Redis (or the application layer wrapping it) synchronously propagates that same write to the backing database before acknowledging success. This guarantees the cache is never stale relative to the database because the two are updated in the same logical operation, but it adds the database's write latency to every single write path, which can hurt throughput on write-heavy workloads like order processing.
Cricket analogy: It is like a scorer updating both the giant stadium scoreboard and the official BCCI digital scorecard in the same motion the instant a boundary is hit, so neither ever shows a different total than the other.
Write-Behind (Write-Back) Caching
Write-behind caching flips the write-through trade-off: the application writes only to Redis and returns immediately, while a background process (often reading from a Redis Stream or a queue like Kafka) asynchronously batches and flushes those writes to the database moments later. This gives very low write latency and lets many writes to the same key be coalesced into a single database update, but it introduces a durability gap where a Redis crash before the flush means the write is lost unless Redis persistence is also enabled.
Cricket analogy: It resembles a scorer quickly jotting runs in a personal notebook during a fast T20 over and only transcribing the full over's tally into the official ICC records once the over actually finishes, batching six deliveries into one update.
Write-behind caching is often paired with Redis persistence (RDB snapshots or AOF logging) to reduce, but not eliminate, the data-loss window. Redis persistence protects the cache itself from restarts — it does not replace a durable primary database as the ultimate source of truth.
Choosing TTLs and Eviction Policies
A cache's usefulness depends heavily on its expiration strategy: a TTL that is too short causes excessive cache misses and defeats the purpose of caching, while a TTL that is too long risks serving stale data long after the source has changed. Redis also needs a maxmemory-policy such as allkeys-lru (evict the least recently used key across the whole keyspace) or volatile-ttl (evict keys with the nearest expiry first among keys that have a TTL set) so that when memory fills up, Redis degrades gracefully instead of running out of memory and rejecting writes.
Cricket analogy: It is like deciding how long a live win-probability percentage stays valid on a cricket broadcast graphic — refresh it every ball and it's accurate but noisy, refresh it only once an innings and it's stale by the time Jos Buttler smashes a six.
When thousands of keys share the same TTL — for example, if an entire product catalog is cached with ex=3600 in one bulk job — they can all expire at nearly the same moment, causing a 'cache stampede' where every request floods the database simultaneously. Mitigate this by adding random jitter to TTLs (e.g. ex=3600 + random.randint(0, 300)) or using a locking/early-recomputation strategy so only one request repopulates the cache while others wait or serve a slightly stale value.
- Cache-aside (lazy loading) is the most common pattern: the app checks Redis, falls back to the database on a miss, and populates the cache afterward.
- Write-through keeps the cache always consistent with the database by writing to both synchronously, at the cost of higher write latency.
- Write-behind minimizes write latency by writing to Redis first and flushing to the database asynchronously, but introduces a durability gap.
- TTLs balance freshness against cache-hit rate: too short wastes the cache, too long serves stale data.
- maxmemory-policy (e.g. allkeys-lru, volatile-ttl) determines what Redis evicts once it hits its memory limit.
- Add jitter to bulk-set TTLs to prevent cache stampedes where many keys expire at once and overwhelm the database.
Practice what you learned
1. In the cache-aside pattern, who is responsible for populating Redis after a cache miss?
2. Which caching strategy guarantees the cache is never stale relative to the database, at the cost of write latency?
3. What is the main risk of the write-behind (write-back) pattern?
4. Which maxmemory-policy evicts keys that have an expiry set, prioritizing those closest to expiring?
5. What technique helps prevent a cache stampede when many keys share the same TTL?
Was this page helpful?
You May Also Like
Cache Invalidation Patterns
How to keep Redis-cached data correct as the underlying source changes, covering explicit invalidation, TTL trade-offs, and Pub/Sub-based invalidation across multiple servers.
Redis as a Session Store
How Redis centralizes web session state across multiple application servers, from modeling sessions as hashes with TTLs to session security and scaling with Sentinel and Cluster.
Redis Memory Optimization
Techniques for minimizing Redis's RAM footprint, from choosing efficient encodings and bucketing strategies to compression, serialization, and maxmemory tuning.