Redis
Key-value, data structures, caching, pub/sub.
Theory
Redis is an in-memory data structure store used as cache, database, message broker, and queue. Single-threaded event loop processes commands — extremely low latency (<1ms) but CPU-bound on one core per instance.
Strings: SET/GET with optional EX (seconds TTL). Use for session tokens, feature flags, simple cache. INCR/DECR are atomic — perfect for counters and rate limiting without race conditions.
Hashes: HSET/HGET field-value maps — store objects without serializing entire JSON on each field update. Memory-efficient for objects with many fields accessed individually.
Lists: LPUSH/RPOP for queues (FIFO with BRPOP blocking pop). Streams: XADD/XREAD for append-only log with consumer groups — Kafka-lite for event sourcing within Redis.
Sets (SADD/SMEMBERS) for unique collections; Sorted Sets (ZADD/ZRANGE) for leaderboards — score-based ranking with O(log N) inserts. GEO commands built on sorted sets for location queries.
EXPIRE sets TTL in seconds; EXPIREAT uses Unix timestamp. Keys without TTL persist until manual DEL or eviction. Always set TTL on cache keys — unbounded keys cause OOM. TTL jitter prevents simultaneous expiry stampedes.
Pipelining sends multiple commands without waiting for each response — reduces RTT overhead. Lua scripting (EVAL) runs multiple operations atomically on server — implement compare-and-set, distributed locks (Redlock with caution).
Pub/Sub is fire-and-forget messaging — subscribers miss messages if offline. Redis Cluster shards data across nodes by hash slot (16384 slots); Sentinel provides HA failover for single-master setups. Cluster handles horizontal scale; Sentinel handles automatic failover.
Architecture Diagram
App clients
|
Redis (single or cluster)
|
+----+----+----+
v v v v
cache session rate-limit pub/sub
|
RDB/AOF persistence (optional)Examples
SET session:abc123 "{\"userId\":42}" EX 3600
INCR page:views:homepage
HSET user:42 name "Ada" email "ada@example.com"
ZADD leaderboard 9500 "player1" 8200 "player2"
ZRANGE leaderboard 0 9 WITHSCORES REV
EXPIRE cache:product:99 300
TTL cache:product:99
# Python pipeline — batch commands
pipe = r.pipeline()
for key in keys:
pipe.get(key)
results = pipe.execute()
# Atomic lock with Lua
SET lock:resource NX EX 30
# Lua: if GET(key) == token then DEL(key)
EVAL "if redis.call('get',KEYS[1])==ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end" 1 lock:resource mytoken
XADD events * type order_placed order_id 123
XREAD COUNT 10 STREAMS events 0
XGROUP CREATE events order_workers $ MKSTREAM
XREADGROUP GROUP order_workers c1 COUNT 5 STREAMS events >
PUBLISH notifications "user:42:login"
SUBSCRIBE notifications
Key Concepts
Interview Questions
When do you use Redis Hash vs String for an object?
Hash: update individual fields without reading/writing entire object — memory efficient with many small objects. String: store serialized JSON when you always read/write the whole object. Hash wins for partial updates (user profile fields).
How do INCR and SETNX enable atomic operations?
Redis commands are atomic (single-threaded). INCR counter avoids read-modify-write races. SETNX (SET if Not eXists) implements distributed lock acquisition. Combine SET key value NX EX 30 for lock with auto-expiry. Always set TTL on locks to prevent deadlocks.
What is pipelining and when does it help?
Send multiple commands before reading responses — one round trip for N commands instead of N trips. Critical for bulk reads/writes over network. Not the same as transactions (MULTI/EXEC) — pipeline has no atomicity guarantee across commands.
Redis pub/sub vs Streams — which to use?
Pub/sub: real-time broadcast, no persistence, subscribers miss offline messages. Streams: persisted append-only log, consumer groups with ack/retry, replay capability. Use pub/sub for live notifications; Streams for reliable event processing.
Redis Cluster vs Sentinel?
Sentinel: monitors master, automatic failover to replica, single-shard HA. Cluster: partitions data across nodes (hash slots), horizontal scaling beyond one machine's RAM. Use Sentinel for HA with moderate data; Cluster when data exceeds single node memory or write throughput.
How do you prevent Redis OOM?
Set maxmemory and maxmemory-policy (allkeys-lru). Always TTL cache keys. Monitor used_memory and evicted_keys. Avoid storing large collections without bounds (unbounded LIST growth). Use Redis Cluster to shard large datasets.
FAANG: Is Redlock a safe distributed lock?
Controversial. Martin Kleppmann showed edge cases with clock drift and fencing tokens missing. For strict correctness, use a coordination service (ZooKeeper, etcd) or database locks. Redlock acceptable for efficiency where occasional double-execution is tolerable (idempotent workers).
When should you NOT use Redis as primary database?
When you need durable complex queries, joins, or strong consistency guarantees. Redis persistence (RDB/AOF) has windows of data loss. Use as cache/session store/queue; use PostgreSQL/MySQL as source of truth. Cache-aside pattern with TTL and invalidation.
Best Practices
- Always set TTL on cache keys — unbounded growth fills memory and triggers eviction of useful data.
- Use
SCANinstead ofKEYS *in production — KEYS blocks the single-threaded event loop. - Choose the right structure: Hashes for objects, Sorted Sets for rankings, Lists for queues — not everything as JSON strings.
- Use
INCRfor counters and rate limits — atomic without WATCH/MULTI complexity. - Configure
maxmemory-policyexplicitly (e.g. allkeys-lru) — default noeviction causes write failures when full. - Enable AOF or RDB persistence if data loss on restart is unacceptable — pure cache can skip persistence.
Common Mistakes
- Using Redis as primary database without persistence — restart wipes all data.
- Storing large values (>100KB) in single keys — blocks the event loop during serialization.
- Running
KEYS pattern*in production — O(N) scan freezes Redis for seconds on large datasets. - Assuming MULTI/EXEC rolls back on error — Redis executes all commands; partial failure leaves inconsistent state.
- No memory limit — one bad client can OOM the host; set maxmemory and eviction policy.
Cheat Sheet
Practical Exercises
Implement 100 req/min per IP using INCR + EXPIRE. Test with a script exceeding the limit and verify 429 behavior.
Build a game scoreboard. Use ZADD, ZREVRANGE WITHSCORES, and ZRANK. Add TTL on inactive player keys.
Cache Postgres query results with SETEX. On cache miss, load from DB. Measure hit ratio under load.