Back to Blog
Lesson 28 of the Redis: Redis Essentials & Data Types course
DatabasesAugust 15, 20264 min read

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.

RedisCachingBackendDatabasesConsistencyNode.js
Closeup photo of a textured red brick wall showcasing a detailed pattern with natural color variations.

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.

  1. 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.
  2. 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

JAVASCRIPT
async 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

A person engages in writing on paper using traditional ink, captured in black and white.

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).

PatternWrite LatencyRead LatencyConsistency
Cache-AsideLowHigher (on miss)Eventual
Write-ThroughHigherVery LowStronger

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:

  1. Identify a PUT or PATCH endpoint in your project.
  2. Add a redis.del() call to the handler that updates that resource.
  3. 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

Team members presenting a project in a modern office setting with a focus on collaboration.

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.

Similar Posts