SYSTEM DESIGN · TECHNOLOGIES · 6 OF 12

Redis: The In-Memory Data Store Behind Caches, Sessions and Leaderboards

What Redis is and why it is so fast: in-memory data structures served by a single command thread, strings, hashes, sorted sets and streams, TTLs and eviction, RDB and AOF persistence, replication with Sentinel, Redis Cluster hash slots, the patterns it powers (cache-aside, rate limits, leaderboards, locks), where it hurts and the numbers to quote.

  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9
  10. 10
  11. 11
  12. 12
1 / 16

Press play to hear the narration, or step through at your own pace.

Notes

What it is

Redis is an in-memory key-value store where values are data structures: strings, hashes, lists, sets, sorted sets and more. Everything lives in RAM, so a read over the network takes a fraction of a millisecond. It can also persist to disk. After a licence change, the Linux Foundation forked it as Valkey, which speaks the same commands. AWS, Google and others run both for you.

Redis in one line
  • in-memory key-value store
  • whose values are data
  • structures, not just strings
  • all data in RAM, optional
  • persistence to disk
  • Valkey: open-source fork,
  • same commands and protocol
  • managed: ElastiCache,
  • MemoryDB, Redis Cloud

Data structures

The data structures are what make Redis more than a cache. Strings for cached values and counters. Hashes for objects with fields. Lists for simple queues. Sets for unique members. Sorted sets, where each member has a score, for leaderboards and time-ordered feeds. Streams for an append-only event log with consumer groups. HyperLogLog counts unique items in twelve kilobytes. And geo commands find things nearby.

Structures and their jobs
String   SET k v EX 60      cache, counter
Hash     HSET user:7 name A  object fields
List     LPUSH / BRPOP      simple queue
Set      SADD / SISMEMBER   unique visitors
Sorted   ZADD / ZREVRANGE   leaderboard
Stream   XADD / XREADGROUP  event log
HLL      PFADD / PFCOUNT    approx count
Bitmap   SETBIT / BITCOUNT  daily actives
Geo      GEOSEARCH          nearby drivers

Why it is fast

Why is it so fast? The data is in memory, so there is no disk on the read path. Commands run one at a time on a single thread, so there are no locks, and every command is atomic. Increment a counter from a thousand clients and none of the updates are lost. Newer versions use extra threads for network input and output. Pipelining sends many commands in one round trip. The flip side: one slow command blocks everyone.

Why it is fast
  • • data in RAM: no disk seek
  • • one thread executes
  • commands one at a time:
  • no locks, each command
  • is atomic
  • • I/O threads for network
  • (6.0+)
  • • pipelining: send 100
  • commands, one round trip
  • • simple, compact encodings

TTL + eviction

Every key can have a time to live, after which it disappears. That is how caches stay fresh and sessions end. When memory reaches the limit you set, an eviction policy decides what goes. For a pure cache, all keys LRU drops the least recently used. If Redis also holds data you cannot lose, like queues or locks, use no eviction, so writes fail loudly instead of silently deleting data.

Expiry and eviction
SET page:/home html EX 300
  → gone after 5 minutes

maxmemory 12gb
maxmemory-policy:
  allkeys-lru    cache: drop least
                 recently used
  volatile-ttl   drop soonest expiry
  noeviction     errors on write
                 (for queues, locks)

Cache-aside

The pattern you will use most is cache-aside. The app asks Redis first. On a hit, it returns in under a millisecond. On a miss, it reads the database, then stores the result in Redis with a time to live. When the data changes, write the database and delete the cache key so the next read refills it. Add jitter to expiry times so thousands of keys do not expire together. The Cache building block goes deeper.

The most common pattern
  • 1. GET key from Redis
  • 2. hit → return (sub-ms)
  • 3. miss → read the database
  • 4. SET key value EX ttl
  • on update: write DB, then
  • DELETE the key
  • stampede guard: lock or
  • jittered TTLs

