Skip to Content
Course content

205: Cache Invalidation Strategies

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Caching is the easy part. Putting a value into Redis or Memcached takes about two lines of code. The actual engineering challenge—the part that keeps us up at night—is cache invalidation. As the saying goes, there are only two hard things in Computer Science: cache invalidation and naming things.

I want to show you how this plays out in a real scenario. Let's imagine we're building a "Featured Products" section for an e-commerce site. This list is expensive to generate because it involves several joins and a complex sorting algorithm based on current sales trends. We can't hit the database for this on every single page load.

The "Set it and Forget it" approach

My first instinct, and usually the first move for most developers, is to use a TTL (Time-to-Live). I'll just tell the cache to hold onto the data for an hour. It's simple and requires zero logic in my update scripts.

$cacheKey = 'featured_products_list';
$products = $cache->get($cacheKey);

if (!$products) {
    $products = $db->getFeaturedProducts();
    // Cache for 3600 seconds (1 hour)
    $cache->set($cacheKey, $products, 3600);
}

return $products;

For a while, this works great. The site is snappy, and the database is breathing easy.

The moment it all falls apart

Here is where I messed up. A few weeks into production, the marketing manager tells me that they've just updated the price of a "Door Buster" deal to be 50% off, but the home page is still showing the old price. I check the DB—the price is correct. I check the site—it's wrong.

Of course. The cache is still alive for another 40 minutes. I've created a "stale data" bug. In a high-traffic store, showing an incorrect price for 40 minutes isn't just a glitch; it's a customer service nightmare. I realized that relying solely on TTL is a gamble: you're either caching for too short a time (hurting performance) or too long (hurting data integrity).

Switching to Event-Driven Invalidation

To fix this, I shifted to a "Push" model. Instead of waiting for the cache to expire, I'll tell the cache to die the second a product is updated. I added a hook into the ProductService update method.

class ProductService {
    public function updateProduct($productId, $data) {
        $this->db->updateProduct($productId, $data);
        
        // Explicitly kill the cache key
        $this->cache->delete('featured_products_list');
    }
}

This is much better. The moment the price changes, the next visitor triggers a fresh DB hit and repopulates the cache with the correct price. But wait—what if we have other cached lists? What if the product is also in a "Summer Sale" cache and a "Category: Electronics" cache? I can't possibly keep a manual list of every single cache key that might contain this product. That's a recipe for a maintenance disaster.

Solving the scaling problem with Cache Tagging

The professional way to handle this is through tagging. Instead of thinking about individual keys, we group related cache entries under a tag. While not every PHP cache driver supports tags natively, the logic is the same: we associate the 'featured_products_list' with a tag called products.

When I save the cache, I tag it. When I update any product, I invalidate everything associated with that tag.

// Saving with tags
$cache->set('featured_products_list', $products, 3600, ['products']);
$cache->set('electronics_category_list', $electronics, 3600, ['products']);

// In the ProductService update method:
public function updateProduct($productId, $data) {
    $this->db->updateProduct($productId, $data);
    
    // This wipes every cache entry tagged as 'products'
    $this->cache->invalidateTags(['products']);
}

Now, it doesn't matter if I have one cached list or one thousand. If a product changes, everything that depends on product data is flushed. It's a slightly heavier hammer, but it guarantees that your users never see a price from an hour ago.




📋 Practical Task

Implement a Multi-View Cache Invalidator for User Profiles

You are managing a user profile system. A user's username and avatar_url appear in three different cached locations: the Profile Page, the Global Navigation Header, and the Comment Section of their posts.

Your Task:

  1. Create a UserCacheManager class that handles the saving of these three different cache keys.
  2. Implement a tagging system (you can simulate this using an array of keys mapped to a tag in a mock cache object) so that all three keys are associated with a tag unique to that user (e.g., user_123).
  3. Write a method updateUserProfile($userId, $newData) that updates the database and then invalidates all cache entries associated with that specific user's tag, ensuring the header and comments are updated simultaneously with the profile page.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.