SproutStackSproutStack home

Redis in Depth

10 min readBeginner-friendlyFree to read

Before this, it helps to know: Caching Basics, Replication & Sharding.

Read a little, play a little. No scary maths, and no rush.

Share:

What Redis is, in one minute

Redis keeps data in memory and gives you ready-made data structures: strings, hashes, lists, sets, sorted sets, streams and more. Because everything is in RAM, a read or write typically takes well under a millisecond. Commands run one at a time on a single main thread, so each command is atomic: two clients incrementing the same counter never lose an update.

That combination makes Redis the default answer for caches, sessions, counters, rate limiters, leaderboards and lightweight queues. It is also why Redis can surprise you: memory is finite, a slow command blocks everyone, and by default some data can be lost in a crash. This page covers the settings that decide those behaviors.

Pick the right data type

Type Looks like Typical use
String page:/home → <html> cached values, counters with INCR
Hash user:42 → {name, plan} objects whose fields you read or update separately
List jobs → [a, b, c] simple queues (LPUSH + BLMOVE)
Set online → {42, 77} membership, de-duplication
Sorted set scores → {alice: 980, bob: 870} leaderboards, "top N", time-ordered indexes
Stream append-only log with IDs durable-ish queues with consumer groups and acknowledgements

Choosing the type is a design decision, not a detail: a leaderboard in a sorted set is one ZADD and one ZREVRANGE; the same thing in strings is a full scan.

How a typical deployment looks

commandsreplicatesreplicateshealthApp serversPrimarytakes all writesReplica 1async copyReplica 2async copySentinels (3)vote on failover
The app writes to the primary. Replicas copy it asynchronously and can serve reads. Sentinels watch the primary and promote a replica if it fails.

When one machine's memory or CPU is not enough, Redis Cluster splits the data across several primaries, each with its own replicas (see below).

The settings that matter

Setting Default What it decides
maxmemory 0 (no limit on 64-bit systems) how much RAM Redis may use before the policy kicks in
maxmemory-policy noeviction what happens when that limit is reached
appendonly no (RDB snapshots only) whether every write is also logged to an append-only file
appendfsync everysec how often that file is flushed to disk

Leaving maxmemory at 0 on a cache means Redis grows until the operating system runs out of memory. Always set it, with headroom (see "How it fails").

Eviction: the choice that bites

When Redis reaches maxmemory, the policy decides what happens:

  • noeviction (the default): commands that add data fail with an error. Right for data you must not lose silently, such as a job queue. Wrong for a cache.
  • allkeys-lru or allkeys-lfu: evict the least recently or least frequently used keys. The usual choice for a pure cache. LFU keeps keys that are read often even if not read in the last few seconds.
  • volatile-lru, volatile-lfu, volatile-ttl, volatile-random: evict only keys that have a TTL. If no key has a TTL, these behave exactly like noeviction.
  • allkeys-random: evict anything at random. Rarely what you want.

Redis approximates LRU by sampling a few keys (maxmemory-samples, default 5) rather than tracking a perfect order. That is close enough for caching and much cheaper.

The trap is mixing data in one instance: cached pages and user sessions with allkeys-lru. Under memory pressure Redis evicts sessions and logs people out. Use separate instances, each with its own memory limit and policy, for "may disappear" and "must not disappear" data.

Persistence: what survives a restart

  • RDB snapshots (on by default): Redis writes a point-in-time copy of the dataset every so often. Compact and fast to restart from, but a crash loses everything since the last snapshot, often minutes.
  • AOF (append-only file): every write is appended to a log. With appendfsync everysec (the default once AOF is on), a crash loses about one second of writes. always syncs on every write and is much slower; no leaves flushing to the operating system.
  • Both: common in production. Redis uses the AOF on restart because it is more complete.

Persistence protects against a restart. It does not protect against losing the machine: for that you need replicas.

Replication and failover

Replicas copy the primary asynchronously: the primary answers the client first and sends the change to replicas just after. So if the primary dies, the newest acknowledged writes may not have reached any replica, and they are lost when a replica is promoted.

WAIT n timeout makes the client block until n replicas have the write. The Redis docs are careful here: it greatly reduces the chance of losing a write, but does not turn Redis into a strongly consistent store. If a write must never be lost, Redis should not be its only home.

Failover is automatic with Sentinel (for a single primary) or built into Redis Cluster. Clients must be able to discover the new primary, which most client libraries handle.

Redis Cluster: sharding built in

  • The key space is split into 16,384 hash slots. A key's slot is CRC16(key) mod 16384, and each primary owns a range of slots.
  • A client that asks the wrong node gets a MOVED redirect (or ASK while a slot is being migrated) and caches the new location. Smart clients go straight to the right node afterwards.
  • Commands touching several keys (MGET, MULTI/EXEC, Lua scripts) only work if all keys are in the same slot; otherwise you get a CROSSSLOT error. Hash tags fix this: in {user:42}:cart, only user:42 is hashed, so all of a user's keys land together.
  • Cluster supports only database 0.

