SQL vs NoSQL· 20 min

Redis Use Cases

Cache, sessions, rate limiting, leaderboards

NoSQLIntermediate20 min read

In-memory key-value store used for caching, sessions, rate limiting, leaderboards, pub/sub, and queues.

Redis — Use Cases & Patterns — diagram
🖱️ Drag nodes · Scroll to zoom · Drag background to pan
RedisIn-memory · 100k ops/sCacheSessionsRate LimiterLeaderboardPub/SubQueue

Explanation

Redis is single-threaded, in-memory, 100k+ ops/sec. Data structures: strings, lists, sets, sorted sets (leaderboards), hashes, streams. Persistence: RDB (snapshot) or AOF (append-only log). Use cases: cache (TTL), session store, rate limiter (atomic INCR), leaderboard (ZSET), pub/sub, queue (LPUSH/BRPOP).

Example

-- Rate limiting (token bucket per user per minute)
local key = "rl:" .. user_id .. ":" .. math.floor(time() / 60)
local count = redis.call("INCR", key)
if count == 1 then redis.call("EXPIRE", key, 60) end
if count > 100 then return 0 end
return 1
-- Leaderboard
ZADD leaderboard 1500 "alice"
ZREVRANGE leaderboard 0 9 WITHSCORES

Key points

  • →Single-threaded = no locks, avoid slow commands (KEYS)
  • →In-memory = fast but volatile — use AOF/RDB
  • →TTL on every key — Redis is not a primary datastore
  • →Pipelining: 10-100x speedup
  • →Eviction: allkeys-lru is most common

Quick interview answers

Is Redis durable?
AOF + fsync=everysec loses at most 1 sec. RDB is a snapshot. For true durability, use SQL + Redis as cache.
Redis vs Memcached?
Memcached is simpler (strings only). Redis has more data structures, persistence, pub/sub, clustering.

DBMS deep dive

Redis — Use Cases & Patterns: deep dive

Redis is a set of in-memory data structures you reach over the network, and each structure maps cleanly to a production use case. The skill interviewers check is picking the right structure and command for the job, and knowing what breaks when memory, persistence or atomicity are pushed.

Data structure to use case map

StructureUse caseKey commandsComplexity
StringCache entry, counter, distributed lockSET key val EX 60 NX, GET, INCRO(1)
HashSession or user profile fieldsHSET, HGET, HINCRBYO(1) per field
ListSimple job queue, recent-activity feedLPUSH, BRPOP / BLMOVE, LTRIMO(1) at the ends
SetUnique visitors per day, tags, mutual friendsSADD, SISMEMBER, SINTERO(1) membership
Sorted setLeaderboard, sliding-window rate limit, delayed jobsZADD, ZREVRANGE, ZRANGEBYSCORE, ZREMRANGEBYSCOREO(log n)
StreamDurable event log with consumer groupsXADD, XREADGROUP, XACKO(1) append
HyperLogLogApproximate distinct count (~0.81% error, 12 KB)PFADD, PFCOUNTO(1)
BitmapDaily active flags per user IDSETBIT, BITCOUNTO(1) per bit
GeoNearby drivers or storesGEOADD, GEOSEARCHO(log n + m)

Worked example: a cache that survives a traffic spike

In cache-aside, the app reads Redis first. On a miss it loads the value from the database and writes it to Redis with a TTL. On writes it updates the database and then deletes the key rather than overwriting it, which avoids racing writers leaving a stale value. The danger is a cache stampede: a hot key expires and thousands of requests hit the database at once. Two fixes help. Add random jitter to TTLs so keys do not expire together, and let only one request rebuild the value while the others briefly wait or serve the stale copy.

Redis commands for cache, lock, leaderboard and queue
# Cache-aside with jittered TTL (300s + random 0-60s chosen by the app)
GET product:42
SET product:42 "{...json...}" EX 337

# Rebuild lock so only one worker refills a hot key
SET lock:product:42 "worker-7f3a" NX PX 5000   # OK = you own it, nil = someone else does

