Every cache in this chapter has had a in it somewhere, and every team that has one has had the same argument: is five minutes too long, or not long enough?

That single sentence turns the argument from a matter of taste into a question about your own traffic, which is countable. For a key requested every ten minutes, a five minute TTL never hits at all, however careful the reasoning behind it was.
So the right number depends on how often keys repeat. The next thing to establish is what that actually looks like.

The straight line is what people implicitly assume when reasoning about caches. The curved line is what request popularity actually looks like, and every intuition about changes once you accept it.
A very small head takes most of the traffic and is easy to cache well. A very long tail is asked for once or twice, and no helps it.
The script is at scripts/labs/ttl/sweep.py. It generates 400,000 requests across 20,000 keys over an hour, Zipf distributed, and reports hit rate, origin load and staleness at seven different TTLs.

The benefit is a curve that flattens. The cost is a line that does not, because mean staleness is always about half the . Those two columns moving differently is the whole argument.

Half of 900 seconds is 442 seconds of average wrongness, bought for 7.7 more points of hit rate than 300 seconds gave. Somewhere on every one of these graphs the two lines cross over in value.

2,051 of the 20,000 keys appeared exactly once in 400,000 requests. Each one is guaranteed to be a miss, because there is no second request. Chasing the last few points is chasing something that does not exist.
Work out your compulsory miss rate before setting a hit rate target. A team aiming for 99 percent on traffic whose ceiling is 95 will keep raising the , keep adding staleness, and never arrive.

Alert on this line, not on hit rate. Hit rate is a proportion that can be improved by adding cacheable traffic. Origin requests per second is the absolute number that decides whether the thing behind the cache survives a busy hour.

A single global is applied to keys whose request intervals differ by orders of magnitude, and it can only be right for a narrow band of them.

A request arrives at a uniformly random point in an entry's life, so the mean age is half. At a 15 minute TTL the average user is looking at seven minute old data and the worst case is fifteen, routinely.
Say the TTL out loud as an age instead of a duration. If that sentence is uncomfortable, the number is wrong, and it is a much better test than intuition.

A restart, a deploy or a scale-up fills many entries in the same second, and a constant turns that into a synchronised expiry exactly one TTL later. If your origin load spikes at an interval matching your TTL, this is why.

Do the first two on every cache, always, before there is a problem. They are cheap, they have no downside, and both address failures that only appear under load, which is the worst time to be discovering them.


The bottom branch is the honest one and it is rarely taken. When correctness truly requires the current value, a very short is not a compromise, it is the same bug with a smaller window.

That is why TTLs survive and invalidation schemes decay. A migration script, an admin tool or another service writing directly all break invalidation, and the TTL keeps working regardless. Use both: invalidate for freshness, and keep a TTL underneath as a backstop.
4 questions - Score 80% to pass
Your cache hit rate is 88.5 percent at a 900 second TTL. Someone proposes raising it to an hour. What should you expect?
A cache that never expired would still miss on 4.8 percent of requests in the measured traffic. Why?
Your database load spikes sharply every five minutes, matching your cache TTL exactly. What is happening and what is the cheapest fix?
You want to stop guessing at TTLs. What single measurement answers the most questions?

If somebody proposes raising a TTL, ask what the previous raise bought. The measurement takes an afternoon and usually shows the last change was already past the knee.
With a long tail of keys, a TTL alone lets the key space grow without limit. The two together are what makes a cache both bounded and reasonably fresh, and they interact: with a capacity limit, most tail entries are evicted well before their TTL expires.

The measurement explains why. A minute already delivers two thirds of the achievable hit rate, and thirty seconds of average staleness is tolerable for most things. Start there and move with a measurement.

Check the key before checking the TTL. A cache with a near zero hit rate is nearly always keyed on something that changes every request: a timestamp, a request id, a full query string.

Keys that appear once give you the compulsory miss rate directly. The median gap tells you the smallest TTL that can ever hit. None of it requires changing the cache or running an experiment in production.

can expire per key, so different key classes can carry different TTLs. Plotting hit rate against origin load shows the knee without any modelling at all.

Restate every TTL as an age before agreeing to it. That one habit settles more arguments than any amount of reasoning about acceptable staleness in the abstract.

A TTL is the only cache setting that is simultaneously a performance decision, a correctness decision and a capacity decision, which is why it attracts so much opinion. It is also, unusually, one where a single hour of logging produces a number nobody can argue with.