← Back to system design

System Design Topic

Redis Caching: Patterns, Consistency & Performance at Scale

Run Redis as a cache: data structures, TTLs and eviction, persistence trade-offs, replication, clustering, hot keys, and stampede protection.

30 min read
Overview: Redis Caching: Patterns, Consistency & Performance at Scale

Redis Caching

Why Engineers Care About This

Redis is the default answer when teams need a fast, shared cache—but "put it in Redis" hides dozens of decisions. Pick the wrong data structure and you serialize huge JSON blobs on every partial update. Skip eviction policy and Redis runs out of memory and starts rejecting writes. Treat replicas like free read scaling without accounting for replication lag and users see stale profiles after an edit. Run a single primary with one hot key and you've built a distributed single point of congestion.

This article is about operating Redis as a cache—structures, memory, persistence, replication, clustering, hot keys, and stampedes. For which caching pattern to use (cache-aside vs write-through, CDN layers, invalidation philosophy), start with Caching Strategies. For a full distributed-cache interview walkthrough—capacity, sharding design, and architecture—see the Distributed Cache system design guide.

In interviews, when someone asks "How would you cache this with Redis?", they're really asking: "Do you know Redis beyond GET and SET? Can you scale it, survive hot keys, and protect the database when the cache blips?" Most engineers don't. They add a Redis client, set a TTL, and discover problems at 10x traffic.

Core Intuitions You Must Build

  • Redis is a data-structure server, not a dumb key-value blob store. Strings work for whole-object caching. Hashes suit field-level updates (user profile columns). Sorted sets power leaderboards and ranked feeds. Lists and streams handle queues and timelines. The structure you pick changes memory use, invalidation, and how painful partial updates are.

  • TTL is necessary but not sufficient. Every cache key should have a TTL or explicit delete path—but identical TTLs on hot keys expire together and cause stampedes. Add jitter, version suffixes, or proactive refresh for keys that matter.

  • Persistence is a product choice for caches. Pure caches often run with no persistence or short RDB snapshots—losing the cache on restart is acceptable if the origin can refill. Session stores and rate-limit counters may need AOF or hybrid persistence. Know what you lose on restart.

  • Replicas scale reads but add lag. A replica can serve read-heavy catalog data if seconds of staleness is fine. After a write, read-your-writes usually means hitting the primary or using a short-lived "recently written" flag—not blindly load-balancing across replicas.

  • Cluster mode shards by hash slot, not by your mental model of keys. One hot key still lives on one slot and one primary. Cluster fixes memory ceiling; it does not fix hot-key skew by itself.

  • Eviction policy defines what happens when memory is full. allkeys-lru evicts any key; volatile-lru only evicts keys with a TTL. noeviction returns errors on write—dangerous if you expected silent eviction. Set maxmemory and policy before production, not after the first OOM.

  • Stampede protection belongs in the cache layer for hot keys. On miss, use SET key lock NX EX 10 (or a library single-flight) so one worker rebuilds while others wait or serve stale. Without that, Redis just coordinates how fast you reach the database together.

Data structure pick at a glance

Different Redis types fit different cache shapes—memory and update patterns change with the choice:

TypeBest forWatch outPick when
STRINGSerialized JSON/HTML, counters, session tokensFull rewrite on one field changeWhole object is read/written together; simple GET/SET
HASHObject with fields (profile, product attrs)Still one key—hot key risk unchangedPartial field updates without deserializing a blob
ZSETRanked lists, time-ordered feeds, leaderboardsScore ties and range query costTop-N by score or time window
SET / LISTTags, dedup, small collectionsLarge collections blow memoryMembership checks, bounded fan-out lists

Redis STRING, HASH, and ZSET used for object cache, field updates, and ranked feed slices

Request path (app → Redis → origin)

A typical cache-aside read checks Redis first; on miss the app loads the origin and populates Redis. With replicas, only some reads can move off the primary:

Application servers read from Redis primary or replica on hit, falling back to database on miss

[ Client ] → [ App ] → GET redis:key ──hit──→ response
                         miss
                    [ Database ] → SET redis:key EX ttl → response

Subtopics (Taught Through Real Scenarios)

Data Structures Beyond STRING GET/SET

What people usually get wrong:

Teams cache every object as a JSON string because the client library makes SET easy. One field change means deserialize, mutate, serialize, and SET the whole blob again. They ignore hashes for field-level storage, sorted sets for ranked data, and counters for INCR-style metrics.

How this breaks systems in the real world:

A user profile lived in Redis as a 40 KB JSON string. Every "last seen" heartbeat rewrote the entire value 500 times per second across active users. CPU on the app and Redis spiked; network bandwidth between app and Redis doubled. The fix was a HASH—HSET user:123 last_seen without touching name or avatar fields—and moving high-churn counters to separate STRING keys. For a trending feed, a ZSET with post IDs as members and timestamps as scores replaced repeated "fetch top 50 and cache JSON" queries. But the real lesson is: match Redis type to access pattern, not to what your ORM serializes by default.

