Redis and caching: a short guide to faster reads without stale surprises
A practical introduction to Redis caching: when cache-aside helps, how TTLs and invalidation bound stale data, and how to avoid cache stampedes, hidden failure modes and treating a cache as the source of truth.
A cache is a copy with a purpose
Caching helps when repeatedly reading the same data is expensive enough to affect latency, database load or both. Product catalogues, profile summaries, reference data and rendered API responses are common candidates because many callers request the same result.
Redis is often used as an in-memory shared cache. It can serve a value quickly, but it is not automatically the source of truth. The primary database or service remains responsible for durable, correct data; the cache is an optimisation that must be safe to lose.
Do not add a cache because a database query feels slow. First identify the query, load pattern and acceptable freshness. An index, simpler query or smaller response may solve the actual problem with less operational complexity.
Start with cache-aside
Cache-aside is the most useful default pattern. On a read, the application checks Redis first. A cache hit returns the stored value; a miss reads the primary source, puts the result in Redis with an expiry and returns it to the caller.
On a write, update the primary source first, then invalidate the related cache key. The next read reloads a current value. Deleting the key is usually safer than trying to predict every cached representation that needs to be rewritten.
Choose names that make ownership and invalidation obvious. A key such as cache:product:123 is easier to inspect and delete deliberately than a hash of an undocumented query.
Read:
1. GET cache:product:123
2. On miss, read product 123 from the database
3. SET cache:product:123 <value> EX 300
Write:
1. Update product 123 in the database
2. DEL cache:product:123
3. The next read repopulates the cacheA TTL is a freshness decision
A time to live gives a cache entry an expiry. It is the upper bound on how long a cached value can survive without being refreshed by a later read, not a universal number to apply to every key.
Choose the TTL from product behaviour. A stock level or permission may need immediate invalidation and a short fallback window; a country list may be acceptable for hours. A longer TTL can improve the hit rate and reduce source load, but increases the period in which a missed invalidation can serve old data.
Apply expiry deliberately and monitor it during troubleshooting. A Redis key without a TTL can remain until eviction or manual removal, which is rarely the intended contract for ordinary cached data.
SET cache:product:123 "{...}" EX 300
TTL cache:product:123
# Delete after an authoritative write:
DEL cache:product:123Design for misses, expiry and Redis failure
A cache miss is normal. The application must still work when the key has expired, been evicted or never existed. Keep the source read bounded and observable so a cache miss does not turn into an unbounded request backlog.
When a popular key expires, many concurrent requests can all miss and overload the primary source. This cache stampede can be reduced with request coalescing, a short-lived lock, probabilistic early refresh or controlled stale-while-revalidate behaviour. Pick the simplest approach justified by actual load.
Redis can also be unavailable. Decide whether the request can fall back to the source, return a reduced response or fail clearly. Do not make a cache mandatory for a path unless it is operated as a required dependency with the appropriate resilience and recovery plan.
- Measure cache hit rate, misses, source latency and source error rate together
- Set a memory limit and eviction policy appropriate to the key workload
- Invalidate after authoritative writes rather than treating cache updates as the only copy
- Keep cache keys namespaced by capability and representation
- Test expiry, Redis unavailability and concurrent misses—not only cache hits