Skip to content
intermediatePhase 46 · Caching

Cache Invalidation

Implement strategies to keep cached data fresh and correct.

45m
0 problems
Topic Progress0%

Invalidation Strategies

Cache Invalidation Strategies

Cache invalidation is the process of removing or updating cached data when the underlying data changes.

Strategy Overview

Invalidation Strategies:

1. Time-Based (TTL):
   - Auto-expire after duration
   - Simple, automatic
   - May serve stale data

2. Event-Based:
   - Invalidate on data change
   - Immediate consistency
   - More complex

3. Version-Based:
   - Cache key includes version
   - New version = new key
   - Automatic invalidation

4. Manual Invalidation:
   - Explicit delete/update
   - Full control
   - Requires code changes

Implementation Patterns

# 1. Direct Invalidation
def update_user(user_id, data):
    db.update(user_id, data)
    cache.delete(f"user:{user_id}")

# 2. Pattern-Based Invalidation
def invalidate_user_cache(user_id):
    # Delete all cache entries for this user
    keys = cache.keys(f"user:{user_id}:*")
    cache.delete(*keys)

# 3. Tag-Based Invalidation
def invalidate_by_tag(tag):
    # Delete all entries with this tag
    keys = cache.keys(f"tag:{tag}:*")
    cache.delete(*keys)

# 4. Version-Based Keys
def get_user_versioned(user_id, version):
    key = f"user:{user_id}:v{version}"
    return cache.get(key)

Time-Based Invalidation

Time-Based Invalidation (TTL)

TTL Implementation

class TTLCache:
    def __init__(self, redis_client):
        self.redis = redis_client
    
    def set_with_ttl(self, key, value, ttl_seconds):
        """Set with explicit TTL"""
        self.redis.setex(key, ttl_seconds, json.dumps(value))
    
    def get_ttl(self, key):
        """Get remaining TTL"""
        return self.redis.ttl(key)
    
    def refresh_ttl(self, key, new_ttl):
        """Refresh TTL on access"""
        self.redis.expire(key, new_ttl)

# Usage
cache.set_with_ttl("user:123", user_data, ttl_seconds=3600)

# Remaining TTL
ttl = cache.get_ttl("user:123")  # 3599, 3598, ...

TTL Strategies

Strategy Description Use Case
Fixed TTL Same duration for all entries General purpose
Variable TTL Different TTLs per data type Mixed workloads
Sliding TTL Refresh on access Active data
Adaptive TTL Adjust based on patterns Dynamic workloads

Stale Data Window

TTL Analysis:

Data written: 10:00 AM
TTL: 1 hour
Data updated: 10:30 AM (in DB)

10:00 - 11:00: Cache serves data (potentially stale after 10:30)
11:00: Cache expires, next read gets fresh data

Stale window: Up to 30 minutes in this example

Event-Based Invalidation

Event-Based Invalidation

Event-Driven Architecture

Event-Based Flow:

Data Change → Event Published → Consumer Invalidates Cache

Components:
1. Event Producer (DB trigger, app code)
2. Event Bus (Kafka, RabbitMQ)
3. Event Consumer (Cache invalidator)

Implementation with Event Bus

# Producer: Publish invalidation event
def update_product(product_id, data):
    db.update(product_id, data)
    
    # Publish invalidation event
    event_bus.publish('cache.invalidation', {
        'type': 'product',
        'id': product_id,
        'timestamp': time.time()
    })

# Consumer: Handle invalidation events
def handle_invalidation_event(event):
    if event['type'] == 'product':
        cache.delete(f"product:{event['id']}")
        # Also invalidate related caches
        cache.delete(f"product:{event['id']}:details")
        cache.delete(f"product:{event['id']}:reviews")

# Subscribe to events
event_bus.subscribe('cache.invalidation', handle_invalidation_event)

Database Trigger Approach

-- PostgreSQL example
CREATE OR REPLACE FUNCTION invalidate_cache()
RETURNS TRIGGER AS $$
BEGIN
    -- Publish to notification channel
    PERFORM pg_notify('cache_invalidation', 
        json_build_object(
            'table', TG_TABLE_NAME,
            'id', NEW.id,
            'operation', TG_OP
        )::text
    );
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER user_cache_invalidation
AFTER INSERT OR UPDATE ON users
FOR EACH ROW EXECUTE FUNCTION invalidate_cache();

Pros and Cons

Aspect Time-Based Event-Based
Consistency Eventual Stronger
Complexity Simple Complex
Network overhead None Event bus traffic
Reliability Automatic Depends on event delivery
Latency Up to TTL delay Near real-time

The Two Hard Problems

The Two Hard Problems of Caching

Phil Karlton famously said: "There are only two hard things in Computer Science: cache invalidation and naming things."