More patterns

Other patterns are just a few commands. A rate limiter increments a counter per user per minute and sets it to expire, as in Concepts episode 30. A leaderboard adds points to a sorted set and reads the top ten in order, in logarithmic time even with millions of players. A simple lock sets a key only if it does not exist, with an expiry, and deletes it only if the token still matches. For locks that must be correct, see Concepts episode 31.

Patterns in a few commands
rate limit (fixed window):
  INCR rl:u42:202610241105
  EXPIRE rl:u42:202610241105 60

leaderboard:
  ZINCRBY board 50 player:9
  ZREVRANGE board 0 9 WITHSCORES

lock:
  SET lock:job1 <token> NX PX 30000
  (delete only if token matches)

Persistence

Redis can survive restarts. RDB writes a point-in-time snapshot every few minutes: compact and quick to load, but you can lose the writes since the last one. The append-only file logs every write, and with a flush every second you lose at most about a second. Many setups use both. A pure cache often uses neither, because it can always be rebuilt from the database.

RDB vs AOF
  • RDB: point-in-time snapshot
  • every few minutes; compact,
  • fast restart, lose minutes
  • AOF: log of every write
  • fsync everysec → lose at
  • most ~1 s on crash
  • common: both; or none for
  • a pure cache

Replication

For availability, replicas copy the primary asynchronously and can serve reads. Sentinel, a small group of watcher processes, agrees when the primary is down, promotes a replica and tells clients the new address. Because replication is asynchronous, writes acknowledged just before a crash can be lost. That is fine for a cache, but worth saying if Redis holds anything important. MemoryDB on AWS adds a durable log for that case.

High availability
  • replicas: async copy of the
  • primary, can serve reads
  • Sentinel (3+ processes):
  • agree the primary is down,
  • promote a replica, tell
  • clients the new address
  • async → writes acked just
  • before a crash can be lost

Redis Cluster

When one machine's memory or CPU is not enough, Redis Cluster shards. Keys map to one of sixteen thousand three hundred and eighty-four hash slots, and each shard owns a range of slots, with its own replicas. Clients cache the slot map and are redirected if a slot has moved. Commands that touch several keys only work when they share a slot, so you put a common part in curly braces, a hash tag, to keep a user's keys together.

Redis Cluster
  • 16,384 hash slots
  • slot = CRC16(key) mod 16384
  • each shard owns slot ranges,
  • has its own replicas
  • client caches the slot map;
  • wrong shard → MOVED reply
  • multi-key ops only within
  • one slot: use hash tags
  • {user:42}:cart, {user:42}:seen

Great at

Redis is great whenever many stateless app servers need fast shared state: caching database results, sessions and carts, rate limits and counters, leaderboards, short locks and deduplication, publish and subscribe to fan messages out to WebSocket servers, light queues with streams, nearby searches and online presence. One Redis often replaces several smaller systems.

Where Redis shines
  • • cache in front of a DB
  • • sessions and carts
  • • rate limiting, counters
  • • leaderboards (sorted sets)
  • • short-lived locks, dedupe
  • • pub/sub fan-out to sockets
  • • lightweight queues (Streams)
  • • geo lookups, presence

Where it hurts

Where it hurts. RAM is far more expensive than disk, so large data sets get costly. Asynchronous replication can lose recent writes. Because commands run one at a time, a single slow one, like listing all keys or deleting a huge set, freezes every client; use scan and unlink instead. A gigantic key or one wildly popular key overloads a single shard. And it should not be the only home for data you cannot afford to lose.

Pain points
  • • RAM is expensive per GB
  • • async replication: may
  • lose recent writes
  • • one slow command blocks all
  • (KEYS *, huge DEL, Lua)
  • • big keys: a 1 GB hash
  • • hot keys: one shard melts
  • • cluster: no cross-slot ops
  • • not a primary database for
  • data you cannot lose

Use it when

