Designing for Cache Invalidation: Patterns for Data Consistency
Master cache invalidation in Redis. Learn how to implement cache-aside and write-through patterns to keep your application data consistent and reliable.

Previously in this course, we covered memory management strategies to keep your instance healthy. While eviction handles memory pressure, it doesn't solve the "stale data" problem. In this lesson, we address the core challenge of distributed systems: ensuring the data in your cache matches the source of truth in your primary database.
When you introduce a cache, you create two places where data can live. If these two locations drift, your users see outdated information. We solve this through deliberate invalidation strategies.
The Cache-Aside Pattern
The cache-aside pattern is the industry standard for most read-heavy applications. In this model, your application code manages the interaction between the database and Redis.
- Read: Application checks Redis. If it's a "hit," return the data. If it's a "miss," fetch from the database, write to Redis, then return.
- Write: Application updates the database. Immediately after, it deletes (invalidates) the corresponding key in Redis.
By deleting the key rather than updating it, you avoid complex race conditions where an old write might overwrite a newer one.
Worked Example: Cache-Aside Implementation
JAVASCRIPTasync function getUserProfile(userId) { const cacheKey = CE9178">`user:${userId}`; // 1. Try to get from cache const cached = await redis.get(cacheKey); if (cached) return JSON.parse(cached); // 2. Cache miss: fetch from DB const user = await db.query(CE9178">'SELECT * FROM users WHERE id = ?', [userId]); // 3. Store in cache with a TTL (Time-To-Live) await redis.set(cacheKey, JSON.stringify(user), CE9178">'EX', 3600); // 1 hour return user; } async function updateUserProfile(userId, data) { // 1. Update primary DB await db.query(CE9178">'UPDATE users SET ... WHERE id = ?', [userId]); // 2. Invalidate cache await redis.del(CE9178">`user:${userId}`); }
The Write-Through Pattern

In write-through caching, the application treats the cache as the primary interface. When you save data, your code updates the cache and the database simultaneously, often wrapping the database write in a transaction or ensuring the cache update happens immediately.
This ensures the cache is always fresh, making it ideal for systems where read latency is critical and you cannot afford a "cold start" (the initial miss).
| Pattern | Write Latency | Read Latency | Consistency |
|---|---|---|---|
| Cache-Aside | Low | Higher (on miss) | Eventual |
| Write-Through | Higher | Very Low | Stronger |
The Role of TTLs in Consistency
Even with rigorous invalidation, bugs or network partitions can lead to stale data. Always set a TTL (Time-To-Live) on your cached items as a "self-healing" mechanism. If your invalidation logic fails, the cache will naturally expire and refresh from the database within a predictable window.
As we discussed in implementing expiration and TTL, setting an expiration is your final line of defense against data drift.
Hands-on Exercise
Modify your existing API response cache logic:
- Identify a
PUTorPATCHendpoint in your project. - Add a
redis.del()call to the handler that updates that resource. - Verify the behavior by updating a record in your database and checking if the API returns the updated data on the next request.
Common Pitfalls
- Deleting before the DB update: If you delete the cache before the DB update finishes, a concurrent read might fetch the old DB data and re-populate the cache with stale info. Always update the DB first, then delete the cache.
- The "Thundering Herd": If a very popular key expires, many concurrent requests might see a cache miss and hit the database simultaneously. Use a short, randomized TTL or locking if this becomes a bottleneck.
- Ignoring Failures: If your cache deletion fails, your data will be stale until the TTL expires. In mission-critical systems, consider adding a retry queue for invalidation events.
FAQ
Q: Why delete the cache instead of updating it? A: Updates are prone to race conditions (two concurrent updates might finish in a different order than they started). Deletion is idempotent and safer.
Q: Should I use write-through for everything? A: No. It adds complexity and increases write latency. Use it only for data that is frequently read and must be perfectly consistent.
Q: What if the Redis server goes down? A: Your application should be designed to catch cache errors and fall back to the primary database gracefully.
Recap

Consistency is about bridging the gap between your primary database and memory. We’ve learned that cache-aside (invalidate on write) is the most flexible pattern, while write-through offers tighter consistency at the cost of complexity. Always pair these with TTLs to ensure your system recovers from unexpected state mismatches.
Up next: We’ll explore implementing connection pooling to ensure our application can handle high-concurrency connections to Redis without exhausting resources.
Work with me

Laravel REST API Development
Clean, secure, well-documented Laravel REST APIs — the backend engine for your app, mobile client, or SaaS. Built by an API specialist.

AI Automation & Agentic Workflow Development
Automate the repetitive work eating your time — content pipelines, data workflows, and agentic AI tasks that run themselves.