# Safe release: delete only if you still own it (atomic Lua script)
EVAL "if redis.call('GET', KEYS[1]) == ARGV[1] then return redis.call('DEL', KEYS[1]) else return 0 end" 1 lock:product:42 worker-7f3a

# Leaderboard: top 3 and one player's rank (0-based)
ZINCRBY lb:weekly 50 "asha"
ZREVRANGE lb:weekly 0 2 WITHSCORES
ZREVRANK lb:weekly "asha"

# Reliable queue: move the job to a processing list atomically
LPUSH jobs '{"id":981}'
BLMOVE jobs jobs:processing RIGHT LEFT 5

Why the Lua script for releasing the lock? A plain DEL could delete a lock that already expired and was taken by another worker. Comparing the token and deleting must happen as one atomic step. Scripts run atomically because Redis executes commands on a single thread. Since Redis 6, I/O threads can handle networking, but command execution is still serial.

Sliding-window rate limiting with a sorted set

A fixed-window counter (INCR plus EXPIRE) lets a client send up to twice the limit across a window boundary. A sliding window stores each request's timestamp as a sorted-set member. Remove members older than the window with ZREMRANGEBYSCORE, count what is left with ZCARD, and add the new request with ZADD, all in one MULTI block or Lua script. It is more accurate, at the cost of memory per request.

Memory, eviction and persistence trade-offs

  • Set maxmemory and a policy. allkeys-lru or allkeys-lfu suit pure caches. volatile-* policies evict only keys that have a TTL. noeviction returns errors on writes, which is what you want for data you cannot lose.
  • RDB snapshots fork the process. Copy-on-write can briefly need much more memory on write-heavy instances.
  • Replication is asynchronous, so a failover can lose recent writes. That is one reason Redis locks are advisory for efficiency, not a correctness guarantee. Use fencing tokens if correctness depends on the lock.
  • Pub/Sub is fire-and-forget: offline subscribers miss messages. Use Streams when consumers must catch up.

For how Redis fits next to a primary database, see SQL vs NoSQL.

Common mistakes

  • Big keys, such as a hash with millions of fields or a 50 MB string. Deleting or reading one blocks the server. Use UNLINK for background deletion and split big keys into smaller ones.
  • Running KEYS * or SMEMBERS on huge sets in production instead of SCAN and SSCAN.
  • Setting the value and the TTL in two separate commands. A crash in between leaves a key that never expires. Use SET ... EX.
  • Sending many round trips per request instead of pipelining or using a multi-key command such as MGET.
  • Assuming multi-key commands work across a Redis Cluster. Keys must share a hash slot, which you can force with hash tags like {user:42}:cart.

Interview questions

›How do MULTI/EXEC transactions differ from SQL transactions?

MULTI queues commands, and EXEC runs them back to back with nothing interleaved. There is no rollback, though: if one command fails at runtime, the others still apply. WATCH adds optimistic concurrency by aborting EXEC if a watched key changed.

›How would you design a real-time leaderboard for 10 million players?

Use a sorted set with score = points. ZINCRBY updates are O(log n), ZREVRANGE fetches the top N, and ZREVRANK gives a player's rank. Shard by season or region if one key gets too large, and store player profiles in hashes or the main database.

›What happens when Redis reaches maxmemory?

It applies the configured eviction policy, sampling keys to approximate LRU or LFU. With noeviction, commands that would add memory return an OOM error, while reads keep working.

›Is Redlock safe for distributed locking?

It is debated. Redlock depends on bounded clock drift and process pauses, so a GC pause can let two clients believe they hold the lock. For efficiency locks it is fine. For correctness, use fencing tokens checked by the protected resource, or a consensus system like etcd or ZooKeeper.

›When would you use Redis Streams over a List-based queue?

Use Streams when you need consumer groups, per-message acknowledgement, re-delivery of pending messages after a consumer crash, and the ability to replay history. A List queue is simpler but has no built-in acknowledgement or fan-out.

More Database Management Systems

Compare this lesson with the other 8 concepts in the course. Expand any card for its design, trade-offs and diagram.

Category:
Difficulty:
Showing 8 of 8