Two copies of the truth
The moment you cache a value you hold two copies of the truth, and they disagree the instant your database changes before your cache does.
Staleness is not a bug you can eliminate. It is a budget you set.
The expiry is your crude but dependable tool. An entry older than its expiry is thrown away, so that number is your worst case for serving old data.
Setting it is a product decision wearing an engineering costume. A username can be an hour stale and nobody files a ticket. A price on a checkout page five minutes stale becomes an escalation, and a stale stock count oversells.
Each cached field gets one question. What does somebody do with a wrong value, and what does that cost you?
Set the expiry to the largest number that answer tolerates, because longer expiries mean higher hit rates and less load on your database.
Closing the gap
Expiries leave a gap, since on average an entry sits stale for half its expiry after every write. If that is too long, delete the key inside the code path that writes to your database.
Delete, do not update. Updating from your application races with concurrent fills and can pin an old value in place, while a delete simply forces the next read to fetch fresh.
The expiry stays as a backstop, because invalidation code has bugs, deploys crash between the write and the delete, and messages get dropped. Your expiry guarantees any such mistake heals itself in bounded time.
A third trick sidesteps invalidation entirely: put a version in the key. Writes bump the version, readers fetch the new key, and the old entry dies by eviction. It costs memory for dead entries, and it works best when your row already carries a version.
Worked example
Priya's team caches seat availability for a cinema chain with a 60-second TTL, reasoning that shows rarely sell out in a minute. That holds until a Marvel premiere, when a 300-seat screen sells out in 40 seconds and the cache keeps advertising availability for most of another minute. Around 1,800 users tap into a checkout flow for seats that are gone, and 214 of them get an error after entering payment details. The redesign splits the data by volatility: static show metadata keeps a 10-minute TTL, the availability count drops to a 5-second TTL, and the final seat lock skips the cache and hits Postgres directly. Checkout errors from staleness fall to near zero, and the database barely notices the short TTL because only the hottest shows churn.