allkeys-lru Evicted Rate-Limit — Redis Interview Gotcha
allkeys-lru evicted TTL-less rate-limit keys, causing 200 not 429.
20+ years shipping production code across the stack, with years spent interviewing engineers. Everything here is grounded in real deployments.
- ✓Solid grasp of fundamentals
- ✓Comfortable reading code examples
- ✓Basic production concepts
- Redis is an in-memory data structure server, not just a cache — it natively supports strings, hashes, lists, sorted sets, streams, and more
- Single-threaded command execution eliminates lock contention and makes latency predictable — I/O is multi-threaded in v6+
- Persistence is opt-in: RDB snapshots (compact, minutes of data loss) vs AOF logs (near-zero loss, larger) — use both for production
- Eviction policies like allkeys-lru silently kick in when maxmemory is hit — wrong policy can evict important non-cache keys
- Lua scripts (EVAL) provide true atomicity for conditional operations like rate limiting — MULTI/EXEC does NOT roll back on runtime errors
Redis is an in-memory data structure store that operates as a networked, single-threaded key-value server with optional durability. It's not 'just a cache' because it provides first-class data structures (strings, hashes, lists, sets, sorted sets, streams, bitmaps, HyperLogLog, geospatial indexes) with atomic operations, Lua scripting, transactions, pub/sub, and configurable persistence via RDB snapshots or AOF logs.
Its single-threaded event loop guarantees strong consistency for a single instance, but that same design means a single slow command (like KEYS * or a blocking operation) can stall all clients. Redis is the right choice when you need sub-millisecond latency, high throughput (100k+ ops/sec on modest hardware), and built-in data structures for leaderboards, rate limiters, session stores, or real-time analytics.
It's the wrong choice when you need complex SQL joins, multi-key ACID transactions across shards, or data that exceeds available RAM without predictable access patterns — that's when you reach for PostgreSQL, Cassandra, or ScyllaDB. Production Redis deployments commonly use 4–8 GB instances per shard, with eviction policies (like allkeys-lru) that silently drop keys when memory fills, which is exactly where the 'allkeys-lru evicted rate-limit' gotcha bites: you think you're rate-limiting, but Redis is evicting your rate-limit keys under memory pressure, silently allowing traffic spikes through.
Imagine your office has a massive filing cabinet (your database) and a sticky-note board right next to your desk (Redis). Every time you need a document, walking to the cabinet takes 30 seconds. But if you stick the most-requested documents on your board, you grab them in 2 seconds. Redis is that sticky-note board — blazing-fast, lives in memory, and holds your hottest data so your app never has to make the slow trip to the filing cabinet. The catch? Your board has limited space, and if the office loses power, the sticky notes are gone unless you back them up.
Redis shows up in almost every modern backend stack — from session management at Netflix scale to real-time leaderboards in gaming apps to rate-limiting at API gateways. If you're interviewing for any backend, full-stack, or DevOps role, expect at least two or three Redis questions. Interviewers don't ask them to trip you up — they ask because Redis is one of those tools where misuse causes production fires, and they want to know you understand the trade-offs.
What Redis Actually Is — And Why It's Not 'Just a Cache'
Redis stands for Remote Dictionary Server. Yes, it's famous as a cache, but calling it 'just a cache' in an interview is a red flag. Redis is an in-memory data structure store. It natively understands strings, lists, sets, sorted sets, hashes, bitmaps, hyperloglogs, and streams. That means it's not a dumb key-value bucket — it can perform operations directly on those structures without you pulling data out, modifying it in application code, and pushing it back.
Why does this matter? Take a leaderboard. In a relational database you'd SELECT all scores, sort them in application memory, and return the top 10. With Redis Sorted Sets, you call ZREVRANGE leaderboard 0 9 and Redis returns the top 10 in O(log N) time — atomically, server-side, with no round-trip logic. That's the real power: moving computation closer to the data.
Redis is single-threaded for command execution (as of v6, I/O is multi-threaded), which sounds like a weakness but is actually why it's so predictable. No lock contention. No deadlocks. One command finishes before the next starts.
# Connect to Redis CLI redis-cli # ── STRING: Simple key-value with TTL (time-to-live) ── SET user:1001:session_token "abc123xyz" EX 3600 # EX 3600 means this key auto-deletes after 1 hour # Perfect for session tokens — no manual cleanup needed GET user:1001:session_token # Returns: "abc123xyz" TTL user:1001:session_token # Returns: 3598 (seconds remaining — live countdown) # ── HASH: Store a user object without serializing to JSON ── HSET user:1001 name "Priya Sharma" email "priya@example.com" plan "pro" # Redis stores each field separately — you can update ONE field # without reading and rewriting the whole object HGET user:1001 plan # Returns: "pro" HGETALL user:1001 # Returns all fields: name, email, plan # ── SORTED SET: Real-time leaderboard ── ZADD game:leaderboard 9450 "alice" ZADD game:leaderboard 8820 "bob" ZADD game:leaderboard 9900 "carol" # Top 3 players, highest score first (0-indexed range) ZREVRANGE game:leaderboard 0 2 WITHSCORES # Returns: # 1) "carol" # 2) "9900" # 3) "alice" # 4) "9450" # 5) "bob" # 6) "8820" # ── LIST: Message queue pattern ── LPUSH email:queue "welcome:user:1002" # Push to the LEFT (head) LPUSH email:queue "receipt:order:5501" RPOP email:queue # Pop from the RIGHT (tail) — FIFO queue # Returns: "welcome:user:1002"
Connecting from Java: Spring Data Redis & Jedis
In a production Spring Boot environment, you won't be using the CLI. You'll likely use Spring Data Redis with Lettuce or Jedis. The key is using the RedisTemplate or StringRedisTemplate to ensure thread-safe operations and proper serialization. Interviewers often check if you know how to handle connection pooling and why Lettuce (asynchronous/non-blocking) is generally preferred over Jedis (synchronous) in high-concurrency Spring Boot applications.
package io.thecodeforge.redis; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; @Configuration public class RedisConfig { /** * Production-grade RedisTemplate configuration with JSON serialization. * By default, Spring uses JdkSerializationRedisSerializer (binary), * which is unreadable in CLI. JSON is better for debugging. */ @Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) { RedisTemplate<String, Object> template = new RedisTemplate<>(); template.setConnectionFactory(factory); // Use String serializer for keys template.setKeySerializer(new StringRedisSerializer()); // Use JSON serializer for values to store complex Java objects template.setValueSerializer(new GenericJackson2JsonRedisSerializer()); return template; } }
Persistence, Eviction & the Trade-offs That Cause Production Incidents
The single most dangerous misconception about Redis is treating it as a durable store by default. It isn't. By default, Redis is in-memory only — restart the process and your data is gone. Redis gives you two persistence mechanisms: RDB (Redis Database snapshots) and AOF (Append-Only File), and you need to understand both for interviews and for production.
RDB takes point-in-time snapshots — like a photograph of your data every N seconds if M keys changed. It's compact, fast to restore, but you can lose up to the last snapshot window of writes. AOF logs every write operation — like a transaction log. Slower to restore, larger on disk, but much less data loss risk (configurable to fsync every second or every command).
Eviction policy is equally critical. When Redis hits its maxmemory limit, it has to decide what to drop. The allkeys-lru policy evicts the least-recently-used key across all keys — great for a pure cache. The volatile-lru policy only evicts keys that have a TTL set — useful when some keys must survive (like a rate limit counter with no TTL). Picking the wrong eviction policy is a silent killer: you'll see cache misses spike with no obvious error.
# ── Check current persistence config ── redis-cli CONFIG GET save # Default output — snapshot triggers: # 1) "save" # 2) "3600 1 300 100 60 10000" # Meaning: snapshot if 1 change in 3600s, OR 100 changes in 300s, OR 10000 changes in 60s # ── Enable AOF (Append-Only File) for better durability ── redis-cli CONFIG SET appendonly yes redis-cli CONFIG SET appendfsync everysec # everysec = fsync every second — best balance of performance vs durability # always = fsync on every write — safest but ~10x slower # no = OS decides when to flush — fastest but data loss risk # ── Set a memory limit and eviction policy ── redis-cli CONFIG SET maxmemory 256mb redis-cli CONFIG SET maxmemory-policy allkeys-lru # allkeys-lru: when full, evict the least-recently-used key from ANY key # volatile-lru: only evict keys that have a TTL — non-TTL keys are safe # noeviction: reject new writes when full — returns OOM error (dangerous for a cache!) # ── Simulate checking what would be evicted ── redis-cli OBJECT IDLETIME user:1001:session_token # Returns idle time in seconds — higher = more likely to be evicted under LRU # ── Persist config changes to redis.conf so they survive restart ── redis-cli CONFIG REWRITE # Without this, all CONFIG SET changes are lost on restart!
Atomicity, Transactions & the Lua Script Pattern
Redis is single-threaded, so individual commands are always atomic. But what about multi-step operations like 'check a counter, increment it only if it's below 100'? That's where MULTI/EXEC transactions and Lua scripts come in — and this is a favourite interview deep-dive.
MULTI/EXEC queues a batch of commands that execute atomically. No other client can sneak a command in between. But there's a critical gotcha: Redis transactions don't roll back on runtime errors. If one command in the queue fails (e.g. wrong type), the rest still execute. That's intentional and very different from SQL transactions.
For complex conditional logic, Lua scripts are the better tool. A Lua script runs entirely on the Redis server as a single atomic unit. This is how rate limiting is implemented correctly — the check-and-increment happens in one atomic server-side script with no race condition possible.
Understanding the difference between MULTI/EXEC (queue and batch) versus Lua (conditional server-side logic) is what separates candidates who've used Redis in production from those who've only read the docs.
# ── MULTI/EXEC: Batch commands atomically ── redis-cli MULTI redis-cli INCR order:5501:item_count redis-cli EXPIRE order:5501:item_count 86400 # Set TTL inside the transaction redis-cli EXEC # Both commands execute atomically — no client can modify order:5501:item_count # between INCR and EXPIRE # ── Lua Script: Atomic rate limiter ── # This is the CORRECT way to implement rate limiting. # Using separate GET + INCR commands creates a race condition. redis-cli EVAL " local key = KEYS[1] -- e.g. 'ratelimit:user:1001:minute:2024010112' local limit = tonumber(ARGV[1]) -- e.g. 100 (requests per minute) local current = redis.call('GET', key) if current == false then -- Key doesn't exist yet — first request in this window redis.call('SET', key, 1, 'EX', 60) -- Set count=1 with 60s TTL return {1, limit - 1} -- {current_count, remaining} end current = tonumber(current) if current >= limit then return {current, 0} -- Rate limit hit — reject the request end -- Under the limit — increment and return updated values local new_count = redis.call('INCR', key) return {new_count, limit - new_count} " 1 "ratelimit:user:1001:minute:2024010112" 100 # Output on first call: 1) (integer) 1 2) (integer) 99 # Output on 100th call: 1) (integer) 100 2) (integer) 0 # Output on 101st call: 1) (integer) 100 2) (integer) 0 (blocked) # ── WATCH: Optimistic locking (alternative to Lua for simple cases) ── redis-cli WATCH user:1001:balance # If user:1001:balance is modified by another client before EXEC, # the entire transaction aborts and returns nil — your app retries redis-cli MULTI redis-cli DECRBY user:1001:balance 50 redis-cli EXEC # Returns nil if balance was modified by another client (optimistic lock failed) # Returns array of results if successful
redis.call() inside a loop with many iterations. This blocks the event loop for the entire duration. Keep scripts fast — O(1) or small O(log N) — and avoid time-consuming computations.Redis Replication, Sentinel & Cluster — Knowing When to Use Each
Single-node Redis is fine for development and small applications, but production requires thinking about high availability and horizontal scaling. Interviewers at mid-to-large companies almost always probe here.
Replication is the foundation: one primary node accepts writes, one or more replicas asynchronously receive those writes. It's not synchronous — there's always a tiny lag, meaning replicas can serve slightly stale reads. This is an intentional trade-off for write throughput.
Redis Sentinel adds automatic failover on top of replication. Sentinel is a separate process (or set of processes) that monitors your primary. If the primary goes down, Sentinel promotes the most up-to-date replica to primary and updates your clients. You need at least 3 Sentinel nodes to avoid split-brain scenarios — a quorum of 2 must agree before a failover triggers.
Redis Cluster is a different beast: it's about horizontal scaling, not just failover. It automatically shards your keyspace across multiple primary nodes using 16,384 hash slots. Each primary can have replicas. The trade-off is that multi-key commands (like MGET or Lua scripts touching multiple keys) only work if all keys hash to the same slot — which you control using hash tags like {user:1001}:session and {user:1001}:profile.
# ── Check replication status ── redis-cli INFO replication # On primary node output includes: # role:master # connected_slaves:2 # slave0:ip=10.0.0.2,port=6379,state=online,offset=1234567,lag=0 # slave1:ip=10.0.0.3,port=6379,state=online,offset=1234560,lag=1 # replication_backlog_size:1048576 # ── Redis Cluster: Hash slot calculation ── # Redis Cluster uses CRC16(key) % 16384 to decide which shard a key lives on # Problem: these two keys land on DIFFERENT shards: redis-cli CLUSTER KEYSLOT "user:1001:session" # Returns e.g. 11543 redis-cli CLUSTER KEYSLOT "user:1001:profile" # Returns e.g. 6452 # MGET user:1001:session user:1001:profile would FAIL in cluster mode! # ── Solution: Hash Tags force keys to the same slot ── # Wrap the shared part in curly braces — only the part in {} is hashed redis-cli CLUSTER KEYSLOT "{user:1001}:session" # Both hash on "user:1001" redis-cli CLUSTER KEYSLOT "{user:1001}:profile" # Same slot as above! # Now MGET {user:1001}:session {user:1001}:profile works correctly # ── Check cluster node topology ── redis-cli CLUSTER NODES # Output shows all nodes, their roles, and which hash slot ranges they own: # a1b2c3... 10.0.0.1:6379 master - 0 1620000000000 1 connected 0-5460 # d4e5f6... 10.0.0.2:6379 master - 0 1620000000001 2 connected 5461-10922 # g7h8i9... 10.0.0.3:6379 master - 0 1620000000002 3 connected 10923-16383 # ── Check Sentinel state ── redis-cli -p 26379 SENTINEL MASTERS # Port 26379 is the default Sentinel port # Shows the primary being monitored and its current state # 'num-slaves': 2 — how many replicas exist # 'num-other-sentinels': 2 — how many other Sentinel nodes are watching # 'quorum': 2 — how many must agree to trigger failover
Data Types Beyond Strings — The Ones That Actually Save You
If you think Redis is just key-value with strings, you've already lost the interview.
The real power is in hashes, sorted sets, bitmaps, and HyperLogLog. Every senior dev has a story about the junior who stored JSON blobs in string values and then wondered why partial updates were a nightmare. Hashes let you mutate individual fields without serializing the whole object. That's not a nice-to-have — it's how you avoid race conditions in session stores.
Sorted sets are the interview darling for leaderboards, but they're also how you build rate limiters, priority queues, and time-series rollups without a separate database. The score doubles as a timestamp. The rank doubles as a counter. One data structure, three problems solved.
HyperLogLog gets you unique counts on billions of events with 12KB of memory. If an interviewer asks how you'd count unique visitors for a high-traffic site and you start talking about exact counts, you've missed the point. Redis gives you approximate data structures because exactness is expensive. Learn when to trade accuracy for speed.
// io.thecodeforge — interview tutorial import redis client = redis.Redis(host='cache-cluster-01.internal') # Track unique page views per hour page = "/api/checkout" for user_id in range(100000): client.pfadd(f"hll:{page}:2024-03-15:14", user_id) count = client.pfcount(f"hll:{page}:2024-03-15:14") print(f"Unique visitors (approx): {count}") # Merge two hours to get unique across time window client.pfmerge("hll:checkout:afternoon", f"hll:{page}:2024-03-15:14", f"hll:{page}:2024-03-15:15") print(f"Unique across 2 hours: {client.pfcount('hll:checkout:afternoon')}")
Key Eviction Policies — Where Production Goes to Die
Every Redis instance has a memory limit. When you hit it, Redis doesn't crash — it evicts. The question is: what does it evict?
Default is noeviction. Sounds safe, right? Wrong. On a write, Redis returns an error. Your application throws. Users see 500s. I've debugged this at 3 AM after a marketing campaign spiked traffic.
allkeys-lru evicts the least recently used key regardless of TTL. This is the default for good reason — it's the closest to a sane production setup. volatility-lru only touches keys with a TTL set. Sounds smarter, but it assumes you set TTLs on everything. You don't. So when memory fills, Redis evicts nothing, and you're back to noeviction hell.
volatility-ttl evicts the key with the nearest expiry. Don't use it. Determining the "nearest TTL" across all keys at eviction time is O(n). In production, that's a latency spike when you can least afford it.
Pick allkeys-lru. Set maxmemory to 80% of your instance RAM. Leave headroom for replication buffers and background saves. And for the love of god, set a maxmemory-policy in your config before you go live.
// io.thecodeforge — interview tutorial import redis client = redis.Redis(host='cache-cluster-01.internal') # Fill instance with 10MB of keys for i in range(50000): client.set(f"session:{i}", "a" * 200) # Check current eviction policy config = client.config_get('maxmemory-policy') print(f"Current policy: {config['maxmemory-policy']}") # Simulate memory pressure — this write might evict client.set("urgent:payment-lock", "user_9876") # See what got evicted info = client.info('stats') print(f"Evicted keys: {info['evicted_keys']}")
Pub/Sub Is a Liar — Know When to Walk Away
Redis Pub/Sub looks simple. A publisher sends a message, subscribers receive it. Elegant. But here's the production trap: if a subscriber isn't connected when a message is published, that message vanishes forever. No queues, no persistence, no retries. Zero delivery guarantees. This isn't a bug—it's a design decision that burns teams who treat Pub/Sub like a message queue. Use it for ephemeral notifications like live score updates or WebSocket broadcasts. Never for critical data that must survive crashes. The moment you need at-least-once delivery, move to Redis Streams or a proper broker like RabbitMQ. Pub/Sub scales horizontally for fire-and-forget patterns, but monitoring subscriber health is non-negotiable. One dead subscriber and your entire system goes silent. Always implement health checks and reconnect logic. Your on-call rotation will thank you.
// io.thecodeforge — interview tutorial import redis import threading def subscriber(): r = redis.Redis() # Blocking subscribe — dies silently if Redis goes down pubsub = r.pubsub() pubsub.subscribe('alerts') for msg in pubsub.listen(): if msg['type'] == 'message': print(f"Got: {msg['data'].decode()}") if __name__ == '__main__': t = threading.Thread(target=subscriber, daemon=True) t.start() r = redis.Redis() # These survive only as long as the subscriber exists r.publish('alerts', 'Production fire in aisle 4')
Pipelining: Your 100x Performance Amplifier That Everyone Gets Wrong
Every interview candidate mentions Redis is fast. Few understand why pipelining makes it faster. Each command sent to Redis carries network round-trip latency. Do that for 1000 commands individually and you've eaten 1000 network hops. Pipelining bundles them into one shot: send all, get all replies. Latency collapses from O(n) to O(1). But here's where teams burn production: pipelining doesn't guarantee atomicity. If your pipeline has five commands and the third fails, commands 4 and 5 still execute. You get no rollback. Pipelining also eats memory—the client buffers responses until the pipeline completes. A pipeline with 10,000 SET commands will hold 10,000 replies in RAM before you process them. Under heavy load, that kills your application heap. Always pipeline for bulk writes like cache warming or batch updates. Never pipeline when each command depends on the previous result. For atomic multi-commands, use Lua scripts instead.
// io.thecodeforge — interview tutorial import redis r = redis.Redis() # Without pipeline: 10 network round-trips for i in range(10): r.set(f"user:{i}:score", i * 10) # With pipeline: 1 network round-trip pipe = r.pipeline() for i in range(10): pipe.set(f"user:{i}:score", i * 10) pipe.execute() # Execute all at once # Verify — note: no atomicity guarantee for partial failure print(r.mget(f"user:{i}:score" for i in range(10)))
Redis Modules: When Built-In Types Aren't Enough
Redis Modules extend the core engine with custom data structures and commands — think JSON, search, time series, or graph processing built directly into Redis. Why does this matter? Instead of deserializing strings in your application, a module like RedisJSON allows atomic operations on nested JSON fields via JSON.GET, JSON.SET, and JSON.ARRAPPEND. RediSearch gives you full-text indexing, auto-complete, and aggregation queries without an external search engine. But there's a catch: modules increase memory overhead and require careful version alignment. They're not free — each module adds its own memory profile and can trigger latency spikes if queries are poorly optimized. The production trap is assuming modules are drop-in replacements. They change Redis behavior: replication, persistence, and cluster compatibility vary per module. Before deploying, test eviction behavior with modules like RedisTimeSeries, which can generate unexpected memory pressure under write-heavy loads.
// io.thecodeforge — interview tutorial import redis r = redis.Redis() # Requires RedisJSON module loaded on server r.execute_command('JSON.SET', 'user:1', '.', '{"name":"Alice","scores":[95,87]}') # Atomic append to array without round-trip r.execute_command('JSON.ARRAPPEND', 'user:1', '.scores', '99') # Get partial field result = r.execute_command('JSON.GET', 'user:1', '.name') print(result) # b'"Alice"'
Redis Anti-Patterns: Patterns That Crash Production at 3 AM
Three anti-patterns kill Redis in production repeatedly. First, using KEYS in production — it blocks all other operations in a single-threaded event loop. Instead, use SCAN with cursor iteration. Second, ignoring memory fragmentation: default allocator (jemalloc) works well, but long-running servers with frequent key churn fragment memory. Monitor mem_fragmentation_ratio > 1.5 and consider restart or defrag. Third, treating Redis as a primary database — without AOF every write, you lose data on crash. Even with AOF, fsync=everysec gives at most 1 second of loss. The silent killer is oversized keys: a single 500MB hash blocks every other operation while serializing. Use HSCAN or split into multiple keys. Another hidden trap: using transactions (MULTI/EXEC) to read and then write without WATCH — that's not atomic. You need optimistic locking with WATCH or Lua scripts for actual consistency.
// io.thecodeforge — interview tutorial import redis r = redis.Redis() # ❌ KEYS blocks everything # all_keys = r.keys('user:*') # ✅ SCAN with cursor cursor = 0 while True: cursor, keys = r.scan(cursor, match='user:*', count=100) for k in keys: # process pass if cursor == 0: break # ❌ No WATCH leads to lost updates def safe_decrement(key): with r.pipeline() as pipe: while True: try: pipe.watch(key) val = int(pipe.get(key)) pipe.multi() pipe.set(key, val - 1) pipe.execute() break except redis.WatchError: continue
The Missing Rate Limit: How Redis Eviction Killed Our API Throttling
- Eviction policies are not 'free' — each one has a class of keys it will silently destroy. volatile-lru protects non-TTL keys, allkeys-lru does not.
- Rate-limit counters without TTL are unprotected under allkeys-lru. Always set a TTL matching the window length.
- Watch eviction metrics (evicted_keys in INFO stats) in your monitoring. A spike means Redis is discarding data — investigate immediately.
LATENCY LATESTSLOWLOG GET 50MEMORY STATSredis-cli --bigkeysINFO replicationiostat -x 1 5 on replica| Aspect | RDB Persistence | AOF Persistence |
|---|---|---|
| Mechanism | Point-in-time snapshots (fork + dump) | Logs every write command sequentially |
| Data loss risk | Up to last snapshot interval (minutes) | Up to 1 second (with everysec config) |
| File size | Compact binary format — small | Grows over time — needs periodic rewrite |
| Restart/restore speed | Very fast — single file load | Slower — replays all commands |
| CPU/Memory impact | Fork() spike during snapshot | Continuous small overhead per write |
| Best for | Acceptable data loss, fast restarts | Near-zero data loss requirement |
| Use together? | Yes — Redis supports RDB+AOF simultaneously for best of both worlds | AOF used for recovery, RDB for backups |
| File | Command / Code | Purpose |
|---|---|---|
| redis_data_structures_demo.sh | redis-cli | What Redis Actually Is |
| io | @Configuration | Connecting from Java |
| redis_persistence_and_eviction.sh | redis-cli CONFIG GET save | Persistence, Eviction & the Trade-offs That Cause Production |
| redis_atomic_rate_limiter.sh | redis-cli MULTI | Atomicity, Transactions & the Lua Script Pattern |
| redis_cluster_and_sentinel_concepts.sh | redis-cli INFO replication | Redis Replication, Sentinel & Cluster |
| HyperLogLogUserCount.py | client = redis.Redis(host='cache-cluster-01.internal') | Data Types Beyond Strings |
| EvictionPolicyDemo.py | client = redis.Redis(host='cache-cluster-01.internal') | Key Eviction Policies |
| pub_sub_example.py | def subscriber(): | Pub/Sub Is a Liar |
| pipeline_example.py | r = redis.Redis() | Pipelining |
| ModuleExample.py | r = redis.Redis() | Redis Modules |
| AntiPatternFix.py | r = redis.Redis() | Redis Anti-Patterns |
Key takeaways
Common mistakes to avoid
3 patternsUsing KEYS * in production
Not setting TTLs on cached keys
Assuming MULTI/EXEC rolls back on error
Interview Questions on This Topic
Explain how you'd implement a rate limiter using Redis. What commands would you use and why? What race conditions could occur with a naive implementation?
Your Redis instance is running out of memory. Walk me through how you'd diagnose what's taking up space, which eviction policy you'd choose and why, and how you'd prevent this from happening again.
A developer on your team suggests using Redis Pub/Sub as a reliable message queue for order processing. What would you tell them? What are the limitations of Pub/Sub that make it unsuitable, and what Redis feature would you recommend instead?
How does Redis handle master-slave replication? Is it synchronous or asynchronous, and what happens to pending writes during a network partition?
Describe the Big O complexity of the most common Redis operations (GET, SET, LPOP, ZADD). Why is ZADD O(log N) while SET is O(1)?
Frequently Asked Questions
Redis uses a single thread for command execution, which means no lock contention and perfectly predictable performance. It's not slow — it handles millions of operations per second because memory access is orders of magnitude faster than disk I/O. Since Redis 6.0, network I/O is handled by multiple threads, but command processing remains single-threaded by design.
The key ones: allkeys-lru evicts the least-recently-used key across all keys (best for a pure cache), volatile-lru evicts only keys with a TTL set (safe for mixed workloads where some keys must never be evicted), and noeviction rejects new writes when full (useful when you'd rather crash loudly than silently lose data). Always pair maxmemory-policy with a maxmemory limit, otherwise no eviction ever triggers.
Use Pub/Sub when you need fire-and-forget real-time messaging where losing messages is acceptable — live notifications, chat, presence indicators. Use Redis Streams when delivery guarantees matter: Streams persist messages, support consumer groups with acknowledgement, and let offline consumers catch up. For anything business-critical like order processing or payment events, Streams is the right choice.
20+ years shipping production code across the stack, with years spent interviewing engineers. Everything here is grounded in real deployments.
That's Database Interview. Mark it forged?
7 min read · try the examples if you haven't