Redis · cheat sheet

Redis

Data types, TTLs and eviction, caching patterns, persistence, replication, Sentinel, Cluster slots, locks and Lua: the Redis facts interviewers probe.

The Redis facts that come up in backend and system design interviews, for Redis 7.4 and 8.x. Valkey, the BSD-licensed Linux Foundation fork of Redis 7.2.4, matches everything here except the modules built into Redis 8.

Core model

  • In-memory server of typed values under string keys; commands run server-side, so each command is atomic.
  • One main thread executes commands in an event loop (epoll/kqueue). Background threads free memory (UNLINK) and fsync the AOF; fork() makes RDB snapshots and AOF rewrites; io-threads N offloads socket reads, parsing and replies.
  • Consequence: single commands never race, but one slow command (KEYS *, DEL of a huge key, a long Lua script) blocks every client.
  • Licence: RSALv2/SSPLv1 since 7.4, AGPLv3 added as an option in Redis 8. Redis 8 bundles JSON, the Query Engine, time series, probabilistic types (Bloom, Cuckoo, Top-K) and vector sets.

Data types

Type Key commands Cost Typical use
String (≤ 512 MB) SET, GET, INCR, SET NX PX, GETEX O(1) cache blobs, counters, locks
Hash HSET, HGET, HMGET, HINCRBY, HEXPIRE (7.4) O(1) per field objects, carts
List LPUSH, RPOP, BRPOP, LRANGE, LTRIM, LMOVE O(1) at ends simple queues, recent items
Set SADD, SISMEMBER, SINTER, SCARD O(1) add/check tags, unique members
Sorted set ZADD, ZINCRBY, ZRANGE … REV, ZRANK, ZRANGEBYSCORE O(log N) leaderboards, schedules, sliding windows
Stream XADD, XREADGROUP, XACK, XAUTOCLAIM, XTRIM O(1) append durable event queues
HyperLogLog PFADD, PFCOUNT, PFMERGE ≤ 12 KB, ~0.81% error unique counts
Bitmap SETBIT, BITCOUNT, BITOP, BITFIELD 1 bit per offset daily active flags
Geo GEOADD, GEOSEARCH O(log N) nearby search
  • LPUSH l a b c then LRANGE l 0 -1 gives c b a. SADD returns how many members were new.
  • Sorted-set ties sort by member name. ZREVRANK is zero-based from the top.

Expiry and eviction

SET k v EX 60          # TTL in seconds (PX for ms)
SET k v2 KEEPTTL       # plain SET would remove the TTL
EXPIRE k 120 GT        # NX | XX | GT | LT options (7.0+)
TTL k                  # -1 = no expiry, -2 = no such key
PERSIST k
Terminal
  • Expired keys are removed lazily (on access) and actively (a sampling cycle, hz 10).
  • INCR, HSET, LPUSH, RENAME keep the TTL; SET, GETSET and DEL clear it. Replicas wait for the primary’s DEL.
maxmemory-policy Evicts Use
noeviction (default) nothing; writes get OOM primary data
allkeys-lru / allkeys-lfu any key pure cache
volatile-lru / volatile-lfu keys with a TTL mixed cache + data
volatile-ttl shortest remaining TTL expiry-driven caches
allkeys-random / volatile-random random uniform access
  • volatile-* with no TTL keys behaves like noeviction.
  • LRU and LFU are approximate: maxmemory-samples 5 plus a 16-entry candidate pool. LFU uses a logarithmic 8-bit counter (lfu-log-factor 10) that decays (lfu-decay-time 1). OBJECT FREQ needs an LFU policy.
  • Watch evicted_keys, expired_keys, keyspace_hits/keyspace_misses in INFO stats.

Caching patterns

Pattern Read Write Trade-off
Cache-aside app checks cache, loads DB on miss, SET … EX update DB, then DEL key default; miss penalty, small stale window
Read-through cache layer loads misses (pairs with others) needs a loader library
Write-through from cache write cache + DB synchronously warm cache, slower writes
Write-behind from cache write cache, flush DB async fast writes, loss risk
  • Invalidate with update DB, then delete; never update the cached value from the write path (writer races). TTL is the safety net; CDC or an outbox makes invalidation reliable.
  • Stampede (hot key expires, everyone rebuilds): rebuild lock with SET NX PX, stale-while-revalidate, probabilistic early refresh, TTL jitter.
  • Penetration (missing IDs): cache a short negative entry, Bloom filter. Avalanche (mass expiry or outage): jitter, HA, DB protection.

Persistence

