Lesson 4 / 25

Cache-Aside (Lazy Loading)

Implement cache-aside correctly, including TTLs and serialisation.

The application manages the cache

Cache-aside, also called lazy loading, is the most common pattern. On a read, the application checks the cache; on a hit it returns the value; on a miss it reads from the database, stores the result in the cache with a TTL (time to live), and returns it. On a write, it updates the database and then deletes (invalidates) the cache entry, so the next read reloads fresh data. Advantages: only requested data is cached, the cache can fail without breaking reads (they just go to the database), and the logic is easy to follow. Drawbacks: the first request for each key is slow (a cold miss), data can be stale until the TTL expires or the key is deleted, and there are race conditions to consider (covered in the invalidation section). Use a consistent key naming scheme, such as product:v2:{id}, and a compact serialisation format.

Cache-aside read path

Check the cache first; on a miss, read the database and fill the cache.

An application box with an arrow to a cache box and, from there, a dashed fallback arrow down to a database cylinder, with a return arrow filling the cache.
Figure 2.1 — Cache-aside: hit returns immediately, miss loads and fills.

Cache-aside with Redis

Reads fill the cache lazily; writes delete the key after updating the database.

import json

TTL_SECONDS = 600

def get_product(product_id):
    key = f"product:v2:{product_id}"
    cached = redis.get(key)
    if cached is not None:
        return json.loads(cached)                        # hit
    product = db.fetch_one("SELECT * FROM products WHERE id = %s", (product_id,))
    if product is not None:
        redis.set(key, json.dumps(product), ex=TTL_SECONDS)   # fill on miss
    return product

def update_price(product_id, price_minor):
    db.execute("UPDATE products SET price_minor = %s WHERE id = %s", (price_minor, product_id))
    redis.delete(f"product:v2:{product_id}")             # invalidate after the write

Version your keys

Including a version in the key (product:v2:) lets you change the cached format safely: deploy code that reads v3 keys and the old entries simply expire, instead of being misread.

Quick check: In cache-aside, what does the application do after updating a record in the database?

  • Nothing; the TTL handles it
  • Flushes the entire cache
  • Deletes (invalidates) the corresponding cache entry
  • Writes the old value back to the cache
Answer

Deletes (invalidates) the corresponding cache entry — Deleting the key forces the next read to load the fresh value.