Skip to content
Cache-Aside Pattern: The Most Common Caching Strategy Explained 3 min

Cache-Aside Pattern: The Most Common Caching Strategy Explained

SD
ScaleDojo
May 11, 2026
3 min read739 words
1
Cache-Aside Pattern

Why Instagram Needed Caching

When Instagram hit 25 million users, their PostgreSQL database was handling 10,000 queries per second for user profile lookups alone. Each profile query took 5-15ms. They added a Redis cache in front. The same lookups now took 0.1ms. Database load dropped by 90%. The pattern they used? Cache-aside - the most common and most practical caching strategy in production systems.

How Cache-Aside Works

Cache-Aside Flow (Lazy Loading):

  Application        Cache (Redis)      Database (PostgreSQL)
      |                   |                    |
  1.  |---GET user:42---->|                    |
  2a. |<--FOUND (HIT)-----|                    |  (0.1ms - fast!)
      |    return data     |                    |
      |                    |                    |
  --- OR if cache miss: ---                     |
  2b. |<--NOT FOUND (MISS)-|                    |
  3.  |----SELECT * FROM users WHERE id=42----->|
  4.  |<---user data----------------------------| (5-15ms)
  5.  |---SET user:42 EX 300-->|                |  (cache for 5 min)
  6.  |    return data     |                    |

  Key: the APPLICATION manages both cache and DB.
  The cache never talks to the database directly.

Code Example

import redis
import json

cache = redis.Redis(host='localhost', port=6379)
TTL = 300  # 5 minutes

def get_user(user_id: int) -> dict:
    # Step 1: Check cache
    cache_key = f"user:{user_id}"
    cached = cache.get(cache_key)
    
    if cached:
        # Cache HIT - return immediately (0.1ms)
        return json.loads(cached)
    
    # Cache MISS - query database (5-15ms)
    user = db.query("SELECT * FROM users WHERE id = %s", user_id)
    
    if user:
        # Populate cache for future requests
        cache.setex(cache_key, TTL, json.dumps(user))
    
    return user

def update_user(user_id: int, data: dict):
    # Update database first (source of truth)
    db.execute("UPDATE users SET ... WHERE id = %s", user_id)
    # Invalidate cache (delete, don't update)
    cache.delete(f"user:{user_id}")
    # Next read will miss cache and re-populate from DB

Why 'Lazy Loading' Is Smart

Data is only loaded into the cache when it is actually requested. You never pre-fill the cache with data nobody asked for. This means your cache naturally contains the hottest data - the data real users are actively requesting.

The tradeoff: the first request for any piece of data will always be a cache miss (a 'cold start'). After a server restart, the first few hundred requests are slow as the cache warms up.

Cache Invalidation: The Hard Part

Phil Karlton famously said: 'There are only two hard things in computer science: cache invalidation and naming things.' Here is why:

The Stale Data Problem:

  Time 0:   Cache has user:42 = {name: "Alice", city: "Seattle"}
  Time 1:   Alice updates her city to "Portland" in the database
  Time 2:   Another service reads user:42 from cache
            --> Gets {city: "Seattle"} - STALE!
  Time 300: TTL expires, cache entry deleted
  Time 301: Next read misses cache, fetches from DB
            --> Gets {city: "Portland"} - finally correct

Invalidation Strategies:

  Strategy 1: TTL-based (simplest)
    - Set TTL = 300s (5 min max staleness)
    - Tradeoff: shorter TTL = fresher data but more DB load

  Strategy 2: Delete on write (most common)
    - When writing to DB, delete cache key immediately
    - Next read will miss and re-populate from DB
    - Maximum staleness: milliseconds

  Strategy 3: Event-driven invalidation
    - Database publishes change events (CDC)
    - Cache service subscribes and invalidates
    - Works across multiple services

When to Use Cache-Aside

Workload Pattern          Cache-Aside?    Why
------------------------  ------------    ---------------------------
Read-heavy (90%+ reads)   YES             Most reads served from cache
User profiles             YES             Read often, change rarely
Product catalog           YES             Millions of reads, few updates
Session data              MAYBE           Consider write-through instead
Real-time leaderboard     NO              Use Redis sorted sets directly
Financial balances        NO              Staleness is unacceptable

Numbers That Matter

  • Redis GET latency: ~0.1ms (vs 5-50ms database query)

  • Cache hit ratio target: 95%+ for most applications

  • At 95% hit ratio with 10K req/sec: 9,500 cache hits + 500 DB queries instead of 10,000 DB queries

  • RAM cost: $5/GB/month on cloud vs CPU cost of database queries - usually a massive net savings

  • TTL sweet spot: 60-300 seconds for user-facing data, 3600+ seconds for rarely changing data

Interview Tip

When an interviewer asks about caching, start with cache-aside and explain why: 'The application checks Redis first. On a miss, it queries the database, stores the result in Redis with a 5-minute TTL, and returns it. On writes, I delete the cache key so the next read gets fresh data. This gives us sub-millisecond reads for 95%+ of requests while keeping the database as the source of truth.' Then mention the cold-start problem and how you would handle cache stampedes (locking or pre-warming).

Key Takeaway

Cache-aside is simple and effective: check cache first, fall back to database on miss, populate cache for next time. Invalidate on write by deleting the cache key. It works best for read-heavy workloads where slight staleness is acceptable. Most applications should start here.

Enjoyed this article?

Share it with your network to help others level up their system design skills.

Discussion0

Join the Discussion

Sign in to leave comments, reply to others, or like insights.

Sign In to ScaleDojo

No comments yet. Be the first to start the thread!

Related Articles

Enjoyed this content?

Your support keeps us creating free resources

We put a lot of hours into researching and writing these guides. If it helped you, consider buying us a coffee. Every bit goes toward keeping ScaleDojo's content free and growing.

$

One-time payment via Stripe. ScaleDojo account required.

ScaleDojo Logo
Initializing ScaleDojo