TT Lab
Get started
Learn Learning paths Courses

Redis and Caching

A Copy Goes Stale the Moment the Original Changes

Continue in TT Lab

Summary

A caching strategy is a balancing act between staleness and misses. There is no single right answer, only a balance point that fits the nature of the data.

Why this was needed

Caches work well thanks to two properties. Temporal locality — data you just used is likely to be used again soon. Spatial locality — when you use some data, the data near it is likely to be used soon too.

The problem comes from the fact that a cache is a copy of the original. A copy goes out of date the moment the original changes. Here you need to tell two bad situations apart. Stale means the cache holds a value older than the original, and the user sees a wrong value. A miss means the cache has no value, so you have to make a trip to the original, which is slow.

If you discard often to reduce staleness, misses increase; if you keep entries longer to reduce misses, staleness increases. This balancing act is the source of every difficulty with caching.

How it works

The simplest invalidation is the TTL. You attach a lifetime to each entry and discard it when that time passes. Its appeal is simplicity and predictability — you do not need to write separate invalidation logic, and even in the worst case staleness never exceeds the TTL. That makes it a good fit for data that can be briefly out of date: news lists, popular posts, exchange rates. Conversely, something like an account balance is not served by a TTL alone and needs explicit invalidation.

Explicit invalidation has two branches. Event-based invalidation deletes the related caches immediately when the original changes. Freshness is good, but tracking dependencies is hard — "product 42" may be in the product detail cache, in the category list cache, and in the search result cache. One missed dependency becomes a silent stale bug.

A version key is a much sturdier practical technique. You embed the version in the key itself, so invalidation is handled by moving rather than deleting. You use user:42:v7:profile, and when the data changes you bump the version to v8. Nobody looks up the v7 key any more, so it is cleaned up naturally by the TTL. You do not have to do the deletion yourself, and it is also robust against race conditions where invalidation and re-fetch interleave, because the old key and the new key are physically different.

What you meet in the field

There are three real reasons invalidation is hard. First, distribution — caches are scattered across many servers and many layers, so invalidating atomically becomes a distributed consensus problem. Second, race conditions — if the moment you delete the cache and the moment another request puts the old value back interleave, the value you just deleted comes back to life. Third, the complexity of dependencies.

So in practice people give up on perfect invalidation, decide "how long can we tolerate staleness", and mix TTL with explicit invalidation.

Four cache patterns

They are divided by who does the reads and writes. Knowing the names keeps team conversations short.

Pattern Read Write Characteristics
Cache-aside The app checks the cache → if absent, the DB → puts it in the cache The app writes the DB and invalidates the cache The most common. The first request is always slow
Read-through The cache reads the DB on your behalf — App code gets simpler
Write-through — Writes to the cache and the DB together The cache is always current. Writes are slow
Write-behind — Writes to the cache, then to the DB later Writes are fast. Risk of loss

In practice, 95% of usage is the first row. The rest appears when you use a library where the cache layer wraps DB access. Write-behind is not used for monetary data, because if the cache dies, writes that have not yet reached the DB disappear.

What not to cache

People often miss the third one. If you put a 1μs in-memory computation into Redis, it becomes 500 times slower. A cache only has value for slow things (DB lookups, external APIs, heavy computation).

Rules for designing cache keys

labhub:v3:user:42:profile
  │     │    │   │   └ 무엇
  │     │    │   └ 식별자
  │     │    └ 종류
  │     └ 스키마 버전     ← 형식이 바뀌면 올린다. 옛 키는 TTL 로 사라진다
  └ 애플리케이션 접두     ← 같은 Redis 를 여러 앱이 쓸 때 충돌을 막는다

Putting in a version is the key point. If the structure of the object stored in the cache changes, reading old values breaks things, but if you bump the version you use a new key and the old keys disappear naturally through the TTL. It is safer than wiping everything with FLUSHALL — that empties all caches at once and triggers a stampede on the DB.

Be careful with wildcard deletion too. KEYS user:* is an O(n) operation that scans everything, so it stops Redis. Use SCAN, or manage a Set for tags alongside from the start.

What to look at in the next check

First, in the quiz, check the trade-off between staleness and misses and the conditions of data for which a TTL is acceptable. From the next module on, you will work with Redis data structures one by one, and then practice ranking, cache patterns, and stampede defense in turn.