Caching with Redis
An in-memory store used as cache, session bag, lock, counter, and queue.
40 minDifficulty 3/5cache · performanceAI-writtenWritten by a model on 16 August 2026 and not yet read by a person. Checked automatically: schema, the pedagogical rules the hand-written material is held to, and every diagram parsed for real.
Before this
Why this exists
A dashboard query aggregating a million rows takes 800ms every single time it runs, even though the underlying data only changes once an hour. Running that same expensive query for every single page view is pure waste — computing it once and storing the result somewhere fast to re-read is the entire idea behind caching, and Redis is the tool most commonly reached for to hold that "somewhere fast."
The mental model
Redis is an in-memory key-value store — reads and writes are extremely fast because everything lives in RAM, not on disk, but it's used for far more than a plain cache: sessions, rate-limit counters, distributed locks, and simple job queues all reuse the same underlying speed and a handful of atomic data structure operations.
How it works
The cache-aside pattern is the most common usage: check cache, fall back to the source
A read first checks Redis for a key; if present (a cache HIT), return it immediately without touching the database at all. If absent (a MISS), query the actual database, then write the result into Redis before returning it — so the NEXT request for the same key is a hit. The application code, not Redis itself, decides this fallback logic.
TTLs (expiration) keep a cache from serving stale data forever
Setting a key with `EX 300` (expire in 300 seconds) means Redis automatically removes it after that duration — the next request after expiration is a miss, which re-fetches from the source and refreshes the cached value. Choosing the right TTL is a genuine tradeoff: too short and you lose most of the caching benefit; too long and users can see stale data for an uncomfortably long window.
Cache invalidation on write keeps the cache honest sooner than waiting for a TTL
Rather than waiting for a TTL to expire, a write to the underlying data can immediately DELETE (or update) the corresponding cache key — the next read is then a guaranteed miss that fetches fresh data. This is more responsive than TTL-only expiration but requires the application to correctly identify and invalidate every cache key that could be affected by a given write, which is where cache invalidation bugs typically live.
Redis's atomic operations make it useful well beyond caching
`INCR` atomically increments a counter with no race condition possible, even under massive concurrent access — this single primitive underlies rate limiting (counting requests per window), like counters, and view counters. A distributed lock can be built from `SET key value NX EX ttl` (set only if not already present, with an expiry) — giving exactly one caller exclusive access to a resource across multiple server instances.
The mechanism
A read request checks Redis first. On a hit, the cached value is returned directly, with no work done against the actual data source. On a miss, the application queries the real source, stores the result in Redis with an appropriate TTL, and returns it — populating the cache for subsequent requests. A write to the underlying data optionally invalidates the corresponding cache key immediately, rather than relying solely on the TTL to eventually expire it.
What people get wrong
- Redis automatically keeps cached data in sync with the underlying database.
- Redis has no awareness of your database at all — it's the APPLICATION's responsibility to write to the cache, invalidate it on updates, and set appropriate TTLs; Redis just stores whatever it's told to, faithfully, until told otherwise or a TTL expires. This misconception is exactly why stale-cache bugs happen: developers assume synchronization is automatic, when every bit of cache consistency logic has to be explicitly written into the application.
- A longer TTL is always safer because it reduces the chance of a cache miss causing a slow request.
- A longer TTL trades FRESHNESS for hit rate — data can be stale for the full TTL duration, which is a real cost for anything users expect to be reasonably current, like inventory counts or account balances. The right TTL genuinely depends on how tolerant the specific data is to staleness, not a single 'longer is always better' rule — a stock ticker and a user's bio have very different acceptable staleness windows.
- Caching a query result is always a safe, purely beneficial optimization with no downside.
- Caching introduces a genuine new failure mode — serving stale or incorrect data — that didn't exist before, and cache invalidation (deciding exactly when a cached value is no longer valid) is a well-known source of subtle, hard-to-reproduce bugs. The famous programmer joke 'there are only two hard problems in computer science: cache invalidation and naming things' exists precisely because getting invalidation logic correct is genuinely difficult, not a solved, risk-free operation.
When not to use it
- The data changes extremely frequently (multiple times per second) and staleness of even a second or two is unacceptable.
- Consider whether caching is actually appropriate here at all — a cache's value comes from data being relatively stable between reads; for data this volatile, the cache's hit rate and freshness benefit may not justify its complexity.
- You need durable, persistent storage that must survive a full restart with zero data loss.
- A proper database, not Redis as a cache — while Redis CAN be configured for persistence, its typical caching use case treats data as disposable and reconstructible from the source of truth, which a genuine data store cannot assume.
Terms
- Cache-aside
- — A caching pattern where the application checks the cache first, falling back to the source of truth on a miss and populating the cache with the result.
- Cache hit / miss
- — A hit means the requested data was found in the cache; a miss means it wasn't and had to be fetched from the underlying source.
- TTL (Time To Live)
- — The duration after which a cached key automatically expires and is removed, forcing the next request to be a miss.
- Cache invalidation
- — Explicitly removing or updating a cached value when the underlying data changes, rather than waiting for its TTL to expire naturally.
In an interview
A product's price is updated in the database, but the API keeps returning the old price for several minutes. What's the likely cause, and how would you fix it?
- the price is being served from a cache (Redis) with a TTL that hasn't yet expired
- the write to the database didn't invalidate the corresponding cache key
- fix: explicitly delete or update the cache key as part of the same operation that updates the price, rather than relying solely on the TTL
Can you recall it?
Why does Redis have no awareness of whether cached data is still accurate, and whose responsibility is it to keep the two in sync?
Connected ideas
Also part of
This idea matters in more than one area — which is usually why it matters.