Use Redis when you need sub-millisecond access to data that is rebuildable or short-lived, or when atomic counters, sorted sets and expiring keys make the problem easy. Avoid it as the system of record for data that must never be lost, when the data set is far larger than memory, or when you need flexible queries. Then it belongs in front of a real database, not instead of one.

Use it when / avoid it when
  • USE when
  • you need sub-ms reads
  • data is rebuildable or
  • short-lived (TTL)
  • you need atomic counters,
  • sorted sets, rate limits
  • AVOID when
  • data set much larger than RAM
  • data must never be lost
  • you need queries or joins

Numbers

Numbers to have ready. Latency inside a region is a tenth of a millisecond to a millisecond. One instance handles around a hundred thousand simple operations a second, and over a million with pipelining. A value can be five hundred and twelve megabytes, but keep them small. Cluster has sixteen thousand three hundred and eighty-four slots. HyperLogLog uses twelve kilobytes with under one percent error. Aim for a cache hit rate above ninety percent.

Numbers worth knowing
latency in-region        0.1 - 1 ms
ops per instance         ~100k+/s
  with pipelining        1M+/s
max value size           512 MB
  (keep values under ~100 KB)
hash slots               16,384
HyperLogLog              12 KB, ~0.81% err
AOF everysec loss        up to ~1 s
cache hit rate goal      90%+

Say it like this

How to say it. Redis as a cache-aside layer for product pages, ten minute time to live with jitter, least recently used eviction. The same cluster holds rate limit counters. Each shard has a replica and automatic failover. And if Redis is lost entirely, the service falls back to the database behind a circuit breaker, because the cache is an optimisation, not the source of truth. That last sentence is what interviewers listen for.

In the interview
  • "Redis as a cache-aside layer
  • for product pages, TTL 10
  • minutes with jitter, LRU
  • eviction. Same cluster for
  • rate-limit counters. A primary
  • and replica per shard with
  • failover. If Redis dies we
  • fall back to the database
  • with a circuit breaker."

Interview questions

Why is Redis so fast?

All data is in memory, commands run on a single thread with no locking, encodings are compact and the protocol is simple; pipelining amortises network round trips. A typical instance serves around a hundred thousand operations per second with sub-millisecond latency.

How would you build a leaderboard with Redis?

Use a sorted set with the player as member and score as the score: ZINCRBY to add points, ZREVRANGE to read the top N and ZREVRANK for a player’s position, all in logarithmic time.

How do you implement a rate limiter with Redis?

For a fixed window, INCR a key made of user id and current minute and set EXPIRE on first use, rejecting when the count passes the limit. For a sliding window, store request timestamps in a sorted set or use a Lua script for an atomic token bucket.

What is the difference between RDB and AOF persistence?

RDB writes periodic point-in-time snapshots that are compact and fast to load but can lose minutes of data. AOF logs every write and, with fsync every second, loses at most about a second; many deployments use both.

Can Redis lose data on failover?

Yes. Replication is asynchronous, so writes acknowledged by the primary but not yet copied to a replica are lost if the primary fails and the replica is promoted. WAIT can reduce the risk, and services like MemoryDB add a durable log.

How does Redis Cluster shard data?

Each key is hashed with CRC16 into one of 16,384 slots and each shard owns a range of slots. Multi-key commands must use keys in the same slot, which hash tags such as {user:42} make possible.

Which eviction policy would you choose for a cache?

allkeys-lru or allkeys-lfu, so the least recently or least frequently used keys are dropped when memory is full. Use noeviction when Redis holds data such as queues or locks that must not disappear silently.

Why should you never run KEYS * in production?

Commands run on one thread, so a command that walks millions of keys blocks every other client until it finishes. Use SCAN to iterate incrementally and UNLINK to delete large keys in the background.

Is Redis a good primary database?

Only for data that fits in memory and can tolerate the durability trade-offs of its persistence and asynchronous replication. Usually it sits in front of a durable database as a cache or holds short-lived state.