(a) Hash-slot sharding. Redis Cluster divides the keyspace into 16384 slots. Each key maps to a slot via CRC16(key) mod 16384. Each shard (leader node) owns a contiguous range of slots. The client caches the slot-to-node map and routes commands directly -- no proxy needed.
On cluster rebalance (adding/removing nodes), slots migrate between shards. During migration, a client may receive a MOVED redirect (slot permanently moved -- update map) or ASK redirect (slot mid-migration -- retry once at new node). Smart clients handle both transparently.
# Hash slot calculation example
key = "user:42"
slot = CRC16("user:42") % 16384 # = 7218
# Slot 7218 falls in Shard B's range (5461-10922)
# Client routes SET/GET directly to Shard B leader
# Hash tags force related keys to same slot
"order:{user42}:history" → CRC16("user42") % 16384
"cart:{user42}:items" → CRC16("user42") % 16384
# Both land on the same shard — enables multi-key Lua scripts
(b) Eviction policies. When maxmemory is hit, Redis must evict keys to make room. Policies:
volatile-lru -- evict least-recently-used among keys with a TTL set
allkeys-lru -- evict LRU across all keys (most common for cache use)
allkeys-lfu -- evict least-frequently-used (better for skewed access patterns)
Redis uses approximated LRU: sample N random keys (default 5), evict the one with the oldest last-access timestamp. Not true LRU (no linked list), but very close in practice and O(1) per eviction. Increasing the sample size to 10 gets within 1% of true LRU accuracy.
# redis.conf eviction settings
maxmemory 80gb
maxmemory-policy allkeys-lru
maxmemory-samples 10
(c) Persistence.
- RDB = point-in-time snapshot. Redis forks, child writes entire dataset to disk. Fast restore, but you lose all writes since the last snapshot on crash.
- AOF = append-only file. Every write command appended to log. Three fsync modes:
always (safe, slow), everysec (lose ~1 s on crash -- recommended), no (OS decides).
- RDB+AOF hybrid (Redis 4+) = AOF rewrite starts with an RDB prefix (compact binary) followed by AOF tail of recent writes. Fast restore + minimal data loss. This is the default in Redis 7.
AOF rewrite. Over time, AOF grows large. Redis triggers background rewrite: forks a child process that writes a minimal set of commands to recreate the current dataset. Parent buffers new writes during rewrite, appends them after. Result: compact AOF, no downtime.
(d) Replication. Async leader-to-follower streaming. Leader sends write commands to all replicas after executing locally. On leader failure:
- Sentinel mode: external Sentinel processes monitor the leader. Quorum agrees leader is down, promotes a follower, updates clients.
- Cluster mode: replicas initiate a Raft-like voting process. Other leaders vote to elect one replica as new leader. No external process needed.
Full resync vs partial resync. When a replica reconnects after a brief disconnect, Redis attempts partial resync using the replication backlog buffer (a ring buffer of recent writes on the leader). If the disconnect was short and the backlog hasn't wrapped, only the missed writes are sent. If the backlog overflowed, a full resync is triggered: leader forks, creates RDB, streams entire dataset. This is expensive -- configure repl-backlog-size to at least 256 MB in production to minimize full resyncs.
Write Path — SET key valueMermaid
sequenceDiagram
participant C as Client
participant SC as Smart Client
participant L as Shard Leader
participant AOF as AOF Log
participant R as Replica
C->>SC: SET user:42 "data"
SC->>SC: CRC16("user:42") mod 16384 = slot 7218
SC->>L: route to Shard B (owns 5461-10922)
L->>L: write to in-memory hash table
L->>AOF: append SET command
L-->>C: OK
L->>R: async replicate SET command
R->>R: apply to memory
Cache-aside pattern (the most common Redis usage):
# Read path (cache-aside)
value = redis.GET(key)
if value is None: # cache miss
value = db.query(key) # load from DB
redis.SET(key, value, EX=3600) # populate cache, 1hr TTL
return value
# Write path (invalidate on write)
db.update(key, new_value) # write to DB first
redis.DEL(key) # invalidate cache
# Next read will miss → refill from DB with fresh data
Why DEL on write instead of SET? If you SET the cache on write, a race between two concurrent writers can leave stale data in cache permanently. Consider: Writer A reads DB, Writer B reads DB, Writer B updates DB, Writer B writes cache, Writer A (with stale data) overwrites cache. Now cache is stale forever. DEL is safer: worst case, you get one extra cache miss.
Lua scripting for atomic multi-step operations:
-- Rate limiter: sliding window counter
-- KEYS[1] = rate limit key, ARGV[1] = window (sec), ARGV[2] = max requests
local current = redis.call('INCR', KEYS[1])
if current == 1 then
redis.call('EXPIRE', KEYS[1], ARGV[1])
end
if current > tonumber(ARGV[2]) then
return 0 -- rate limited
end
return 1 -- allowed
Lua scripts execute atomically on a single shard. No other command runs between lines. This is how Redis replaces what would otherwise require distributed locks or database transactions.
Interview answer
"Redis Cluster shards the keyspace into 16384 hash slots using CRC16. Each shard is a leader-replica pair. The smart client caches the slot map and routes directly -- no proxy overhead. Writes go to the leader, get appended to AOF, and async-replicated to followers. On leader failure, cluster voting promotes a replica in ~5 seconds. Eviction uses approximated LRU: sample 5 keys, evict the stalest. Persistence is RDB+AOF hybrid: RDB prefix for fast restore, AOF tail for minimal data loss."