Database Systems · Guide

Redis

Key-value, data structures, caching, pub/sub.

— min read Database Systems

Theory

Redis is in-memory with optional persistence — use the right structure (hash, zset, stream) and always set TTLs on cache keys.

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

redis — data types & TTL
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
redis — pipelining & Lua
# 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
redis — streams & pub/sub
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

Data modelHow Redis structures and queries data
ConsistencyGuarantees under failure and replication
Scaling axisVertical vs horizontal growth patterns
OpsBackup, monitoring, and upgrade path

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 SCAN instead of KEYS * 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 INCR for counters and rate limits — atomic without WATCH/MULTI complexity.
  • Configure maxmemory-policy explicitly (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

Watch for these patterns — they cause most production incidents around Redis.
  • 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

SET / GET / DELBasic string key operations
SETEXSet key with TTL in seconds
HSET / HGETHash field get/set
LPUSH / RPOPQueue: push left, pop right
SADD / SMEMBERSSet add and list all members
ZADD / ZRANGESorted set — leaderboards, rankings
INCRAtomic integer increment
EXPIRE / PERSIST / TTLManage key expiry
MULTI / EXECTransaction batch (not rollback on error)
SCANIterate keys without blocking (not KEYS)

Practical Exercises

Rate limiter with INCR

Implement 100 req/min per IP using INCR + EXPIRE. Test with a script exceeding the limit and verify 429 behavior.

Leaderboard with ZADD

Build a game scoreboard. Use ZADD, ZREVRANGE WITHSCORES, and ZRANK. Add TTL on inactive player keys.

Cache-aside pattern

Cache Postgres query results with SETEX. On cache miss, load from DB. Measure hit ratio under load.