Why Cache Invalidation is Hard

Challenges:

1. Distributed Consistency:
   - Multiple cache nodes
   - Network partitions
   - Eventual consistency

2. Complex Data Relationships:
   - Derived data
   - Aggregations
   - Cross-entity dependencies

3. Timing Issues:
   - Race conditions
   - Concurrent updates
   - Out-of-order events

4. Failure Scenarios:
   - Lost invalidation events
   - Partial failures
   - Recovery after crash

Common Solutions

# 1. Version-Based Keys (Simplest)
def get_user(user_id, version=None):
    if version is None:
        version = get_user_version(user_id)
    return cache.get(f"user:{user_id}:v{version}")

# 2. Cache Stampede Prevention
def get_with_lock(key, loader):
    value = cache.get(key)
    if value is None:
        if lock.acquire(f"lock:{key}"):
            try:
                value = loader()
                cache.set(key, value)
            finally:
                lock.release(f"lock:{key}")
    return value

# 3. Read-Through with Versioning
class VersionedReadThrough:
    def get(self, key, version):
        versioned_key = f"{key}:v{version}"
        value = cache.get(versioned_key)
        if value is None:
            value = db.get(key)
            cache.set(versioned_key, value)
        return value

Best Practices

  1. Use short TTLs as a safety net
  2. Implement idempotent invalidation
  3. Version your cache keys
  4. Monitor stale data detection
  5. Have fallback mechanisms

Practice Problems

0/3solved
Design Cache Invalidation System

Design a scalable Cache Invalidation system. Cover high-level architecture, data model, and API design.

Solution
// Complete system design:
// - Functional + Non-functional requirements
// - Capacity estimation
// - Data model (SQL/NoSQL choice)
// - API endpoints
// - Component architecture
// - Scaling strategy
// - Monitoring & reliability
Cache Invalidation Scaling

How would you scale Cache Invalidation to handle 10x the current load? Identify bottlenecks and solutions.

Solution
// Scaling approach:
// 1. Load balancing
// 2. Database sharding/replication
// 3. Cache layer (Redis)
// 4. CDN for static assets
// 5. Async processing (queues)
// 6. Microservices decomposition
Cache Invalidation Failure Modes

Analyze potential failure modes for Cache Invalidation and design mitigation strategies.

Solution
// Failure mitigation:
// 1. Redundancy (multi-AZ)
// 2. Circuit breakers
// 3. Retry with backoff
// 4. Dead letter queues
// 5. Health checks
// 6. Graceful degradation

Quiz

1. Which invalidation strategy provides the strongest consistency?

Question 1 options

2. What is the main advantage of time-based invalidation?

Question 2 options

3. Why is cache invalidation considered hard?

Question 3 options

4. How does version-based key invalidation work?

Question 4 options

5. What is a safety net for cache invalidation failures?

Question 5 options

Flashcards

Question

What are the main cache invalidation strategies?

Answer

1) Time-based (TTL), 2) Event-based, 3) Version-based, 4) Manual invalidation

Question

Why is cache invalidation hard?

Answer

Distributed consistency, race conditions, complex data relationships, and failure handling make it challenging

Question

What is a TTL safety net?

Answer

Using short TTLs so even if invalidation fails, stale data expires quickly automatically

Question

Event-based vs time-based invalidation?

Answer

Event-based: stronger consistency, more complex. Time-based: simpler, may serve stale data up to TTL.

Question

How does version-based invalidation work?

Answer

Cache keys include version number; on update, increment version so old keys become stale and expire via TTL

Revision Notes

Key Takeaways

  • 1.Time-based (TTL) is simplest but may serve stale data
  • 2.Event-based provides stronger consistency but is more complex
  • 3.Version-based keys provide automatic invalidation
  • 4.Short TTLs act as safety net for invalidation failures
  • 5.Cache invalidation is hard due to distributed systems challenges

Interview Tips

  • Discuss trade-offs between consistency and complexity
  • Explain why cache invalidation is considered one of the hard problems
  • Mention TTL as a safety net for all strategies
  • Give examples of when to use each strategy

Cheat Sheet

Cheat Sheet: Cache Invalidation

Strategies

  1. Time-Based (TTL): Auto-expire, simple, may be stale
  2. Event-Based: Invalidate on change, strong consistency, complex
  3. Version-Based: Key includes version, automatic invalidation
  4. Manual: Explicit delete, full control

Why Hard?

  • Distributed consistency
  • Race conditions
  • Complex relationships
  • Failure scenarios

Best Practices

  • Use short TTLs as safety net
  • Implement idempotent invalidation
  • Version cache keys
  • Monitor for stale data
  • Have fallback mechanisms