---
title: "Before You Reach for Redis: The Right Cache Strategy"
description: "The questions to ask before adding a cache, why cache invalidation is hard, and the choice between TTL and consistency"
url: https://sade.dev/en/notes/the-right-cache-strategy/
lang: en
author: "Muhammet Şafak"
published: 2026-08-15
updated: 2026-09-06
section: Note
tags: ["caching","redis","performance","production"]
summary: "A cache is never a faster database; it is speed bought with accepted staleness, so the first question is not technical: how stale may this data be? TTL is the boring default, because it does not require knowing every write path and it does not break silently when one is forgotten. Explicit invalidation is exact only if you catch every path. And a cache reduces read load, never write load."
---

# Before You Reach for Redis: The Right Cache Strategy

> A cache is never a faster database; it is speed bought with accepted staleness, so the first question is not technical: how stale may this data be? TTL is the boring default, because it does not require knowing every write path and it does not break silently when one is forgotten. Explicit invalidation is exact only if you catch every path. And a cache reduces read load, never write load.

A team added a Redis cache because "the site is slow." A week later the support queue filled up: "I updated the price but the old one still shows." The cache had fixed the slowness — and quietly created a new problem.

Adding a cache is not a free performance win. It trades freshness for speed; and if you didn't make that trade deliberately, the bill comes back as a bug.

## A cache is a consistency concession

The moment you cache a value, you've accepted this: someone, for a while, may see stale data. A cache is never a "faster database"; it's "speed in exchange for accepted staleness."

So the first question isn't technical: **how stale can this data be?** Data whose answer is "not at all" — an account balance, a stock count — should be thought through twice before it's cached.

## Questions to ask first

Before you turn on Redis:

- **Is this really a read bottleneck?** "The site is slow" is a hypothesis, not a measurement. Adding a cache without seeing the source of the slowness is covering up something you don't understand.
- **Is the slowness actually a missing index?** A cache papers over a bad query but doesn't fix it. Hiding behind a cache what a `CREATE INDEX` would solve is moving the problem, not solving it — this is exactly the wrong breaking point in [data-intensive systems](/en/systems/data-intensive-systems-breaking-points).
- **How stale can this data stay?** The answer determines which strategy you pick.

## Why is invalidation hard?

Cache invalidation gets cited as one of the two hard problems in software — and that's no exaggeration. Putting the cache in is easy; **clearing** it at the right moment is hard.

Because a cached value must be cleared on every write path that affects it. A product price changes not only from the "edit product" screen; it also changes from a bulk price update, a discount job, an admin script. Finding **all** of those paths and clearing the cache — that's the hard part. Miss one, and you get the support ticket from the top of this post.

## Two strategies

In practice there are two roads:

- **TTL-based.** You assign the value a lifetime — 60 seconds, 5 minutes. When it expires, the cache refreshes itself. Simple, sturdy, and it doesn't require knowing every write path. In return: you accept staleness up to the TTL.
- **Explicit invalidation.** You delete the cache by hand when the data changes. Exact and fresh — but only if you catch every write path completely.

My default is TTL. It's boring, predictable, and it doesn't break silently because of a forgotten write path. I move to explicit invalidation only when staleness is genuinely unacceptable and the number of write paths is limited and known. Most of the time the two are used together: a short TTL as a safety net, explicit invalidation for speed.

## A cache doesn't reduce writes

A common mistake: trying to rescue a system under write load by adding a cache. A cache reduces **read** load; it has no effect on write load — if anything, invalidation itself is extra write work. If your problem is on the write side, a cache is the wrong tool.

## Practical patterns

- **Cache-aside.** Read: check the cache first, and on a miss fetch the value from the database and write it to the cache. The most common and most understandable pattern.
- **Stampede protection.** The instant a popular key's TTL expires, hundreds of requests hit the database at once. On a cache miss, use a short lock so only one request goes to the database.
- **Key discipline.** Namespace your cache keys — the pattern in the [shared Redis namespace isolation](/en/notes/shared-redis-namespace-isolation) note applies to cache keys too.

---

A cache is not the cure for a slow system; it's the relief of a measured read bottleneck, bought with a deliberate consistency concession. A cache added without an invalidation plan takes back more as debt than it speeds up.

Before you add a cache, ask: how old can this data be — and who's going to clear it?

## Frequently asked

**Should you use a TTL or explicit invalidation?**

TTL is the safer default: it needs no knowledge of every write path and it does not break silently when one is forgotten, at the price of accepting staleness up to the TTL. Move to explicit invalidation only when staleness is genuinely unacceptable and the write paths are limited and known. Most of the time both run together.

**Does a cache reduce write load?**

No. A cache reduces read load and has no effect on write load; invalidation itself is extra write work. If the problem is on the write side, a cache is the wrong tool.


## Sources

- [RFC 9111: HTTP Caching — freshness lifetime, stale responses and invalidation](https://www.rfc-editor.org/rfc/rfc9111.html) — IETF
- [Distributed Locks with Redis](https://redis.io/docs/latest/develop/clients/patterns/distributed-locks/) — Redis
