Skip to content

Redis

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.

Pipelines, transactions, and scripts

Atomic Round trips Can branch on read values
Pipeline no — other clients' commands interleave one per batch no
MULTI/EXEC yes one per batch when pipelined no; WATCH aborts on change
Lua script or function yes one yes

MULTI/EXEC and WATCH

  • 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.

Data structures

Core types

Type Encoding when large Typical use
String (≤ 512 MB) raw bytes or integer cache values, counters (INCR), locks
Hash hash table objects with per-field updates; per-field TTL since 7.4
List quicklist (linked listpacks) queues, recent items; index access is O(n)
Set hash table membership, tags, intersections
Sorted set skip list + hash table leaderboards, sliding-window limiters, delay queues
Stream radix tree of listpacks append-only event log with consumer groups

Compact encodings

  • 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.

Common patterns

Distributed locks

  • Acquire with SET lock:k <random token> NX PX 30000; the token identifies the owner.
  • 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.