What interviewers are really listening for:

They want structure names tied to use cases—HASH for partial updates, ZSET for leaderboards, STRING for opaque blobs. Junior engineers say "store JSON in Redis." Senior engineers say which type and what breaks when one field changes often.

TTL, Key Design, and Eviction

What people usually get wrong:

Engineers set EX 3600 on every key with the same TTL, use vague keys like data_123, and leave maxmemory unset. When memory fills, they are surprised by OOM command not allowed or sudden evictions of cold keys while hot keys remain—depending on policy misconfiguration.

How this breaks systems in the real world:

All product keys expired at the top of each hour. On the hour, cache hit rate collapsed and the database saw a predictable spike—operations called it "the 9 AM incident." The fix was TTL jitter (3600 + random(0, 300)), namespace prefixes (product:v2:{id}), and maxmemory with allkeys-lru so Redis evicted least-used keys instead of rejecting writes. Version in the key prefix let them roll out a new schema without flushing the whole keyspace. But the real lesson is: key design and expiry are load shaping, not just housekeeping.

What interviewers are really listening for:

You mention maxmemory, eviction policy, TTL jitter, and namespaced versioned keys. Junior engineers say "set a one-hour TTL." Senior engineers explain what happens when memory is full and when synchronized expiry creates a thundering herd.

Persistence: RDB, AOF, and "Cache Only"

What people usually get wrong:

People copy production database persistence settings onto a pure cache. Or they disable persistence on a Redis instance that also holds rate-limit counters and wonder why limits reset after restart. Cache and durable Redis workload are different products on the same software.

How this breaks systems in the real world:

A team enabled AOF always on a read-heavy session cache to "be safe." Disk IO saturated; p99 latency rose above serving sessions from the database. For the session cache, the right call was no persistence—sessions rebuild on login. For a small rate-limit keyspace on the same cluster, they split workloads: ephemeral cache cluster without persistence, durable counter cluster with AOF every second. But the real lesson is: persistence trades latency and recovery for durability—only enable it when restart data loss has a user-visible cost.

What interviewers are really listening for:

You contrast RDB (point-in-time snapshots) vs AOF (replay log) and state what happens on restart for a pure cache (cold start, warm gradually). Junior engineers say "Redis is in-memory so it's fast." Senior engineers say which keys must survive restart and which persistence mode fits.

Replication and Read Scaling

What people usually get wrong:

Teams add read replicas and route all reads to them to "save" the primary—without defining consistency. A user updates their avatar, the write hits the primary, the next read goes to a replica still 200 ms behind, and the UI flashes the old image.

How this breaks systems in the real world:

A catalog service served product pages from replicas. Staleness of a few seconds was acceptable—until pricing updates during a sale. Merchants changed prices on the primary; replica reads served old prices for up to three seconds. The fix was tiered routing: catalog reads on replicas with short TTL keys; price keys read from primary or invalidated synchronously on write. Session tokens stayed primary-only. But the real lesson is: replicas are eventually consistent—route reads based on staleness tolerance per key type.

What interviewers are really listening for:

You explain replication lag and when to read primary vs replica. Junior engineers say "add replicas to scale." Senior engineers say which entities tolerate lag and how you enforce read-your-writes for user edits.

Clustering, Hash Slots, and Hot Keys

What people usually get wrong:

Engineers assume Redis Cluster automatically spreads traffic because it spreads keys. One viral post, one celebrity user id, or one global config key still maps to a single hash slot on one primary. That shard hits CPU and network limits while siblings stay idle.

How this breaks systems in the real world:

A feed:celebrity_123 STRING key served 40% of all Redis traffic on one cluster node. Other nodes sat at 30% CPU; the hot node hit 95%, elevating latency for unrelated keys co-located on that shard. Mitigations were layered: a short local in-process LRU in front of Redis for that key, splitting the feed into feed:celebrity_123:page:{n} keys, and read replicas for that slot's primary. For a global counter, they moved to a probabilistic counter and sharded writes across count:{shard} keys. But the real lesson is: cluster fixes memory scale, not skew—hot keys need key design and edge caching.

What interviewers are really listening for:

You mention hash slots, single-slot hot spots, and mitigations (key splitting, local cache, replica reads, write sharding). Junior engineers say "we'll add more cluster nodes." Senior engineers explain why one key cannot split across slots without redesign.

When one key owns a slot or Redis is unavailable, failure paths diverge:

Hot-key skew on one cluster shard versus SET NX single-flight stampede protection on cache miss

Cache Stampede Protection in Redis