Sharding spreads different keys. It does nothing for one hot key, which always lives on one node. Fixes for that are replicas for reads, a small local cache in each app server, or splitting the key (counter:{1..10}, summed on read).

Patterns you will use

  • Cache-aside with a TTL: see Caching Basics.
  • Counters and rate limits: INCR is atomic; for anything with several steps, use a Lua script. See Design a Rate Limiter.
  • Leaderboards: ZADD scores 980 alice, then ZREVRANGE scores 0 9 WITHSCORES for the top ten.
  • Queues: a list with BLMOVE for the basics; Streams with consumer groups when you need acknowledgements and redelivery of unacknowledged messages.
  • Pub/Sub: fire-and-forget. A subscriber that is disconnected misses messages entirely. If messages matter, use Streams.
  • Locks: SET lock:order:42 <random-token> NX PX 30000 takes a lock for 30 seconds; release it only if the token still matches (a short Lua script), so you never delete someone else's lock.

How it fails

  • Memory full: errors on write (noeviction) or surprise evictions (allkeys-*). Watch used memory and the eviction count.
  • One slow command blocks everyone: KEYS *, SMEMBERS on a set with millions of members, or DEL on a huge key. Use SCAN to iterate, UNLINK to delete big keys in the background, and check SLOWLOG when latency spikes.
  • Fork memory spikes: RDB snapshots and AOF rewrites fork the process. While the child writes, pages the parent changes are copied, so a write-heavy instance can briefly need much more memory. Leave headroom: a maxmemory well below the machine's RAM.
  • Failover loses recent writes: asynchronous replication, as above.
  • Big keys and hot keys: one 500 MB hash or one key taking 50,000 reads a second turns one node into the bottleneck.
Going deeper: locks, fencing and the Redlock debate

A lock with a timeout has a hole: if the holder pauses longer than the timeout (garbage collection, a slow disk, a stopped VM), the lock expires, someone else takes it, and the original holder wakes up still believing it holds the lock. Two writers, one resource.

Martin Kleppmann's well-known critique of Redlock (taking the lock on a majority of independent Redis nodes) argues that no timeout-based lock can fix this on its own. The robust fix is a fencing token: every grant comes with a number that only goes up, and the resource (your database) rejects writes carrying an older number. If correctness truly depends on mutual exclusion, use a store built on consensus (etcd, ZooKeeper) or make the protected operation safe to run twice. A Redis lock is fine for efficiency ("avoid doing this twice") rather than correctness ("must never happen twice").

Going deeper: licensing and Valkey

Redis 7.2 and earlier are BSD-licensed. Versions 7.4 to 7.8 use a choice of two source-available licenses (RSALv2 or SSPLv1), and Redis 8 adds the open-source AGPLv3 as a third option. In response to the 2024 change, the Linux Foundation's Valkey project forked Redis just before the license switch and continues under BSD. Several cloud providers now offer Valkey-based services. For system design the behavior discussed here is the same; for a company, the license is a legal and procurement question worth raising early.

What to say in a design review

  • "Redis is our cache, with allkeys-lfu and a maxmemory below the instance size. Losing it costs latency, not data."
  • "Sessions live in a separate Redis with noeviction and AOF, because evicting them logs users out."
  • "We use hash tags so a user's keys share a slot, and we keep an eye on hot keys."
  • "Anything that must never be lost is written to the database first; Redis replication is asynchronous."

Remember this

  • The default eviction policy is noeviction. A cache needs allkeys-lru or allkeys-lfu, and a maxmemory.
  • volatile-* policies only evict keys with a TTL; with none, writes start failing.
  • AOF with everysec can lose about a second; asynchronous replication can lose the newest writes on failover.
  • Cluster has 16,384 slots; multi-key commands need one slot, which hash tags provide. Sharding does not fix a hot key.
  • A slow command blocks every client. Use SCAN and UNLINK, never KEYS * in production.

Check your understanding

5 questions · first-try answers earn the most XP

1. A Redis instance used only as a cache has maxmemory set and the default eviction policy. It fills up. What happens to new writes?
2. A team sets maxmemory-policy volatile-lru, but their app never sets a TTL on any key. What happens when Redis fills up?
3. In Redis Cluster, `MGET cart:42 profile:42` fails with a CROSSSLOT error. What is the standard fix?
4. Redis is configured with appendonly yes and appendfsync everysec. The machine loses power. Roughly how much acknowledged data can be lost?
5. A Redis lock is taken with `SET lock:order:42 <token> NX PX 30000`. The holder pauses for 40 seconds (a long garbage collection), then writes to the database. What can go wrong?

My notes

Saved in this browser. Highlight a line above and save it, or write it in your own words.

Nothing saved yet. Your highlights will live here.

References

Finished reading?

Ticking it here also ticks the chapter in the sidebar, the section count and your streak — it is all one number.

Try a design drill

Related chapters

Spotted a mistake or want a topic covered? Report an issue

📬 An occasional email when new lessons land

New lessons and showdowns, no more than a few times a month. No spam, unsubscribe anytime. We store your email only to send the newsletter.