Redis interviews test your knowledge of in-memory data structures, caching patterns, persistence, high availability, and the trade-offs between performance and durability. This guide covers the 50 most common questions — with concise answers and CLI/code examples.
Quick reference
| Topic | Most asked questions |
|---|---|
| Core concepts | In-memory store, single-threaded, data types |
| Data structures | String, List, Set, Sorted Set, Hash, Stream, HyperLogLog |
| Persistence | RDB vs AOF, AOF fsync options |
| Pub/Sub & Streams | PUBLISH/SUBSCRIBE, consumer groups |
| Caching patterns | Cache-aside, write-through, write-behind, TTL |
| Replication | Leader/follower, async replication |
| Sentinel | Automatic failover, quorum |
| Cluster | Hash slots, sharding, CROSSSLOT |
| Transactions | MULTI/EXEC, WATCH, Lua scripting |
| Performance | Pipelining, SCAN vs KEYS, memory optimisation |
Core concepts
1. What is Redis and what makes it different from a traditional database?
Redis (Remote Dictionary Server) is an in-memory data structure store used as a database, cache, message broker, and streaming engine.
Key differences from relational databases:
| Feature | Redis | RDBMS (PostgreSQL/MySQL) |
|---|---|---|
| Storage | RAM (optionally persisted) | Disk |
| Data model | Key-value + rich structures | Tables/rows |
| Query language | Commands (GET, SET, ZADD…) | SQL |
| Primary use | Cache, real-time, pub/sub | Durable transactional data |
| Durability | Optional (RDB/AOF) | Default ACID |
| Speed | Sub-millisecond | Milliseconds |
2. Why is Redis single-threaded, and how does it still achieve high throughput?
Redis uses a single-threaded event loop for command processing (since Redis 6 I/O threads handle network I/O separately, but command execution remains single-threaded).
Why single-threaded works:
- Avoids locking overhead — no mutexes needed
- All data is in RAM — no disk I/O bottleneck
- I/O multiplexing (
epoll/kqueue) handles thousands of connections - Commands are designed to be O(1) or O(log N) — not CPU-bound
Result: Redis easily handles 1 million ops/sec on commodity hardware.
3. What are the core data types in Redis?
| Type | Commands | Use case |
|---|---|---|
| String | GET, SET, INCR, APPEND | Counters, cache, sessions |
| List | LPUSH, RPOP, LRANGE | Queues, timelines |
| Set | SADD, SMEMBERS, SINTER | Tags, unique visitors |
| Sorted Set (ZSet) | ZADD, ZRANGE, ZRANGEBYSCORE | Leaderboards, rate limiting |
| Hash | HSET, HGET, HGETALL | User profiles, objects |
| Stream | XADD, XREAD, XGROUP | Event sourcing, message queues |
| HyperLogLog | PFADD, PFCOUNT | Approximate cardinality |
| Bitmap | SETBIT, GETBIT, BITCOUNT | Feature flags, daily active users |
| Geo | GEOADD, GEODIST, GEORADIUS | Location queries |
4. What is the Redis eviction policy and when does it apply?
When Redis reaches maxmemory, it applies an eviction policy to free space:
| Policy | Behaviour |
|---|---|
noeviction |
Return error on write (default) |
allkeys-lru |
Evict least recently used key (all keys) |
volatile-lru |
Evict LRU key with TTL set |
allkeys-lfu |
Evict least frequently used key (Redis 4+) |
volatile-lfu |
Evict LFU key with TTL set |
allkeys-random |
Evict random key |
volatile-random |
Evict random key with TTL |
volatile-ttl |
Evict key with shortest TTL |
Best practice for a pure cache: use allkeys-lru or allkeys-lfu.
5. How does Redis handle expiry (TTL)?
SET session:u123 "data" EX 3600 # expire in 3600 seconds
EXPIRE session:u123 1800 # update TTL
PERSIST session:u123 # remove TTL
TTL session:u123 # remaining seconds (-1 = no TTL, -2 = key gone)
PTTL session:u123 # milliseconds
Expiry mechanisms:
- Lazy expiry — checked when a key is accessed
- Active expiry — background task samples random keys with TTLs every 100 ms and deletes expired ones
Keys are never returned after expiry, but the memory isn't freed until the background task runs.
Data structures
6. When would you use a Sorted Set instead of a regular Set?
Use a Sorted Set when you need ordering by a numeric score:
# Leaderboard
ZADD leaderboard 9800 "alice"
ZADD leaderboard 7500 "bob"
ZADD leaderboard 12000 "carol"
ZREVRANGE leaderboard 0 2 WITHSCORES # top 3
# carol 12000, alice 9800, bob 7500
ZRANK leaderboard "alice" # rank from lowest (0-indexed)
ZSCORE leaderboard "carol" # 12000
ZRANGEBYSCORE leaderboard 8000 15000 # range by score
Use cases: leaderboards, rate limiters (score = timestamp), priority queues, autocomplete.
7. How do Redis Lists work, and what's the difference between LPUSH and RPUSH?
A Redis List is a doubly linked list of strings:
LPUSH → [C, B, A] RPUSH → [A, B, C]
head tail
LPUSH queue "task3" # push to head: [task3]
RPUSH queue "task1" # push to tail: [task3, task1]
LRANGE queue 0 -1 # [task3, task1]
LPOP queue # removes + returns "task3"
RPOPLPUSH src dst # atomic move between lists
BLPOP queue 5 # blocking pop, waits up to 5s
Queue pattern: RPUSH (producer) + BLPOP (consumer) = FIFO queue.
Stack pattern: LPUSH + LPOP = LIFO.
8. What is a Redis Hash and when should you use one instead of separate String keys?
A Hash stores a map of field-value pairs under one key:
HSET user:123 name "Alice" age "30" city "NYC"
HGET user:123 name # "Alice"
HGETALL user:123 # all fields
HINCRBY user:123 age 1 # increment field
HDEL user:123 city # delete field
Hash vs separate strings:
| Approach | Keys used | Memory | Access pattern |
|---|---|---|---|
Separate strings (user:123:name) |
Many | Higher | Individual fields |
Hash (user:123) |
One | Lower (ziplist < 128 fields) | Object as unit |
Use Hash when you model an object with multiple attributes. Redis uses a compact ziplist/listpack encoding for small hashes.
9. What is a HyperLogLog and when would you use it?
A HyperLogLog estimates the cardinality (number of unique elements) of a set using constant memory (~12 KB) with ≤ 0.81% standard error.
PFADD page:views:2026-07-15 "user1" "user2" "user3" "user1"
PFCOUNT page:views:2026-07-15 # 3 (user1 counted once)
PFADD page:views:2026-07-16 "user2" "user4"
PFMERGE weekly page:views:2026-07-15 page:views:2026-07-16
PFCOUNT weekly # ≈ 4
Use when you need approximate unique counts at scale — daily active users, unique page views, unique IPs — where exact counts would require huge memory.
10. How do Redis Streams differ from Pub/Sub?
| Feature | Pub/Sub | Streams |
|---|---|---|
| Persistence | No — fire and forget | Yes — stored in memory |
| History | No — missed if offline | Yes — read from any offset |
| Consumer groups | No | Yes — parallel consumers |
| Acknowledgement | No | Yes — XACK |
| Message ordering | No guarantees | Yes — monotonic ID |
| Fan-out | Yes — all subscribers receive | Per group |
# Producer
XADD events * action "purchase" user "u123" amount "49.99"
# Consumer group
XGROUP CREATE events orders $ MKSTREAM
XREADGROUP GROUP orders worker1 COUNT 10 STREAMS events >
XACK events orders <id>
Use Streams for durable, replayable event logs; use Pub/Sub for ephemeral real-time notifications.
Persistence
11. What are the Redis persistence options?
| Option | Description | Durability | Performance |
|---|---|---|---|
| No persistence | Pure cache, data lost on restart | None | Fastest |
| RDB (snapshot) | Point-in-time snapshot (.rdb file) |
Minutes of data loss possible | Fast writes |
| AOF (Append Only File) | Log every write command | Configurable (per second or always) | Slower |
| RDB + AOF | Both combined | Best — AOF replayed on top of snapshot | Moderate |
12. Explain RDB persistence in detail.
RDB creates a binary snapshot of the dataset at specified intervals using BGSAVE:
# redis.conf
save 900 1 # save if ≥1 key changed in 900s
save 300 10 # save if ≥10 keys changed in 300s
save 60 10000 # save if ≥10000 keys changed in 60s
dbfilename dump.rdb
Process:
- Redis forks a child process
- Child writes the snapshot to a temp file (copy-on-write)
- Atomic rename replaces old
.rdb
Pros: Fast restart, compact file, great for backups.
Cons: Data loss between snapshots, fork() can cause latency spikes on large datasets.
13. Explain AOF persistence and its fsync options.
AOF logs every write command in text format. On restart, Redis replays the log.
appendonly yes
appendfilename "appendonly.aof"
appendfsync everysec # options: always | everysec | no
appendfsync |
Durability | Performance |
|---|---|---|
always |
0 data loss | Slowest (disk fsync per command) |
everysec |
≤1 second data loss | Good balance (default) |
no |
OS decides | Fastest, unpredictable loss |
AOF rewrite: Over time the AOF grows. Redis rewrites it in the background:
BGREWRITEAOF # manual trigger
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
14. When would you choose RDB over AOF or vice versa?
| Scenario | Recommendation |
|---|---|
| Pure cache (no data loss tolerated) | No persistence or RDB |
| Fast restarts, backups | RDB |
| Minimal data loss (seconds) | AOF with everysec |
| Zero data loss | AOF with always |
| Production with HA | RDB + AOF |
| Microservice session store | AOF (everysec) |
Caching patterns
15. What is the cache-aside (lazy loading) pattern?
The application is responsible for loading data into cache:
def get_user(user_id: str) -> dict:
# 1. Try cache
cached = redis.get(f"user:{user_id}")
if cached:
return json.loads(cached)
# 2. Cache miss — load from DB
user = db.query("SELECT * FROM users WHERE id = %s", user_id)
# 3. Populate cache with TTL
redis.setex(f"user:{user_id}", 3600, json.dumps(user))
return user
Pros: Only requested data is cached; cache failure doesn't break reads. Cons: Cache miss causes 3 trips (read cache → miss → read DB → write cache). Risk of stale data.
16. What is the write-through caching pattern?
On every write, data is written to both the database and the cache synchronously:
def update_user(user_id: str, data: dict):
db.update("UPDATE users SET ... WHERE id = %s", user_id)
redis.setex(f"user:{user_id}", 3600, json.dumps(data))
Pros: Cache always consistent with DB. Cons: Write latency increases; cache fills with rarely-read data (combine with TTL).
17. What is a cache stampede (thundering herd) and how do you prevent it?
When a popular cache key expires, many requests simultaneously hit the database:
Solutions:
- Probabilistic early expiry — recompute before expiry:
import random, math
def get_with_early_expiry(key, ttl, beta=1):
value, expiry = redis.get_with_expiry(key)
if expiry - time.time() < beta * math.log(random.random()) * -1:
# recompute proactively
value = recompute(key)
redis.setex(key, ttl, value)
return value
- Mutex/locking — only one request rebuilds the cache:
if redis.set("lock:user:123", "1", nx=True, ex=10):
try:
value = db.fetch(123)
redis.setex("user:123", 3600, value)
finally:
redis.delete("lock:user:123")
else:
time.sleep(0.1)
return redis.get("user:123") # retry
- Background refresh — serve stale data while refreshing asynchronously.
18. What is cache penetration and how do you mitigate it?
Cache penetration occurs when requests for non-existent keys bypass the cache and hit the database every time (e.g., requesting user:99999 which doesn't exist).
Mitigations:
- Cache null values:
redis.setex("user:99999", 60, "null")— short TTL for non-existent keys - Bloom filter: Fast probabilistic check before cache/DB query
from bloom_filter import BloomFilter
bloom = BloomFilter(max_elements=1000000, error_rate=0.01)
# populate bloom with all valid user IDs at startup
def get_user(user_id):
if user_id not in bloom:
return None # definitely doesn't exist
return get_from_cache_or_db(user_id)
19. What is the difference between cache warming and cache pre-loading?
| Term | Description |
|---|---|
| Cold cache | Cache is empty; all requests go to DB initially |
| Cache warming | After startup, proactively populate cache with frequently accessed data |
| Pre-loading | Load known hot data before traffic arrives (e.g., before a sale event) |
| Lazy loading | Cache is populated on first access (cache-aside) |
20. How do you implement rate limiting with Redis?
Token bucket using Sorted Sets (sliding window):
-- rate_limit.lua
local key = KEYS[1]
local now = tonumber(ARGV[1])
local window = tonumber(ARGV[2]) -- seconds
local limit = tonumber(ARGV[3])
-- Remove expired entries
redis.call('ZREMRANGEBYSCORE', key, 0, now - window * 1000)
local count = redis.call('ZCARD', key)
if count < limit then
redis.call('ZADD', key, now, now)
redis.call('PEXPIRE', key, window * 1000)
return 1 -- allowed
end
return 0 -- rate limited
allowed = redis.eval(script, 1, f"rate:{user_id}",
int(time.time() * 1000), 60, 100)
Simple fixed window with INCR:
key = f"rate:{user_id}:{int(time.time() // 60)}"
count = redis.incr(key)
redis.expire(key, 60)
if count > 100:
raise RateLimitError()
Pub/Sub
21. How does Redis Pub/Sub work?
# Subscriber (connection 1)
SUBSCRIBE news sports
# Publisher (connection 2)
PUBLISH news "Breaking: Redis 8.0 released"
PUBLISH sports "Score: 3-2"
# Pattern subscription
PSUBSCRIBE news.* # matches news.breaking, news.local etc.
PUNSUBSCRIBE news.*
Characteristics:
- Fire-and-forget — no message persistence
- Subscribers must be connected at publish time
- Any client can publish; Redis routes to all subscribers
- No consumer groups — all subscribers get every message
22. What are the limitations of Redis Pub/Sub?
- No persistence — offline subscribers miss messages
- No acknowledgement — no guarantee consumer processed the message
- No consumer groups — can't distribute work across consumers
- Memory unbounded — slow subscriber's buffer grows (
client-output-buffer-limit) - Single shard — Pub/Sub doesn't work across Redis Cluster shards (use Streams or a dedicated channel node)
For reliable messaging, prefer Redis Streams.
Replication
23. How does Redis replication work?
Redis uses asynchronous leader/follower (master/replica) replication:
- Initial sync: Replica connects to leader; leader runs
BGSAVEand streams the RDB + buffer of commands - Ongoing sync: Leader sends write commands to replicas in real time
- Partial resync: On reconnect, Redis uses
PSYNCwith replication ID and offset to avoid full resync when possible
# redis.conf on replica
replicaof 192.168.1.1 6379
masterauth <password>
# Check replication status
INFO replication
Key points:
- Replication is asynchronous — replicas may lag behind
- Multiple replicas can replicate from one leader
- Replicas are read-only by default (
replica-read-only yes)
24. What is replication lag and how do you monitor it?
Replication lag is the delay between a write on the leader and its appearance on the replica.
INFO replication
# master_repl_offset: 12345678
# slave0: ip=192.168.1.2,port=6379,state=online,offset=12345600,lag=0
offset difference = master_repl_offset - replica_offset → bytes behind.
Causes: Network latency, slow replica disk (AOF), heavy write load, replica CPU-bound.
Mitigation: min-replicas-to-write 1 and min-replicas-max-lag 10 make the leader refuse writes if replicas are too far behind.
Sentinel
25. What is Redis Sentinel?
Redis Sentinel provides high availability for a non-Cluster Redis setup:
- Monitoring — checks if leader and replicas are running
- Automatic failover — promotes a replica to leader when the leader fails
- Notification — alerts via API and pub/sub
- Configuration provider — clients ask Sentinel for current leader address
# sentinel.conf
sentinel monitor mymaster 192.168.1.1 6379 2
sentinel down-after-milliseconds mymaster 5000
sentinel failover-timeout mymaster 60000
sentinel parallel-syncs mymaster 1
The 2 at the end is the quorum — number of Sentinels that must agree the leader is down before initiating failover.
26. What is the Sentinel quorum and why does it matter?
The quorum is the minimum number of Sentinel instances that must agree a leader is unreachable (ODOWN — Objectively Down) before failover begins.
Recommended setup: at least 3 Sentinels with quorum 2.
Why odd number? To avoid split-brain: if 2 Sentinels can see the leader and 2 cannot, a quorum of 3 prevents two Sentinels from independently promoting different replicas.
After quorum agreement, one Sentinel is elected leader (by majority vote) to perform the failover.
Redis Cluster
27. How does Redis Cluster work?
Redis Cluster shards data across multiple nodes using hash slots:
- Total: 16,384 hash slots (0–16383)
- Key is assigned:
slot = CRC16(key) % 16384 - Each master owns a range of slots, e.g. node A: 0–5460, node B: 5461–10922, node C: 10923–16383
- Each master has replicas for HA
redis-cli --cluster create \
127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:7002 \
127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005 \
--cluster-replicas 1
Minimum recommended: 6 nodes (3 masters + 3 replicas).
28. What is a CROSSSLOT error in Redis Cluster?
A CROSSSLOT error occurs when a command involves keys that belong to different hash slots (different nodes):
MSET user:1 "alice" user:2 "bob"
# Error: CROSSSLOT Keys in request don't hash to the same slot
Solution: Hash tags — use {tag} to force keys to the same slot:
MSET {user:1}:name "alice" {user:1}:email "a@x.com"
# Both hash on "user:1" → same slot → allowed
Lua scripts and transactions also require all keys in the same slot.
29. How does Redis Cluster handle node failures?
- Nodes send PING messages; if a node doesn't reply within
cluster-node-timeout(default 15s), it's marked PFAIL (possible failure) - If enough nodes agree (majority), the node is marked FAIL (confirmed failure)
- A replica of the failed master requests a vote from other masters
- If it wins the election (majority of masters vote), it becomes the new master
- Slots are reassigned; clients get
MOVEDredirects to the new master
If a master fails and has no replicas, that portion of the keyspace becomes unavailable (cluster-require-full-coverage yes by default causes the whole cluster to stop accepting writes).
30. What is the difference between Redis Cluster and Redis Sentinel?
| Feature | Redis Sentinel | Redis Cluster |
|---|---|---|
| Purpose | HA for single shard | Sharding + HA |
| Sharding | No (single dataset) | Yes (16384 slots) |
| Scaling | Vertical only | Horizontal |
| Multi-key operations | All keys allowed | Same-slot keys only |
| Minimum nodes | 1 master + 2 Sentinels | 6 (3 masters + 3 replicas) |
| Client complexity | Low (Sentinel-aware client) | Higher (cluster-aware client) |
| Use case | Up to ~100GB dataset | Datasets > single node |
Transactions
31. How do Redis transactions work with MULTI/EXEC?
MULTI # start transaction
SET balance:alice 900
SET balance:bob 1100
EXEC # execute atomically
- Commands between
MULTIandEXECare queued, not executed EXECruns all queued commands atomically (no other client's commands interleave)DISCARDdiscards the queue- If a command has a syntax error at queue time, the whole transaction is discarded
- If a command fails at execution time (e.g., wrong type), other commands still execute (Redis does NOT roll back)
32. What is WATCH and how does it implement optimistic locking?
WATCH monitors keys; if any watched key changes before EXEC, the transaction is aborted (returns nil):
with redis.pipeline() as pipe:
while True:
try:
pipe.watch("balance:alice")
current = int(pipe.get("balance:alice"))
if current < 100:
raise InsufficientFunds()
pipe.multi()
pipe.set("balance:alice", current - 100)
pipe.incr("orders:count")
pipe.execute()
break
except WatchError:
continue # retry if key changed
This is optimistic locking — no locks held; retry on conflict.
33. Why might you use Lua scripting instead of MULTI/EXEC?
Lua scripts run atomically on the server without round trips, and allow conditional logic:
-- decrement_if_positive.lua
local val = tonumber(redis.call('GET', KEYS[1]))
if val > 0 then
return redis.call('DECR', KEYS[1])
end
return -1
script = redis.register_script(lua_code)
result = script(keys=["counter"], args=[])
Lua vs MULTI/EXEC:
| Feature | MULTI/EXEC | Lua script |
|---|---|---|
| Conditional logic | No | Yes |
| Atomicity | Yes | Yes |
| Round trips | Multiple | One |
| Error handling | Partial execution | Script aborts fully |
| Debugging | Harder | Harder |
Use Lua for complex atomic operations that need conditionals.
Performance
34. What is Redis pipelining and when should you use it?
Pipelining batches multiple commands into one network round trip:
# Without pipelining — N round trips
for i in range(1000):
redis.set(f"key:{i}", i)
# With pipelining — 1 round trip (bulk)
pipe = redis.pipeline(transaction=False)
for i in range(1000):
pipe.set(f"key:{i}", i)
pipe.execute()
When to use:
- Bulk inserts/updates
- Independent operations (no result dependency between commands)
- Reducing network latency impact
Not suitable for: Transactions needing intermediate results (use Lua instead).
35. Why should you avoid the KEYS command in production?
KEYS pattern scans all keys in O(N) time and blocks the single-threaded server:
KEYS user:* # DANGEROUS — blocks server for large keyspace
Use SCAN instead — iterates in batches without blocking:
SCAN 0 MATCH user:* COUNT 100
# returns (cursor, [keys])
# cursor = 0 means scan complete
cursor = 0
while True:
cursor, keys = redis.scan(cursor, match="user:*", count=100)
process(keys)
if cursor == 0:
break
Similarly: avoid SMEMBERS on huge sets (use SSCAN), HGETALL on huge hashes (use HSCAN), LRANGE 0 -1 on huge lists (use LRANGE with offset).
36. How does Redis memory optimisation work?
Encoding optimisations (automatic):
| Structure | Compact encoding | Threshold |
|---|---|---|
| Hash | listpack |
≤ 128 fields, values ≤ 64 bytes |
| Sorted Set | listpack |
≤ 128 members, values ≤ 64 bytes |
| List | listpack |
≤ 512 items |
| Set | intset |
Integer-only, ≤ 512 members |
Config tuning:
hash-max-listpack-entries 128
hash-max-listpack-value 64
zset-max-listpack-entries 128
Other techniques:
- Use short key names (
u:123vsuser:id:123) - Use Hashes to store small objects instead of separate keys
- Compress values before storing (gzip/snappy in application)
OBJECT ENCODING keyto inspect current encoding
37. What does OBJECT ENCODING tell you and why does it matter?
SET myint 42
OBJECT ENCODING myint # "int"
SET mystr "hello world"
OBJECT ENCODING mystr # "embstr" (≤ 44 bytes) or "raw" (> 44 bytes)
HSET myhash a 1 b 2
OBJECT ENCODING myhash # "listpack" (small) or "hashtable" (large)
Redis uses compact internal encodings for small structures. When a structure exceeds thresholds, it promotes to a less memory-efficient but faster-access encoding. Understanding this helps tune *-max-listpack-* config values to balance memory vs speed.
38. How do you diagnose slow Redis commands?
Slowlog:
# redis.conf
slowlog-log-slower-than 10000 # microseconds (10ms)
slowlog-max-len 128
SLOWLOG GET 10 # last 10 slow commands
SLOWLOG RESET
Latency monitoring:
latency-monitor-threshold 100
LATENCY LATEST
LATENCY HISTORY event
DEBUG SLEEP (simulate slow command in dev):
DEBUG SLEEP 0.1
redis-cli --latency for measuring round-trip latency:
redis-cli --latency -h 127.0.0.1 -p 6379
Security
39. How do you secure a Redis instance?
Essential steps:
# redis.conf
bind 127.0.0.1 # bind to localhost or internal network only
protected-mode yes # refuse connections without auth or bind
requirepass <strong-password>
# TLS (Redis 6+)
tls-port 6380
tls-cert-file /path/to/redis.crt
tls-key-file /path/to/redis.key
tls-ca-cert-file /path/to/ca.crt
# Disable dangerous commands
rename-command FLUSHALL ""
rename-command FLUSHDB ""
rename-command CONFIG ""
rename-command DEBUG ""
ACL (Redis 6+):
ACL SETUSER alice on >password ~user:* &* +get +set +del
ACL LIST
Never expose Redis port to the public internet. Always place Redis behind a firewall or VPC.
40. What is the Redis ACL system?
Redis 6 introduced Access Control Lists (ACL) for fine-grained user permissions:
ACL SETUSER writer on >mypassword ~data:* +SET +GET +DEL
ACL SETUSER readonly on >readpass ~* +GET +MGET +HGET +HGETALL
ACL WHOAMI
ACL LOG # failed auth attempts
| Component | Syntax | Meaning |
|---|---|---|
| Status | on / off |
Enable/disable user |
| Password | >password |
Set password |
| Key pattern | ~data:* |
Allow access to matching keys |
| Channel | &news.* |
Allow pub/sub channels |
| Commands | +GET -DEBUG |
Allow/deny commands |
| Categories | +@read -@dangerous |
Command groups |
Advanced patterns
41. How would you implement a distributed lock with Redis?
Redlock algorithm (simplified single-instance):
import uuid, time
def acquire_lock(redis, resource, ttl_ms=10000):
token = str(uuid.uuid4())
acquired = redis.set(f"lock:{resource}", token,
px=ttl_ms, nx=True)
return token if acquired else None
def release_lock(redis, resource, token):
lua = """
if redis.call('GET', KEYS[1]) == ARGV[1] then
return redis.call('DEL', KEYS[1])
end
return 0
"""
redis.eval(lua, 1, f"lock:{resource}", token)
# Usage
token = acquire_lock(redis, "payment:order:123")
if token:
try:
process_payment()
finally:
release_lock(redis, "payment:order:123", token)
The Lua script for release is atomic — prevents releasing another client's lock.
Multi-node Redlock: acquire lock on majority of N independent Redis nodes.
42. How do you implement a session store with Redis?
import json, uuid
class SessionStore:
def __init__(self, redis, ttl=3600):
self.redis = redis
self.ttl = ttl
def create(self, data: dict) -> str:
session_id = str(uuid.uuid4())
self.redis.setex(f"session:{session_id}",
self.ttl,
json.dumps(data))
return session_id
def get(self, session_id: str) -> dict | None:
raw = self.redis.get(f"session:{session_id}")
if not raw:
return None
self.redis.expire(f"session:{session_id}", self.ttl) # slide TTL
return json.loads(raw)
def delete(self, session_id: str):
self.redis.delete(f"session:{session_id}")
Advantages over cookie sessions: Instant invalidation, scalable across server instances, no client-side storage of sensitive data.
43. How would you build a real-time leaderboard with Redis?
# Add/update score
redis.zadd("leaderboard:global", {"alice": 9800, "bob": 7500, "carol": 12000})
# Top 10
redis.zrevrange("leaderboard:global", 0, 9, withscores=True)
# Player rank (0-indexed from highest)
rank = redis.zrevrank("leaderboard:global", "alice") # 1 (carol is 0)
# Player score
score = redis.zscore("leaderboard:global", "alice") # 9800.0
# Increment score
redis.zincrby("leaderboard:global", 200, "alice") # 10000.0
# Players within score range
redis.zrangebyscore("leaderboard:global", 8000, 15000, withscores=True)
# Pagination
redis.zrevrange("leaderboard:global", 10, 19, withscores=True) # page 2
For time-windowed leaderboards (daily/weekly), use separate keys with TTL or Sorted Sets with timestamp-based score.
44. How does Redis implement geospatial queries?
Redis Geo commands store coordinates as Sorted Set members (score = geohash):
GEOADD restaurants 19.2624 42.4415 "Konoba Stari Grad"
GEOADD restaurants 19.2631 42.4422 "Restoran Galion"
# Distance between two members
GEODIST restaurants "Konoba Stari Grad" "Restoran Galion" km # ≈ 0.09
# Find restaurants within 1km of a point
GEORADIUS restaurants 19.263 42.441 1 km ASC COUNT 5
# Redis 6.2+
GEOSEARCH restaurants FROMLONLAT 19.263 42.441 BYRADIUS 1 km ASC COUNT 5
Precision: ~0.6m error. Internally uses 52-bit geohash stored as ZSet score.
45. What is the LRU approximation in Redis and how does it work?
Redis doesn't implement a true LRU (would require a doubly linked list over all keys). Instead, it uses approximated LRU (since Redis 3.0: LFU approximation):
Approximated LRU:
- On eviction, sample
maxmemory-samplesrandom keys (default 5) - Evict the key with the oldest
lru_clocktimestamp
Increasing maxmemory-samples improves accuracy at the cost of CPU:
maxmemory-samples 10 # closer to true LRU
LFU (Redis 4.0+): Uses a Morris counter (logarithmic counter) to track access frequency, decaying over time. Better than LRU for skewed access patterns.
Common mistakes
| Mistake | Problem | Fix |
|---|---|---|
| Storing large objects in a single key | Slow serialisation, memory spikes | Shard large objects; use Streams for events |
Using KEYS * in production |
Blocks server | Use SCAN with cursor |
| Not setting TTL on cache keys | Memory grows unbounded | Always set EX/PX on cached data |
| Storing sensitive data without TLS | Data in transit visible | Enable TLS (Redis 6+), use requirepass |
| Single Redis instance for session store | SPOF | Use Sentinel or Cluster |
| Exposing Redis port publicly | Full access with no auth | Bind to private network, firewall port 6379 |
| Using MULTI/EXEC when Lua needed | Race conditions with conditional logic | Use atomic Lua scripts |
| Not monitoring replication lag | Stale reads from replica | Alert on lag > threshold, use min-replicas-max-lag |
Redis vs alternatives
| Feature | Redis | Memcached | DynamoDB | Apache Ignite |
|---|---|---|---|---|
| Data types | Rich (10+) | String only | JSON documents | Key-value, SQL |
| Persistence | Optional | No | Yes (managed) | Yes |
| Pub/Sub | Yes | No | Streams | Yes |
| Clustering | Yes (Cluster + Sentinel) | Yes (client-side) | Managed | Yes |
| Lua scripting | Yes | No | No | Yes |
| Geospatial | Yes | No | No | Yes |
| Max value | 512 MB | 1 MB | 400 KB | Configurable |
| Managed cloud | ElastiCache, Upstash | ElastiCache | Native | GridGain Cloud |
| Best for | Cache + data structures + pub/sub | Simple high-perf cache | Serverless K-V at scale | Distributed compute + cache |
FAQ
Q: Can Redis lose data even with AOF enabled?
Yes. With appendfsync everysec, up to 1 second of writes can be lost. With appendfsync always, data is durable but throughput drops significantly. For zero data loss, you need always + synchronous replication (min-replicas-to-write).
Q: What's the difference between EXPIRE and EXPIREAT?
EXPIRE key seconds sets a relative TTL. EXPIREAT key unix-timestamp sets an absolute expiry time. PEXPIRE/PEXPIREAT use milliseconds. On Redis 7.0+, EXPIRE supports NX, XX, GT, LT flags to conditionally update TTL.
Q: Should I use Redis as a primary database? Only for appropriate use cases — leaderboards, session stores, rate limiters, real-time features. Redis is not designed for complex queries, joins, or large relational datasets. For durability-critical data, use a relational DB and cache with Redis.
Q: How does Redis handle memory limits in a Cluster?
Each node enforces its own maxmemory independently. If one shard fills up and eviction can't free enough space, that shard returns OOM errors for writes to its slots. Plan capacity per shard, not total cluster.
Q: What is the Redis keyspace notification feature? Redis can publish notifications when keys expire or are modified, via Pub/Sub channels:
# redis.conf
notify-keyspace-events "Ex" # E = keyspace events, x = expired
# Subscribe
SUBSCRIBE __keyevent@0__:expired
Useful for cache invalidation callbacks, session expiry handling.
Q: What is Upstash and when would you use it instead of self-hosted Redis? Upstash is a serverless Redis service — pay-per-request, scales to zero, no idle cost. Ideal for serverless functions (Vercel Edge, Cloudflare Workers), low-traffic applications, and development environments where managing a Redis server isn't practical. For high-throughput production systems, self-hosted or ElastiCache offers better latency and cost at scale.