What people usually get wrong:

Engineers implement cache-aside in application code with no lock on miss. A hot key expires; ten thousand goroutines see a miss and ten thousand database queries follow. Redis is present but only as a spectator to the stampede.

How this breaks systems in the real world:

A homepage configuration key expired during a traffic spike. Each app instance fired a heavy SQL aggregation on miss. Connection pools exhausted; the site returned 503s for two minutes. The fix used Redis SET lock:config NX EX 15—first miss acquires the lock and rebuilds; others GET in a short loop or return stale from a backup key config:stale. They added probabilistic early refresh: background workers extended TTL before expiry for keys above a hit-rate threshold. See the full outage narrative in The Cache Stampede That Took Down Our API. But the real lesson is: on miss, coordinate in Redis—locks, single-flight, stale-while-revalidate—not only at the database.

What interviewers are really listening for:

They want SET NX, single-flight, stale serving, TTL jitter, and warming by name. Junior engineers say "scale the database." Senior engineers describe how only one worker rebuilds a hot key and what others do while waiting.

When Redis Cache Fails or Slows Down

What people usually get wrong:

Applications block on Redis with no timeout. Failover is treated as instantaneous. No one decides fail-open (skip cache, hit origin) vs fail-closed (error) per route when Redis is unhealthy.

How this breaks systems in the real world:

During a primary failover, clients blocked on GET for two seconds. Thread pools filled; APIs timed out although the database was healthy. The team added 20 ms client timeouts, fail-open to the database for idempotent catalog reads with a circuit breaker, and fail-closed with a clear 503 for checkout sessions stored only in Redis. But the real lesson is: Redis is a dependency—degrade with intent, same as any other service.

What interviewers are really listening for:

You state timeouts, fail-open vs fail-closed per data class, and breaker on origin load. Junior engineers say "Redis is highly available." Senior engineers walk through the first thirty seconds of an outage.


Interview questions to practice

  • You cache user profiles in Redis—which structure do you use and how do you handle a field that updates every few seconds?
  • RDB or AOF for a session store vs a pure HTML fragment cache—what do you configure and what do you lose on restart?
  • Add read replicas— which endpoints can read from replicas and how do you prevent stale avatar after upload?
  • One key drives 50% of cluster traffic—what do you change in key design without breaking clients?
  • A hot key expires during a sale—walk me through SET NX single-flight and what waiting requests do.
  • Redis primary fails over—what happens to in-flight writes and what does your app do on timeout?

FAQs

Q: How is this different from the Caching Strategies topic?

A: Caching Strategies covers patterns—cache-aside, write-through, CDN layers, and when to cache. This article covers Redis mechanics—data types, eviction, persistence, replication, cluster slots, hot keys, and stampede primitives. Read patterns first; read this when you implement with Redis.

Q: Should I use STRING or HASH for cached objects?

A: STRING (JSON) when you always replace the whole object and size is modest. HASH when fields update independently or you want smaller partial writes. Very large objects may belong in object storage with Redis holding a pointer key.

Q: Do I need persistence on a Redis cache?

A: Often no—cold cache after restart is acceptable if the origin can refill and stampedes are mitigated. Use persistence when losing data on restart breaks correctness (sessions, idempotency tokens, rate limits you cannot rebuild cheaply).

Q: Can Redis Cluster fix hot keys?

A: Cluster spreads keys across nodes by hash slot, which raises memory ceiling. A single extremely hot key still lives on one slot. Fix with key splitting, local in-process cache, read replicas, or architecture change—not only more nodes.

Q: How do I prevent cache stampede with Redis?

A: Common tools: distributed lock on miss (SET lock NX EX), single-flight in app code, stale-while-revalidate backup key, TTL jitter, proactive warming before expiry. Combine with pattern choice from cache-aside vs read-through in Caching Strategies.

Q: Where do I go for a full distributed cache system design answer?

A: This topic covers Redis operations and interview trade-offs. For end-to-end architecture, capacity, and sharding design, use the Distributed Cache system design guide.


Key Takeaways

Structure follows access pattern — STRING for blobs, HASH for fields, ZSET for ranked data; wrong type wastes CPU and memory

TTL and keys shape load — jitter and versioned namespaces beat synchronized mass expiry

Set maxmemory and eviction policy — know whether Redis evicts or errors when full

Persistence is optional for pure caches — enable AOF/RDB only when restart loss hurts users

Replicas scale reads with lag — route by staleness tolerance; primary for read-your-writes paths

Cluster ≠ hot-key cure — one viral key still hammers one slot; split keys or add local cache

Coordinate on miss — SET NX / single-flight stops Redis from coordinating a database stampede

Keep exploring

Interview system design is a web of trade-offs. Pair this topic with a related fundamentals article or a practice problem while it is fresh.