You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
What experienced Redis users forget before an interview, grouped by subtopic. For caching strategies and rate-limiting algorithms, see system.md; for Redlock and delivery semantics, see distributed.md.
Execution model
Single-threaded execution
Commands execute one at a time on the main thread, so each command is atomic and needs no locks.
One slow command (KEYS *, SMEMBERS on a huge set, DEL of a big key) blocks every other client for its duration.
I/O threads (io-threads, since 6.0; rewritten in 8.0) parse requests and write replies; commands still run on the main thread. Off by default.
Scale CPU-bound workloads with more shards (Cluster), not bigger machines.
Slow and blocking operations
SCAN iterates with a cursor instead of KEYS; it can return a key twice and only guarantees keys present for the whole iteration; COUNT is a hint.
UNLINK (4.0+) frees a big value in a background thread; DEL frees it synchronously unless lazyfree-lazy-user-del yes.
SLOWLOG records commands slower than 10 ms by default, counting execution time only, not network or queueing.
Find offenders with redis-cli --bigkeys / --memkeys, and since 8.6 the HOTKEYS command.
Transactions don't roll back: a command that fails at run time (wrong type) leaves the others applied; an error while queuing (bad syntax) aborts the whole EXEC.
WATCH makes EXEC return null if a watched key changed — an optimistic check-and-set; retry the read-modify-write loop.
In Cluster, every key in the transaction must hash to the same slot.
Lua scripts and functions
A script runs without interleaving but doesn't roll back: a runtime error keeps the writes made before it.
A long script blocks everyone: past busy-reply-threshold (5 s) other clients get BUSY, and SCRIPT KILL works only if it hasn't written yet — otherwise SHUTDOWN NOSAVE.
EVALSHA runs a cached script by hash; the cache isn't persisted, so clients must handle NOSCRIPT by resending the source.
Functions (7.0+) are named libraries loaded once with FUNCTION LOAD and called with FCALL; they're persisted and replicated like data.
Pass every key the script touches in KEYS, so Cluster can route it and check slots.
Small collections use a flat listpack: hashes up to 512 fields, sets and sorted sets up to 128 members, values ≤ 64 bytes; integer-only sets up to 512 use an intset.
Crossing a threshold converts the key to a hash table or skip list, which takes several times the memory; OBJECT ENCODING shows the current form.
Many small hashes beat millions of top-level keys: each key costs tens of bytes of overhead.
Sorted set details
ZADD, ZRANK, ZREM are O(log n); ZSCORE is O(1) via the hash; range by rank or score is O(log n + m).
Scores are 64-bit floats: integers are exact only up to 2^53, so don't pack large IDs into scores.
Members with equal scores sort lexicographically; ZRANGE ... BYLEX works only when every member has the same score.
Probabilistic and specialized types
HyperLogLog counts distinct items in 12 KB with 0.81% standard error; PFMERGE unions them.
Bitmaps are bit operations on strings — one bit per user ID for daily actives.
Geo commands store geohashes as sorted-set scores and search by radius or box.
Since 8.0, the former Redis Stack modules ship built in: JSON, time series, Bloom and Cuckoo filters, count-min sketch, top-k, t-digest, vector sets, and the query engine.
Expiration and eviction
Key expiration
A TTL belongs to the key; SET without KEEPTTL clears it, while INCR, HSET, or LPUSH keep it.
Per-field TTLs on hashes (HEXPIRE) exist since 7.4; before that, expiry was whole-key only.
Expired keys are deleted lazily on access plus by a background sampler, so memory lags behind expirations.
Replicas don't expire keys themselves; they wait for the primary's DEL, but reads of a logically expired key already return nothing.
Eviction policies
maxmemory is 0 (unlimited) by default on 64-bit, so an unbounded cache grows until the kernel OOM killer steps in.
maxmemory-policy defaults to noeviction: at the limit, writes fail with an OOM error while reads continue.
Caches use allkeys-lru or allkeys-lfu; allkeys-lrm/volatile-lrm (8.6+) evict the least recently modified keys.
volatile-* policies evict only keys with a TTL, so without TTLs they behave like noeviction.
Leave headroom: replication and AOF buffers aren't counted against maxmemory.
Approximated LRU and LFU
LRU is approximated: evict the oldest of maxmemory-samples (5) random keys, helped by a pool of good candidates; 10 samples is close to true LRU.
LFU keeps an 8-bit logarithmic (Morris) counter per key that decays every minute (lfu-decay-time 1), so formerly hot keys age out.
Persistence
RDB snapshots
RDB forks; the child writes a point-in-time snapshot while the parent keeps serving, sharing memory copy-on-write.
Under heavy writes the fork can approach 2× memory; set vm.overcommit_memory = 1 and disable transparent huge pages — fork and overcommit in os.md.
Default save points: after 1 hour with ≥ 1 change, 5 min with ≥ 100, 1 min with ≥ 10,000 — everything since the last snapshot is lost on a crash.
stop-writes-on-bgsave-error yes (default) rejects writes after a failed snapshot.
AOF
AOF logs every write; it's off by default (appendonly no) and, when on, takes precedence over RDB at startup.
appendfsync: always (slow, safest), everysec (default, loses up to ~1 s), no (the OS decides, ~30 s on Linux).
A rewrite (BGREWRITEAOF) forks to compact the log; since 7.0 the AOF is multi-part — an RDB-format base file plus incremental files and a manifest.
A pure cache can run without persistence; even with AOF, async replication still loses writes on failover.
Replication and high availability
Replication
Replication is asynchronous: the primary acknowledges writes before replicas have them.
A reconnecting replica resumes with PSYNC (replication ID + offset) if the gap still fits the replication backlog (1 MB default); otherwise it does a full resync — a fork and an RDB transfer.
WAIT blocks until n replicas acknowledge, and WAITAOF (7.2+) until they fsync; both shrink the loss window but don't make Redis strongly consistent.
min-replicas-to-write (off by default) rejects writes when fewer replicas than that acknowledged within min-replicas-max-lag seconds, limiting what an isolated primary accepts.
A primary with persistence off that auto-restarts comes back empty, and its replicas sync to the empty dataset.
Sentinel
Sentinel monitors a primary, promotes a replica on failure, and tells clients the new primary; run at least 3 on independent hosts.
The quorum sets how many Sentinels must agree the primary is down; the failover itself still needs a majority of Sentinels.
Clients must be Sentinel-aware and ask it for the current primary, not cache the address.
Acknowledged writes not yet replicated are lost on failover, and an old primary on the minority side keeps accepting writes unless min-replicas-to-write is set and it loses sight of enough replicas.
Cluster
Hash slots
Redis Cluster splits keys into 16,384 slots (CRC16(key) mod 16384); each primary owns a range.
Hash tags hash only the part in braces, so {user42}:cart and {user42}:orders share a slot.
Multi-key commands, transactions, and scripts need all keys in one slot, else CROSSSLOT; only database 0 exists.
Redirections and resharding
MOVED: the slot lives elsewhere — the client updates its slot map. ASK: the slot is mid-migration — retry once on the target after ASKING, without updating the map.
Smart clients cache the slot map (CLUSTER SHARDS) and refresh it on MOVED.
Since 8.4, CLUSTER MIGRATION moves whole slots atomically instead of key by key with ASK redirects.
Cluster failure handling
Nodes gossip over the cluster bus (data port + 10000); a primary unreachable for cluster-node-timeout (15 s) is marked failed by a majority of primaries, and one of its replicas is promoted.
Not strongly consistent: async replication loses acknowledged writes, and a primary on the minority side of a partition accepts writes until the node timeout.
With cluster-require-full-coverage yes (default), the whole cluster stops serving if any slot has no live owner.
Messaging
Pub/sub
Pub/sub is fire-and-forget: a disconnected subscriber misses messages for good.
A slow subscriber is disconnected once its output buffer exceeds 32 MB, or 8 MB for 60 s.
In Cluster, PUBLISH is broadcast to every node; sharded pub/sub (SPUBLISH, 7.0+) keeps a channel on its slot's shard.
Keyspace notifications are off by default and travel over pub/sub, so they're lossy too.
Streams
XADD appends entries with time-based IDs (<ms>-<seq>); XREADGROUP gives each entry to one consumer in a group.
Delivered entries sit in the pending entries list until XACK — at-least-once delivery.
A crashed consumer's entries are taken over with XAUTOCLAIM (6.2+) or XREADGROUP ... CLAIM (8.4+); XNACK (8.8+) releases them explicitly.
Streams grow until trimmed: XADD ... MAXLEN ~ n trims approximately and cheaply.
Since 8.6, XADD ... IDMP deduplicates producer retries.
Lists as queues
LPUSH + BRPOP is the simplest queue, but a worker crash after the pop loses the job.
Reliable queue: BLMOVE (6.2+) atomically moves the job to a per-worker processing list; remove it after success, re-queue stale ones.
Prefer Streams when you need acknowledgments, replay, or several consumer groups; Kafka-style logs in system.md.
Release only if the token still matches: compare-and-delete in a Lua script, or DELEX key IFEQ token since 8.4.
The TTL can expire mid-work (GC pause, slow I/O), giving two holders — protect the resource with fencing tokens; Redlock's safety debate in distributed.md.
With async replication, a failover can lose the lock key and let a second client acquire it.
Caching with Redis
Strategies (cache-aside, write-through) and stampede fixes are in system.md; give every key a TTL with jitter.
Client-side caching (6.0+): with CLIENT TRACKING, Redis sends invalidation messages for keys a client has read, so it can keep a local copy.
Store objects as hashes when you update single fields; as serialized strings when you always read the whole object.
Counters and rate limiters
INCR then EXPIRE as two calls can leave a counter without a TTL if the client dies between them — wrap them in MULTI or a Lua script.
Since 8.8, INCREX increments with bounds and an expiry in one command; with EX ... ENX the TTL is set only on creation, giving a fixed-window limiter.
Sliding-window log: a sorted set of timestamps — ZREMRANGEBYSCORE old entries, ZADD now, ZCARD; algorithm trade-offs in system.md.
Operations
Memory
Redis uses jemalloc; mem_fragmentation_ratio (RSS ÷ used) below 1 means swapping; a high value can be fragmentation, but confirm with allocator_frag_ratio and absolute bytes before enabling activedefrag.
MEMORY USAGE reports one key's footprint; INFO memory the totals.
Freed memory isn't always returned to the OS right away, so RSS can stay high after deletes.
Security defaults
The shipped redis.conf binds to 127.0.0.1, and protected mode refuses remote clients when no password is set; exposing it without auth invites remote takeover (CONFIG SET dir + SAVE).
ACLs (6.0+) give users per-command and per-key-pattern permissions; TLS is built in since 6.0.
Licensing and Valkey
Redis 7.4 (2024) moved from BSD to RSALv2/SSPLv1; the Linux Foundation forked 7.2.4 as Valkey (BSD), which several clouds now offer as managed Redis.
Redis 8.0 added AGPLv3 as a third license option and renamed Community Edition to Redis Open Source.