🔥 0
0
Lesson 2 of 6 18 min +200 XP

Caching Patterns

There's no one-size-fits-all caching strategy. Different patterns suit different use cases. Let's master the three most important ones.

Pattern 1: Cache-Aside (Lazy Loading)

The most common pattern. The application manages both cache and database explicitly.

How It Works

  1. Application checks the cache first
  2. Cache hit - Return cached data
  3. Cache miss - Query the database
  4. Update cache with fresh data for next time
def get_user(user_id):
    # Step 1: Check cache
    cache_key = f"user:{user_id}"
    cached_user = cache.get(cache_key)

    if cached_user:
        return cached_user  # Cache hit!

    # Step 2: Cache miss - query database
    user = db.query("SELECT * FROM users WHERE id = ?", user_id)

    if user:
        # Step 3: Populate cache for next time
        cache.set(cache_key, user, ttl=3600)  # 1 hour TTL

    return user

Pros

  • Only caches data that's actually requested
  • Cache failures don't break the application
  • Simple to implement and understand

Cons

  • First request always hits the database
  • Stale data possible if DB changes
  • Cache stampede risk on popular keys

The Cache Stampede Problem

When a popular cache key expires, hundreds of requests might simultaneously hit the database:

The Thundering Herd

Popular user's profile cache expires. 1000 concurrent requests all see "cache miss" and hammer the database simultaneously. Solution: Use cache locks or "stale-while-revalidate" pattern.

def get_user_with_lock(user_id):
    cache_key = f"user:{user_id}"
    lock_key = f"lock:user:{user_id}"

    cached = cache.get(cache_key)
    if cached:
        return cached

    # Try to acquire lock
    if cache.set_nx(lock_key, "1", ttl=5):  # NX = only if not exists
        try:
            user = db.query("SELECT * FROM users WHERE id = ?", user_id)
            cache.set(cache_key, user, ttl=3600)
            return user
        finally:
            cache.delete(lock_key)
    else:
        # Another process is fetching - wait and retry
        time.sleep(0.1)
        return get_user_with_lock(user_id)

Pattern 2: Write-Through

Every write goes to both cache AND database synchronously.

How It Works

  1. Application writes to cache
  2. Cache writes to database (synchronously)
  3. Both succeed or both fail
def update_user(user_id, user_data):
    cache_key = f"user:{user_id}"

    # Write to database first
    db.execute("UPDATE users SET ... WHERE id = ?", user_id)

    # Then update cache
    cache.set(cache_key, user_data, ttl=3600)

    return user_data

Pros

  • Cache always has latest data
  • No stale data problems
  • Reads are always fast

Cons

  • Higher write latency (two writes)
  • May cache data that's never read
  • More complex failure handling
Best For

Use write-through when data consistency is critical and write volume is moderate. Common in session stores, user preferences, and shopping carts.

Pattern 3: Write-Behind (Write-Back)

Writes go to cache immediately; database is updated asynchronously.

How It Works

  1. Application writes to cache
  2. Cache acknowledges immediately (fast!)
  3. Cache asynchronously syncs to database
Write Request → Cache (instant) → Background Worker → Database

Pros

  • Extremely fast writes
  • Can batch database writes
  • Absorbs traffic spikes

Cons

  • Risk of data loss if cache fails
  • Complex to implement correctly
  • Eventual consistency only
Warning: Data Loss Risk

If the cache crashes before syncing to the database, data is lost. Never use for financial transactions or critical data.

Pattern Comparison

Pattern Write Speed Consistency Data Loss Risk
Cache-Aside N/A (read-focused) Eventual None
Write-Through Slower Strong None
Write-Behind Fastest Eventual High

Read-Through Pattern

A variation where the cache itself fetches from the database on miss:

# The cache handles the DB lookup automatically
user = cache.get("user:123")  # Returns from DB if not in cache

This simplifies application code but requires cache infrastructure support (like Redis modules or cache middleware).

Choosing the Right Pattern

Cache-Aside

Read-heavy workloads, unpredictable access patterns, when you want full control

Write-Through

Need consistency, moderate writes, session management, user preferences

Write-Behind

High write throughput, analytics, logging, when data loss is acceptable

Key Takeaways

  • Cache-aside is the most common - application controls everything
  • Write-through keeps cache consistent but adds write latency
  • Write-behind is fast but risky - use for non-critical data only
  • Combine patterns - Cache-aside for reads, write-through for writes

Next up: Eviction Policies - What happens when your cache runs out of space?

🧠 Quick Quiz

Test your understanding of this lesson.

1

In the cache-aside pattern, who is responsible for managing the cache?

2

What is the main disadvantage of write-through caching?

3

When is write-behind (write-back) caching most appropriate?

Why Caching Matters