Redis in Depth
Before this, it helps to know: Caching Basics, Replication & Sharding.
Read a little, play a little. No scary maths, and no rush.
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
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-lruorallkeys-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 likenoeviction.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.alwayssyncs on every write and is much slower;noleaves 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
MOVEDredirect (orASKwhile 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 aCROSSSLOTerror. Hash tags fix this: in{user:42}:cart, onlyuser:42is 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:
INCRis atomic; for anything with several steps, use a Lua script. See Design a Rate Limiter. - Leaderboards:
ZADD scores 980 alice, thenZREVRANGE scores 0 9 WITHSCORESfor the top ten. - Queues: a list with
BLMOVEfor 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 30000takes 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 *,SMEMBERSon a set with millions of members, orDELon a huge key. UseSCANto iterate,UNLINKto delete big keys in the background, and checkSLOWLOGwhen 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
maxmemorywell 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-lfuand amaxmemorybelow the instance size. Losing it costs latency, not data." - "Sessions live in a separate Redis with
noevictionand 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 needsallkeys-lruorallkeys-lfu, and amaxmemory. volatile-*policies only evict keys with a TTL; with none, writes start failing.- AOF with
everyseccan 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
SCANandUNLINK, neverKEYS *in production.
Check your understanding
5 questions · first-try answers earn the most XP
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