Redis — Interview Questions
Stack context: This system uses Redis 7 for three distinct patterns: (1) session store — user-service writes
session:{jwt}on login; api-gateway reads on every request; (2) cache-aside — product-service cachesproduct:{uuid}with 600 s TTL; (3) rate limiting — Spring Cloud Gateway token-bucket rate limiter. A Caffeine in-memory cache (60 s, 10k entries) provides resilience when Redis is unavailable.
Q1 — What is Redis and what makes it different from a relational database? junior
Answer: Redis (Remote Dictionary Server) is an in-memory data structure store that can act as a cache, database, message broker, and queue. Unlike relational databases:
| Aspect | Redis | Relational DB |
|---|---|---|
| Storage | Primary: RAM (optional disk persistence) | Primary: Disk |
| Data model | Key-value with rich types (string, hash, list, set, sorted set, stream) | Tables with rows and columns |
| Query language | Commands per data type | SQL |
| Latency | Sub-millisecond | 1–100 ms (disk I/O dependent) |
| Persistence | Optional (RDB snapshots, AOF log) | Always durable |
| Transactions | MULTI/EXEC (limited) | ACID full |
Use case: Redis excels as a complement to a relational database — store frequently read, rarely changed data in Redis for microsecond latency; store authoritative data in PostgreSQL.
Q2 — What is the cache-aside (lazy loading) pattern? junior
Answer: Cache-aside means the application code manages cache population, not the database or cache infrastructure.
Flow:
- Application checks cache for the data.
- Cache HIT: Return data from cache immediately.
- Cache MISS: Fetch from the database, write to cache with TTL, return data.
- On update: Write to database, then invalidate (or update) cache.
// product-service: ProductService.java
public ProductDto getProduct(UUID id) {
String cached = redisTemplate.opsForValue().get("product:" + id);
if (cached != null) {
return objectMapper.readValue(cached, ProductDto.class); // HIT
}
ProductDto product = productRepository.findById(id).orElseThrow();
redisTemplate.opsForValue().set("product:" + id, serialize(product), Duration.ofSeconds(600));
return product; // MISS → populated
}
This system: product-service sets X-Cache: HIT or X-Cache: MISS response header for observability.
Q3 — What is a Redis TTL and why is it important? junior
Answer: TTL (Time-To-Live) is the expiration time for a Redis key in seconds. When the TTL expires, Redis automatically deletes the key.
Why it matters:
- Cache staleness control: Ensures stale product prices or user sessions don't persist indefinitely.
- Memory management: Prevents unbounded memory growth; Redis reclaims expired key space.
- Session expiry: User sessions must expire for security; TTL enforces this without a cleanup job.
Commands:
SET session:abc123 {...} EX 1800 # set with 1800s TTL
EXPIRE session:abc123 1800 # set TTL on existing key
TTL session:abc123 # check remaining TTL (-2 = expired/not found)
PERSIST session:abc123 # remove TTL (make persistent)
This system: Sessions use 1800 s (30 min) TTL matching JWT expiry. Products use 600 s TTL.
Q4 — What Redis data types do you know and when would you use each? junior
Answer:
| Type | Use Cases |
|---|---|
| String | Simple key-value: session data, cached objects (JSON), counters (INCR) |
| Hash | Store object fields individually: user profile HSET user:1 name "Alice" email "..." |
| List | Queues (LPUSH/RPOP), activity feeds, recent items |
| Set | Unique members: tags, user follows, deduplication |
| Sorted Set | Leaderboards, priority queues, range queries by score |
| Stream | Event log, producer-consumer queues with consumer groups |
| Bitmap | User activity tracking (DAU/MAU) with minimal memory |
| HyperLogLog | Approximate unique counts (cardinality) |
This system: Strings for session store and product cache. The rate limiter uses a Sorted Set (Spring's RedisRateLimiter uses ZADD/ZREMRANGEBYSCORE internally for the token bucket).
Q5 — What is Redis eviction and how do you configure it? junior
Answer:
When Redis reaches its maxmemory limit, it must decide what to do with new writes. The maxmemory-policy setting controls the eviction strategy:
| Policy | Behavior |
|---|---|
noeviction |
Return error on write; never evict (default for persistence) |
allkeys-lru |
Evict least recently used keys across all keys |
volatile-lru |
Evict LRU among keys with TTL set |
allkeys-lfu |
Evict least frequently used (Redis 4+) |
allkeys-random |
Evict random key |
volatile-ttl |
Evict key with shortest remaining TTL |
Cache configuration: Use allkeys-lru so Redis automatically evicts cold cache entries when full, keeping hot data in memory.
Session store configuration: Use noeviction or volatile-lru so active sessions are never silently dropped — users should be logged out explicitly.
Q6 — What is the difference between Redis SET, SETEX, SETNX, and SET ... NX EX? junior
Answer:
| Command | Behavior |
|---|---|
SET key value |
Set unconditionally |
SETEX key seconds value |
Set with TTL (older API) |
SETNX key value |
Set only if key does NOT exist (atomic) |
SET key value NX EX seconds |
Set only if NOT exists + TTL (modern, atomic) |
SET key value XX EX seconds |
Set only if EXISTS + TTL |
SET ... NX EX is the correct distributed lock primitive:
SET lock:order-123 "worker-a" NX EX 30
If this returns OK, you hold the lock for 30 s. If it returns nil, another process holds it. This atomically combines the existence check and TTL in a single command — avoiding the TOCTOU race of SETNX + EXPIRE.
Q7 — How does Redis handle persistence and what are the trade-offs of RDB vs AOF? junior
Answer:
RDB (Redis Database) Snapshots:
- Writes a point-in-time binary snapshot of the dataset to disk at configured intervals.
- Fast restart (loads binary dump).
- Risk: data since last snapshot is lost on crash.
- Use: Backup, fast restart for cache workloads.
AOF (Append-Only File):
- Logs every write command to a file. On restart, replays the log.
appendfsync always: Sync to disk after every command — durable but slow.appendfsync everysec: Sync once per second — at most 1 second of data loss.appendfsync no: Let OS decide — fastest, up to seconds of data loss.- Use: Session stores, financial data where losing writes is unacceptable.
Combined: Use both — RDB for fast restarts, AOF for durability. Redis will use AOF for recovery if both exist.
This system: Docker development uses RDB. Production session store would need AOF everysec.
Q8 — What is a Redis pipeline and when should you use it? junior
Answer: A pipeline batches multiple Redis commands into a single network round-trip. Instead of sending N commands and waiting for N replies, you send all N commands at once and receive all N replies together.
// Without pipeline: N round-trips
for (String id : productIds) {
redisTemplate.opsForValue().get("product:" + id);
}
// With pipeline: 1 round-trip
List<Object> results = redisTemplate.executePipelined(connection -> {
for (String id : productIds) {
connection.get(("product:" + id).getBytes());
}
return null;
});
When to use:
- Bulk cache reads (product listing page loading 20 products).
- Batch writes on startup or data migration.
When NOT to use:
- When each command's result is needed to decide the next command (use Lua script instead).
Q9 — What is a distributed lock and how is it implemented in Redis? senior
Answer: A distributed lock prevents multiple processes from concurrently executing a critical section. Redis is a common choice because it's fast, atomic, and shared across services.
Basic implementation (Redlock for single instance):
SET lock:{resource} {unique_id} NX EX {timeout}
NX: Only set if key doesn't exist (atomic test-and-set).EX {timeout}: Auto-release lock if the holder crashes.{unique_id}: UUID per lock acquisition to prevent releasing another holder's lock.
Release (Lua script for atomic check-and-delete):
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end
Redlock (multi-node algorithm by Redis author): Acquire lock on majority of N (≥3) independent Redis nodes. A lock is valid if acquired on the majority within TTL/2 time.
This system: OutboxPublisher uses a single-node lock to prevent multiple pods from double-publishing outbox events simultaneously.
Q10 — How does Spring's RedisRateLimiter work and what algorithm does it use? senior
Answer:
Spring Cloud Gateway's RedisRateLimiter implements the token bucket algorithm using a Lua script executed atomically on Redis.
Token bucket concept:
- A bucket holds up to
replenishRate × burstCapacitytokens. - Tokens are added at
replenishRateper second. - Each request consumes 1 token. Request is allowed if bucket has ≥1 token; denied (429) if empty.
Lua script (executed atomically):
- Compute elapsed time since last request using Redis
TIME. - Add
elapsed × replenishRatetokens to the bucket. - Cap at
burstCapacity. - If bucket ≥
requestedTokens, subtract and allow; else deny.
Redis keys (per user/IP):
{prefix}.{key}.tokens— current token count{prefix}.{key}.timestamp— last refill time
This system: api-gateway applies rate limiting per X-User-Id header. Authenticated users get a higher burst capacity than anonymous requests. Redis handles this statelessly across gateway replicas.
Q11 — What is the N+1 cache problem and how do you solve it? senior
Answer: The N+1 cache problem occurs when loading a list of items:
- Load a list of N IDs from the database.
- For each ID, check the cache individually (N round-trips).
- For each MISS, load from the database (N additional queries).
Solutions:
Multi-get (pipeline):
List<String> keys = ids.stream().map(id -> "product:" + id).collect(toList());
List<Object> values = redisTemplate.opsForValue().multiGet(keys); // 1 round-trip
Cache warming: Pre-populate cache on startup or via an async background job.
Batch DB fallback: Collect all MISS keys, load from DB in a single IN (...) query, populate cache in a pipeline.
List<UUID> misses = findMisses(ids, cachedValues);
if (!misses.isEmpty()) {
List<Product> dbResults = productRepo.findAllById(misses);
batchCacheWrite(dbResults); // pipeline
}
Q12 — Explain Redis Sentinel vs Redis Cluster and when to use each. senior
Answer:
Redis Sentinel:
- Provides high availability for a single Redis master-replica setup.
- Monitors master health; if master fails, Sentinel promotes a replica automatically.
- Client must connect through Sentinel to discover the current master.
- Max dataset size = single node's RAM.
- Use: HA without sharding, simpler ops.
Redis Cluster:
- Provides horizontal sharding across 16,384 hash slots distributed across nodes.
- Each master node handles a subset of slots. Replicas provide HA per shard.
- Data is automatically partitioned by key hash.
- Multi-key operations only work if all keys map to the same slot (use hash tags
{tag}:key). - Use: Large datasets, high write throughput, horizontal scaling.
Decision criteria:
- Dataset < single node RAM AND writes are moderate → Sentinel.
- Dataset > single node RAM OR very high write throughput → Cluster.
This system: Single Redis node (development). Production would use Sentinel for HA, Cluster for scale.
Q13 — What is the Caffeine fallback and why is it needed alongside Redis? senior
Answer: The Caffeine fallback is a JVM-local in-memory cache (per service instance) that stores recently validated sessions. It addresses two problems:
1. Redis latency: Every API Gateway request hitting Redis adds ~1 ms. With 100k req/s, Redis becomes the bottleneck.
2. Redis unavailability: If Redis goes down (network blip, rolling restart), ALL gateway requests would fail with 401/500 without a fallback.
Implementation in this system:
// api-gateway: SessionValidationFilter.java
Caffeine.newBuilder()
.expireAfterWrite(60, SECONDS) // 60-second local TTL
.maximumSize(10_000) // 10k concurrent sessions
.build();
Flow:
- Check Caffeine cache → HIT: return immediately (no Redis call).
- Caffeine MISS → check Redis → HIT: populate Caffeine, return.
- Redis unavailable → serve Caffeine for up to 60 s for known sessions.
Trade-off: 60-second window where a revoked session (logout) is still accepted from Caffeine. Acceptable for most use cases; reduce TTL if stricter revocation is needed.
Q14 — How do Redis transactions work with MULTI/EXEC and what are their limitations? senior
Answer:
Redis MULTI/EXEC queues commands and executes them atomically as a batch:
MULTI
SET session:abc "active"
INCR login_count:user:1
EXEC → [OK, (integer) 5]
Guarantees:
- All commands execute sequentially; no other client's commands interleave.
- All or nothing (if
EXECfails due toDISCARD, none run).
Limitations:
- No rollback on command error: If
SETsucceeds butINCRfails (wrong type), theSETis NOT undone. - No conditional logic: You cannot branch based on the result of a previous command within the transaction.
WATCH(optimistic locking): Watch keys beforeMULTI; if any watched key changes beforeEXEC, the transaction is aborted. You must retry the whole block.
Better alternative for conditional logic: Lua scripts — atomic, support branching, can read and write in the same script. Used by Spring's RedisRateLimiter and distributed lock release.
Q15 — What are common Redis memory optimization techniques? senior
Answer:
Use appropriate data types: Use Hash for objects with multiple fields instead of multiple String keys.
- Bad:
SET user:1:name "Alice",SET user:1:email "..."(2 keys overhead each) - Good:
HSET user:1 name "Alice" email "..."(1 key, hash encoding if <128 fields)
- Bad:
Hash encoding thresholds: Redis uses a compact
ziplistencoding for small hashes (<hash-max-ziplist-entries= 128 by default) → significant memory savings.Set appropriate TTLs: Ensure all cache keys have TTLs to prevent unbounded growth.
Compression at app layer: Store gzip-compressed JSON for large cached objects.
Key naming: Short, consistent key names reduce memory overhead. Use
p:instead ofproduct:for high-volume keys.redis-memory-doctor: CLI tool for memory usage analysis and optimization suggestions.OBJECT ENCODING key: Inspect actual encoding to verify optimization strategies are effective.
Q16 — How would you handle cache stampede (thundering herd) in Redis? senior
Answer: A cache stampede occurs when many requests simultaneously find the same cache key expired, all go to the database concurrently, and all try to repopulate the cache at the same time — overwhelming the database.
Solutions:
1. Probabilistic early expiration: Before the TTL actually expires, a small fraction of requests re-fetch proactively. Prevents simultaneous expiry of many requests.
2. Mutex / lock on MISS:
String cached = redis.get(key);
if (cached == null) {
boolean lockAcquired = redis.set("lock:" + key, "1", NX, EX, 10);
if (lockAcquired) {
cached = db.fetch(id);
redis.set(key, cached, EX, 600);
redis.del("lock:" + key);
} else {
// Wait briefly and retry — another thread is populating
Thread.sleep(50);
cached = redis.get(key);
}
}
3. Background refresh: Serve stale data from cache while asynchronously refreshing. stale-while-revalidate pattern.
4. Jitter on TTL: Add random offset to TTL to prevent mass simultaneous expiry of related keys (e.g., TTL = 600 + random(0, 60)).
Q17 — How do you implement a sliding window rate limiter in Redis? senior
Answer: Using a Sorted Set where the score is the timestamp:
-- Lua script (atomic)
local key = KEYS[1] -- "ratelimit:user:123"
local now = tonumber(ARGV[1]) -- current timestamp in ms
local window = tonumber(ARGV[2]) -- window size in ms (e.g., 60000 = 1 min)
local limit = tonumber(ARGV[3]) -- max requests per window
-- Remove entries older than window
redis.call("ZREMRANGEBYSCORE", key, 0, now - window)
-- Count current requests in window
local count = redis.call("ZCARD", key)
if count < limit then
-- Add current request
redis.call("ZADD", key, now, now .. math.random())
redis.call("EXPIRE", key, math.ceil(window / 1000))
return 1 -- allowed
else
return 0 -- denied
end
vs. Token bucket (Spring's approach): Token bucket smooths traffic by replenishing at a constant rate. Sliding window is exact but uses more memory (stores all request timestamps). Token bucket uses O(1) memory per key.
Q18 — What is Redis pub/sub and how does it differ from Kafka? senior
Answer: Redis pub/sub is a fire-and-forget messaging channel. Publishers send messages to a channel; all current subscribers receive them.
SUBSCRIBE order.notifications
PUBLISH order.notifications "Order #123 shipped"
Key differences from Kafka:
| Feature | Redis Pub/Sub | Kafka |
|---|---|---|
| Persistence | No — missed messages are lost | Yes — retained for retention period |
| Delivery guarantee | At-most-once | At-least-once or exactly-once |
| Consumer groups | No — all subscribers get all messages | Yes — group-based partitioned consumption |
| Replay | No | Yes — seek to any offset |
| Scale | Single node (Cluster = keyslot limitations) | Horizontally scalable |
Use Redis pub/sub for: Cache invalidation broadcast, real-time chat, lightweight notifications where loss is acceptable.
Use Kafka for: Any durable, replayable event streaming (order processing, audit logs, etc.).
This system: Uses Kafka for all inter-service events. Redis pub/sub is not used but would be appropriate for broadcasting cache invalidation across product-service instances.
Q19 — How would you implement a leaderboard with Redis? junior
Answer: Redis Sorted Sets are purpose-built for leaderboards. The score is the numeric value (points, sales, etc.); the member is the user identifier.
// Add/update score
redisTemplate.opsForZSet().add("leaderboard:weekly", "user:123", 1500.0);
// Increment score
redisTemplate.opsForZSet().incrementScore("leaderboard:weekly", "user:123", 50.0);
// Get top 10 (highest score first)
Set<ZSetOperations.TypedTuple<String>> top10 =
redisTemplate.opsForZSet().reverseRangeWithScores("leaderboard:weekly", 0, 9);
// Get user rank (0-indexed, lowest = rank 1)
Long rank = redisTemplate.opsForZSet().reverseRank("leaderboard:weekly", "user:123");
Time complexity: ZADD O(log N), ZRANGE O(log N + M). All leaderboard operations are O(log N) — extremely efficient even for millions of users.
This system: Not currently used, but order-service could maintain orders:count:customer:{id} sorted set for customer activity rankings.
Q20 — How do you monitor Redis in production? junior
Answer: Key monitoring commands and metrics:
Real-time monitoring:
redis-cli MONITOR # stream all commands (dev only — high overhead)
redis-cli INFO # comprehensive stats
redis-cli INFO memory # memory usage, fragmentation ratio
redis-cli INFO stats # keyspace hits/misses, ops/sec
redis-cli INFO clients # connected clients, blocked clients
Key metrics to track:
used_memoryvsmaxmemory— eviction riskkeyspace_hits/keyspace_misses→ cache hit ratio = hits / (hits + misses), target >90%rejected_connections— connection pool exhaustionrdb_last_bgsave_status/aof_last_write_status— persistence healthreplication_lag(Sentinel) — replica falling behindconnected_clients— connection leak detection
Production tooling: Redis Exporter → Prometheus → Grafana dashboard. Alert on: memory > 80%, hit ratio < 80%, rejected connections > 0.
Q21 — What is Redis keyspace notification and how would you use it? senior
Answer:
Keyspace notifications allow clients to subscribe to events on Redis keys via pub/sub channels. Enabled with notify-keyspace-events config.
Event types (configured as flags):
K: Keyspace events (channel:__keyspace@db__:key)E: Keyevent events (channel:__keyevent@db__:event)x: Expired eventsd: Module key-miss eventsg: Generic commands (DEL, EXPIRE, RENAME)s: Set commands
Example — session expiry audit:
// Subscribe to expired events
redisTemplate.convertAndSend("__keyevent@0__:expired", ...);
// In listener: log session_id, user_id, expiry_time to audit table
Use cases:
- Audit log on session expiry.
- Cache invalidation triggers.
- Job scheduling with Redis key expiry as a delay mechanism.
Warning: High-frequency keyspace notifications add overhead. Use selectively and only subscribe to necessary event types.
Q22 — What is the Redis object encoding and why does it matter for performance? senior
Answer: Redis uses different internal encodings for data types based on size/content for memory efficiency:
| Type | Small (ziplist/listpack) | Large (hash table/skiplist) |
|---|---|---|
| Hash | ziplist/listpack (< 128 fields, < 64 bytes/field) |
hashtable |
| List | listpack (< 128 elements) |
quicklist |
| Sorted Set | listpack (< 128 members) |
skiplist + hashtable |
| Set (integers) | intset (integer-only members) |
hashtable |
| String | int, embstr (≤44 bytes), raw |
Performance implications:
ziplist/listpack: CPU-intensive O(N) scan but excellent memory density (cache-line friendly).hashtable/skiplist: O(1) or O(log N) operations but higher memory overhead per element.
Tuning: If you have many small hashes, ensure thresholds aren't crossed unnecessarily. hash-max-listpack-entries 128 and hash-max-listpack-value 64 control the cutoff.
Inspection: OBJECT ENCODING product:abc123 → verify embstr for small JSON values.
Q23 — How do you handle Redis connection pooling in Spring? junior
Answer: Spring Data Redis uses Lettuce (async) or Jedis (sync) as the client. Lettuce uses a single multiplexed connection per thread pool (non-blocking), while Jedis uses a traditional connection pool.
Lettuce (default in Spring Boot):
spring:
redis:
lettuce:
pool:
max-active: 8 # max connections
max-idle: 8 # max idle connections kept alive
min-idle: 2 # min connections maintained
max-wait: -1ms # wait indefinitely for connection (-1 = block)
Jedis pool (if using Jedis):
spring:
redis:
jedis:
pool:
max-active: 8
max-wait: 2000ms # timeout after 2s — better than blocking forever
Best practice with Lettuce: Lettuce's multiplexed connection handles thousands of concurrent requests on a single connection. Pooling is optional but useful for SSL or blocking commands (BLPOP).
This system: Uses Lettuce (Spring Boot default). ReactiveRedisTemplate in the api-gateway (WebFlux) uses Lettuce's non-blocking reactor integration natively.
Q24 — Explain the difference between ReactiveRedisTemplate and RedisTemplate. junior
Answer:
RedisTemplate |
ReactiveRedisTemplate |
|
|---|---|---|
| Programming model | Blocking (synchronous) | Non-blocking (Project Reactor Mono/Flux) |
| Thread usage | One thread per request (Tomcat model) | Event loop (Netty), fewer threads |
| Use with | Spring MVC, RestTemplate | Spring WebFlux, WebClient |
| API | opsForValue().get(key) → value directly |
opsForValue().get(key) → Mono<V> |
This system:
api-gatewayuses Spring WebFlux →ReactiveRedisTemplate(non-blocking, fits event loop).user-service,product-serviceuse Spring MVC →RedisTemplate(blocking, simpler to use).
Mixing RedisTemplate in a WebFlux app blocks the event loop — avoid it. Always use ReactiveRedisTemplate in reactive stacks.
Q25 — What are Redis Streams and how do they compare to Kafka? senior
Answer: Redis Streams (introduced in Redis 5.0) are a persistent, append-only log with consumer groups — similar to a lightweight Kafka.
Features:
XADD: Append entry with auto-generated<timestamp>-<seq>ID.XREAD: Read entries from a position.XGROUP: Consumer groups — each group tracks its own position; entries are delivered to one consumer per group.XACK: Acknowledge processing; unacknowledged entries are tracked in a "pending list".- Persistence: Entries persist until explicitly deleted or trimmed (
MAXLEN).
vs. Kafka:
| Redis Streams | Kafka | |
|---|---|---|
| Persistence | Redis memory + optional AOF | Disk (TB scale) |
| Throughput | Hundreds of thousands msg/s | Millions msg/s |
| Retention | Trim with MAXLEN; memory-bound |
Days/weeks on disk |
| Replication | Replica sync (Sentinel/Cluster) | Configurable replication factor |
| Ecosystem | Basic | Rich (Kafka Streams, Connect, MirrorMaker) |
Use case: Redis Streams work for lightweight, low-latency, low-volume event pipelines within a bounded retention window. Kafka is better for high-throughput, durable event archives.
Q26 — How would you invalidate a cache when data changes in the database? senior
Answer: Cache invalidation is one of the hardest problems in distributed systems. Strategies:
1. Delete on write (eager invalidation):
productRepository.save(product); // update DB
redisTemplate.delete("product:" + product.getId()); // invalidate cache
Simple, but subject to race condition: if another request populates cache between save and delete, you have a stale entry.
2. Update on write (write-through):
productRepository.save(product);
redisTemplate.opsForValue().set("product:" + id, serialize(product), TTL);
Cache always has fresh data. Risk: if DB write succeeds but Redis write fails, cache is stale until TTL expires.
3. CDC (Change Data Capture) + event-driven invalidation:
- Debezium captures DB changes from PostgreSQL WAL → publishes to Kafka
product.updatedtopic → cache service consumes and invalidates/updates. - Most robust: invalidation is decoupled from the write path; no risk of inconsistency on partial failure.
4. TTL-based staleness acceptance: For read-heavy data where minor staleness (< 10 min) is acceptable, just let TTL expire naturally.
This system: Uses delete-on-write (redisTemplate.delete(...)) after product updates.
Q27 — What is Redis Cluster hash slot and how does key routing work? senior
Answer: Redis Cluster divides the keyspace into 16,384 hash slots (0–16,383). Each master node owns a range of slots. A key is assigned to a slot using:
slot = CRC16(key) % 16384
The client computes the slot and connects directly to the correct node.
Hash tags enable multi-key operations on the same node:
{user:123}.sessionand{user:123}.profile→ both hash to slot ofuser:123→ same node →MGETworks.- Without hash tags,
MGET user:123:session user:123:profilemight span nodes → error.
Resharding: When adding/removing nodes, slots (and their data) are migrated. During migration, keys may be on either source or destination node; the cluster redirects with ASK/MOVED responses.
Client support: Lettuce and Jedis both handle MOVED/ASK transparently, reconnecting to the correct node automatically.
Q28 — How do you warm up a Redis cache after a cold start or deployment? senior
Answer: Cache warm-up prevents thundering herd on a fresh deployment where all requests are cache misses.
Strategies:
1. Pre-load on startup (Spring ApplicationRunner):
@Component
public class ProductCacheWarmer implements ApplicationRunner {
public void run(ApplicationArguments args) {
List<Product> topProducts = productRepo.findTop1000ByOrderBySalesDesc();
topProducts.forEach(p -> cache.put("product:" + p.getId(), serialize(p)));
}
}
2. Lazy warm-up with shadow traffic: Route a small percentage of production traffic to the new instance before switching. Cache warms from real requests.
3. Snapshot restore: Export cache from previous instance (BGSAVE + copy RDB) and import on new instance.
4. Read-through proxy: Cache proxy (e.g., Twemproxy) transparently populates on MISS. No app code needed.
This system: Product service would benefit from warming the top 1,000 products on startup, reducing cold-start cache miss rate.
Q29 — What is the Redis memory fragmentation ratio and when is it a problem? senior
Answer: Memory fragmentation occurs when Redis allocates memory blocks that aren't fully utilized. The fragmentation ratio is:
fragmentation_ratio = used_memory_rss / used_memory
used_memory: Memory Redis logically uses for data.used_memory_rss: Physical memory reported by OS (includes fragmentation).
Interpretation:
- Ratio ~1.0: Healthy, minimal fragmentation.
- Ratio > 1.5: Significant fragmentation — OS allocated more than Redis uses.
- Ratio < 1.0: Memory is being swapped to disk — performance degradation imminent.
Causes of high fragmentation:
- Many small allocations and deallocations (frequent key creation/deletion).
- Large value updates resizing in place.
- jemalloc's allocation granularity.
Remediation:
- Enable active defragmentation:
CONFIG SET activedefrag yes(Redis 4+). - Rolling restart (Redis reloads clean state).
- Monitor with
INFO memory→mem_fragmentation_ratio.
Q30 — How would you design a high-availability Redis setup for a critical session store? senior
Answer: For a critical session store, availability and durability are the primary concerns.
Architecture:
[App Instances] → [Lettuce Client (Sentinel-aware)]
↓
[Redis Sentinel Quorum: 3 nodes]
↓
[Master] ←sync→ [Replica 1] ←sync→ [Replica 2]
↑ failover promotion on master failure
Configuration checklist:
replication factor = 3(1 master + 2 replicas)min-replicas-to-write = 1(master refuses writes if no replica is reachable)AOF appendfsync everysec(at most 1 s data loss)requirepass+ TLS in transit- Sentinel quorum = 2 of 3 (prevents split-brain)
maxmemory-policy volatile-lru(evict sessions with TTL before newer sessions)- Health monitoring via
PINGon all Sentinel nodes
Client failover: Lettuce automatically reconnects to the new master after Sentinel elects it (typically < 30 s). Application should implement retry logic for the failover window.
This system: Single-node Redis for development. The architecture above is the production target for the session store.