RDB AOF
What forked point-in-time snapshot log of write commands
Loss window since last snapshot always ≈ none, everysec ≈ 1 s (default), no ≈ OS flush
Size / restart compact, fast larger, replay (RDB preamble helps)
Maintenance BGSAVE, save 3600 1 300 100 60 10000 BGREWRITEAOF; multi-part AOF with manifest (7.0+)
  • fork() costs page-table copy time (latest_fork_usec) and copy-on-write memory; disable transparent huge pages, set vm.overcommit_memory=1, keep headroom.
  • Never auto-restart an empty primary with persistence off: replicas sync to the empty dataset.

Replication, Sentinel, Cluster

  • REPLICAOF host port; PSYNC with replication ID + offset; partial resync from repl-backlog-size (1 MB default) or full resync (diskless by default since 7.0).
  • Replication is asynchronous: acknowledged writes can vanish on failover. WAIT n ms, WAITAOF (7.2) and min-replicas-to-write narrow the window only.
  • Sentinel: ≥ 3 processes; SDOWN after down-after-milliseconds, ODOWN when quorum agree, failover needs a majority leader vote. Clients ask SENTINEL get-master-addr-by-name.
  • Cluster: 16,384 slots, slot = CRC16(key) mod 16384 (foo → 12182); bus port = data port + 10000; only DB 0.
Reply Meaning Client action
MOVED slot host:port slot owned elsewhere retry there, refresh slot map
ASK slot host:port slot migrating, key already moved ASKING + retry once, keep map
CROSSSLOT keys in different slots hash tags or split the command
TRYAGAIN multi-key op split by migration retry later
  • Hash tag: only the text inside the first {…} is hashed; {user:1000}:cart and {user:1000}:profile share a slot. Never put everything under one tag.

Atomicity tools

Tool Atomic Conditional logic Round trips
Pipeline no no 1 per batch
MULTI/EXEC yes, no rollback no (replies come at EXEC) 1 if pipelined
WATCH + MULTI optimistic; EXEC returns null on conflict yes, with retries several
Lua EVAL / Functions (7.0+) yes yes 1
  • Queue-time error (unknown command) → EXECABORT, nothing runs. Runtime error (WRONGTYPE) → that command fails, the others apply.
  • Lua: pass keys in KEYS, values in ARGV; Lua number → integer (3.99 → 3); false → nil. Scripts over busy-reply-threshold (5 s) make others get BUSY.

Locks and rate limits

-- acquire: SET lock:job <token> NX PX 30000   (reply OK or nil)
-- release only if we still own it:
if redis.call('GET', KEYS[1]) == ARGV[1] then
  return redis.call('DEL', KEYS[1])
end
return 0
lua
  • Not SETNX + EXPIRE (crash in between leaves a lock forever); not a bare DEL (removes someone else’s lock after expiry).
  • Pauses can outlive the TTL: use fencing tokens at the resource for correctness. Redlock = majority of 5 independent primaries; Kleppmann’s critique targets timing assumptions.
Limiter Redis structure Note
Fixed window INCR + PEXPIRE on first hit (in Lua) 2× burst at boundaries
Sliding log sorted set of timestamps exact; memory per request
Sliding counter two window counters, weighted approximate, cheap
Token bucket hash tokens, ts refilled in Lua bursts + steady rate

Messaging

  • Pub/Sub: no storage, at-most-once; offline subscribers miss messages; slow ones hit client-output-buffer-limit pubsub 32mb 8mb 60. SPUBLISH (7.0) for sharded channels.
  • Streams: IDs ms-seq, consumer groups, pending entries list until XACK, XAUTOCLAIM for dead consumers, trim with MAXLEN ~.

Operations and memory

Symptom Check Fix
Latency spikes SLOWLOG GET, LATENCY DOCTOR, latest_fork_usec avoid O(N) commands, move persistence to replicas
Big keys redis-cli --bigkeys, --memkeys, MEMORY USAGE k split into buckets, HSCAN, UNLINK
Hot keys redis-cli --hotkeys (LFU policy) local cache, replica reads, key copies
Memory growth INFO memory: used_memory, used_memory_rss, mem_fragmentation_ratio TTLs, listpack-friendly sizes, activedefrag yes
Key listing never KEYS * SCAN 0 MATCH p:* COUNT 1000
  • Compact encodings: hashes and sorted sets stay listpack up to 128 entries / 64-byte values; sets stay intset up to 512 integers. Strings: int, embstr (≤ 44 bytes), raw. Check with OBJECT ENCODING.

Practise with the full question bank on the Redis topic page.

esc