# Cache-Aside (Lazy Loading) — Caching Strategies & CDN Design

Source: https://www.skillbyai.com/en/caching-strategies/p-aside

> 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.](assets/figures/caching-strategies/section-2-map.svg) — 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.

```python
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.

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

- [ ] Nothing; the TTL handles it
- [ ] Flushes the entire cache
- [x] 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.
