You're using Redis Streams with a consumer group. One consumer processes messages and crashes after XREAD (before XACK). The message is stuck in the pending entry list (PEL). After 30 minutes, the message is still unack'd and no other consumer picks it up. Your app loses messages silently. How do you prevent this?
Crashed consumer leaves message in PEL indefinitely. Prevention: (1) set consumer timeout: XREAD BLOCK 5000 (5-second read timeout). If consumer doesn't ack within 5 seconds, consumer is assumed dead. But the consumer group needs to detect this. Use XAUTOCLAIM to automatically reassign stuck messages: XAUTOCLAIM
Follow-up: If a message is acknowledged by a consumer but crashes before it can be processed, how would you detect and replay this message?
You have 10 consumer group consumers processing a high-volume stream (100K messages/sec). Load is unevenly distributed: consumer-1 has 50K pending messages, consumer-10 has 500. consumer-1 is falling behind, building up lag. Meanwhile, new consumers need to be added but won't catch up to the backlog. How do you rebalance?
Uneven consumer load typically results from: (1) message key affinity: if messages for same user are routed to same consumer (via XREAD with pattern), one consumer might get hot keys. (2) consumer speed differences: some consumers are slow (network, computation). (3) missing rebalancing logic: consumers never move or redistribute. Fix: (1) add new consumers to consumer group: XGROUP CREATECONSUMER mystream mygroup consumer-11. They'll pick up new messages but won't get backlog. (2) rebalance existing backlog: use XPENDING to see distribution, then XCLAIM to reassign: XPENDING mystream mygroup shows all pending messages. For messages assigned to consumer-1, run XCLAIM mystream mygroup consumer-2 0
Follow-up: If you add new consumers but they miss the initial stream content, how would you replay messages for them without reprocessing old messages in existing consumers?
Your stream stores 1M messages, each 1KB. You discover the stream is being used by 2 different consumer groups: group-A (payment processing) and group-B (analytics). group-A has processed up to message 500K, group-B is only at 100K. You want to delete old messages (< 100K) to save space, but group-B will lose access to messages it hasn't processed. How do you handle this?
Streams don't support per-consumer retention—once you trim a stream, all consumers lose access to trimmed messages. Conflict: group-A wants to trim old messages, group-B still needs them. Solutions: (1) read before deleting: before trimming, export messages that group-B hasn't processed to separate storage (S3, DynamoDB). Export via: XRANGE mystream 0
Follow-up: If trimming accidentally deleted messages that group-B hasn't processed, how would you recover without losing data?
Your consumer group is consuming a stream at 1K messages/sec. Consumer processes each message with redis.call() from a Lua script. The script is fast (1ms per message), but during peak traffic, you see SLOWLOG entries for XREAD commands taking 100-500ms. The consumer is blocking on XREAD waiting for other consumers to finish their scripts. How do you improve throughput?
Blocking occurs because: (1) consumer reads XREAD BLOCK, but processing is slow (Lua scripts), so consumer is busy. Other readers block on same consumer group. (2) single consumer is saturated. Solution: (1) increase consumer count: add more consumers to consumer group. Each consumer reads independently and processes in parallel. If you have 10 consumers, throughput is ~10x. (2) increase XREAD COUNT: XREAD COUNT 10 BLOCK 1000 STREAMS mystream
Follow-up: If adding more consumers is not possible (resource-constrained), how would you optimize the single consumer to process more messages per second?
Your consumer group reads messages with XREAD, processes each message, then calls XACK to mark as done. But you accidentally deploy buggy code that XREAD without XACK (message is read but never ack'd). After 1 hour, the stream has 3.6M pending messages (1K messages/sec for 1 hour, all unack'd). Memory usage spikes to 5GB. Redis becomes unresponsive. How do you recover?
3.6M unack'd messages = 3.6M entries in PEL (pending entry list). PEL is stored in memory, causing OOM. Recovery: (1) immediately deploy fix to XREAD: add XACK after processing. (2) for existing backlog, clean up: identify which messages are old enough to drop. Run XCLAIM with IDLE parameter to reassign. (3) force-clear PEL: use XAUTOCLAIM with aggressive time window: XAUTOCLAIM mystream mygroup temp-consumer 1000 0-0 FORCE. This claims all messages and moves to temp-consumer, effectively clearing PEL. (4) if critical: restart Redis to flush all in-memory data, reload from persistence. Data loss may occur for messages not persisted. Prevention: (1) implement ack verification: monitor PEL size with XINFO GROUPS mystream. Alert if pending-count grows >100K for >5 minutes. (2) use XPENDING to see oldest unack'd message age: XPENDING mystream mygroup -IDLE 3600000 (messages idle >1 hour). If found, investigate why not being ack'd. (3) set consumer timeout: configure consumer code to have timeout on message processing. If processing takes >30 seconds, auto-ack and move to next message (trade-off: may lose message state). Implementation: (1) after deploying fix, verify XACK is working: run XPENDING mystream mygroup and check pending-count is decreasing. (2) for the current backlog: run script to clean up: EVAL 'local pending = redis.call("XPENDING", KEYS[1], KEYS[2]); for _, msg in ipairs(pending) do redis.call("XACK", KEYS[1], KEYS[2], msg[1]) end' 2 mystream mygroup (cleans all pending). (3) verify: XINFO GROUPS mystream should show pending: 0 after cleanup.
Follow-up: If you can't identify which messages are safe to ack (some might still be processing), how would you safely drain the PEL?
Your stream is replicated: primary pushes messages (XADD), consumer group on replica reads with XREAD. But the replica's consumer group last-delivered-id is ahead of primary's offset (replica somehow read messages that primary hasn't confirmed yet). Now primary and replica are inconsistent. How did this happen and how do you fix?
This shouldn't normally happen (replicas read from primary's replication stream). Likely causes: (1) consumer group was created on replica independently (not replicated from primary). Replica has its own group state that's not synced. (2) replication lag: replica reads ahead of primary (if consumer-group has its own offset tracker). (3) bugs in replication or stream implementation. Fix: (1) delete consumer group on replica: XGROUP DESTROY mystream mygroup on replica, which removes it. (2) recreate group on primary: XGROUP CREATE mystream mygroup $ on primary. This replicates to replica automatically. Verify replication: check XINFO GROUPS mystream on both primary and replica, last-delivered-id should match. (3) if messages exist on replica but not on primary, this indicates replication corruption. Backup replica, restore primary, sync replica from primary. (4) prevent with: use XGROUP CREATE on primary only. Consume on primary, let replication handle sync to replicas. If consuming on replica, ensure group is explicitly created on primary first. Monitor: run periodic consistency check: XINFO GROUPS mystream on primary vs replica, alert if any group's last-delivered-id diverges. Implementation: (1) detect divergence: diff XINFO output on primary vs replica. (2) if divergence exists: restart replica (clean reload from primary's RDB). (3) verify: after restart, run XREAD on both primary and replica, confirm they read the same message ID next.
Follow-up: If you must consume on replicas (to reduce primary load) but maintain consistency, how would you safely coordinate consumer group state?
Your stream stores user activity events. Each message is ~2KB. After 3 months, the stream has 2.7B messages (80GB). Most queries (analytics) use XREAD with a date range filter, but filters must be done client-side (Redis Streams doesn't have server-side filtering). Reading 80GB to client-side filter is impossibly slow. What's the architectural solution?
80GB stream with client-side filtering is impractical (throughput bottleneck). Architectural solution: (1) use secondary indices: instead of one stream, use multiple streams per date: stream:2024-01-01, stream:2024-01-02, etc. When querying date range, only read relevant streams. This is faster and allows retention policies per stream (old streams deleted). (2) use Redis Sorted Sets for indexing: ZADD user-events-index 1704067200
Follow-up: If you shard streams by date but queries span multiple shards, how would you efficiently combine results without pulling everything to client?