Maintenance

Site is under maintenance — quizzes are still available.

Go to quizzes

Read Pending Stream Entries

Learn how to read pending stream entries in Redis with a hands-on exercise, troubleshooting tips, and next steps in the Redis track.

Focus: read pending stream entries

Sponsored

You've built a Redis stream, written entries, and created a consumer group. But what happens when a consumer crashes mid-process or takes too long to acknowledge a message? Those unacknowledged entries pile up as pending entries, and if you don't know how to read them, you risk silent message loss and broken workflows. Learning to read pending stream entries turns chaos into control — you can retry, inspect, and recover gracefully.

The problem this lesson solves

In real-world Redis Streams, consumers can fail unexpectedly: a network blip, a restart, or a slow operation. When a consumer receives a message but never acknowledges it (via XACK), Redis marks that entry as pending for that consumer. If you only ever read new entries, you miss those stuck messages entirely.

Consider a payment processing queue: a consumer reads an order, starts charging the card, then crashes. The payment may or may not have gone through. Without reading pending entries, you have no way to retry or inspect the state. This lesson gives you the XPENDING and XREADGROUP commands to read pending stream entries, so you can build reliable, at-least-once delivery systems.

Core concept / mental model

Think of a stream as a river of messages, and a consumer group as a team of fishermen. Each fisherman (consumer) takes a fish (message) and must tag it before moving on — XACK is that tag. If a fisherman gets distracted and never tags, the fish stays marked as "pending" on his record.

Pending entries are messages that were delivered to a consumer but not yet acknowledged. They belong to a specific consumer within a group. The command XPENDING gives you a summary — count of pending, oldest/newest IDs, and per-consumer breakdown. XREADGROUP with the > ID reads new messages; to read pending messages, you pass a specific stream ID (often '0') or the consumer's last pending ID.

Key definitions

  • Stream: A log of entries, each with a unique ID.
  • Consumer group: A named group of consumers sharing the stream.
  • Consumer: A member of the group, identified by a name.
  • Pending entry: An entry delivered to a consumer but not XACKed.
  • XPENDING: Introspect the pending list.
  • XREADGROUP: Read entries (new or pending) as a group consumer.

How it works step by step

Step 1: Identify pending entries with XPENDING

XPENDING takes: stream key, group name. It returns a summary: - Number of pending entries. - Oldest pending ID. - Newest pending ID. - (Optional) per-consumer breakdown.

Step 2: Read pending entries with XREADGROUP

To read pending messages, use XREADGROUP with: - GROUP <group> <consumer> - COUNT <number> (optional) - STREAMS <key> <id> where <id> is '0' to start from the oldest pending, or a specific ID like '123-0'.

Pro tip: Using '0' as the ID in XREADGROUP tells Redis: "Give me all pending messages starting from the beginning of the pending list." It does NOT return new messages — those require '>'.

Step 3: Take action — retry, inspect, or acknowledge

Once you read pending entries, you can: - Process them again (with idempotency safeguards). - Log them for manual review. - Acknowledge them (XACK) if they are safe to discard.

Hands-on walkthrough

First, set up a stream and a consumer group with some pending entries.

# Start Redis CLI
redis-cli
# Create stream 'orders' and add 3 entries
XADD orders * order_id 1001 status paid
XADD orders * order_id 1002 status pending
XADD orders * order_id 1003 status refunded

# Create consumer group 'processors'
XGROUP CREATE orders processors $ MKSTREAM

# Read a new entry as consumer 'worker1' but don't ack it
XREADGROUP GROUP processors worker1 COUNT 1 STREAMS orders >
# Returns first entry (order_id 1001)

# Read and don't ack second entry as 'worker2'
XREADGROUP GROUP processors worker2 COUNT 1 STREAMS orders >
# Returns second entry (order_id 1002)

Now check pending entries:

XPENDING orders processors
# 1) (integer) 2
# 2) "1672531200000-0"  (oldest pending ID)
# 3) "1672531200001-0"  (newest pending ID)
# 4) 1) 1) "worker1"
#       2) "1"          (1 pending for worker1)
#    2) 1) "worker2"
#       2) "1"          (1 pending for worker2)

Read pending entries for a specific consumer:

# Read all pending for worker1 (oldest first)
XREADGROUP GROUP processors worker1 STREAMS orders 0
# 1) 1) "orders"
#    2) 1) 1) "1672531200000-0"
#          2) 1) "order_id"
#             2) "1001"
#             3) "status"
#             4) "paid"

Now acknowledge that entry:

XACK orders processors 1672531200000-0
# (integer) 1

# Verify pending count
XPENDING orders processors
# (integer) 1

Pro tip: To read pending entries for any consumer in the group, use XREADGROUP GROUP processors consumer-any STREAMS orders 0. The consumer name here is arbitrary; it just tells Redis which consumer's pending list to "act as." Use the same consumer name as in the original XREADGROUP.

Compare options / when to choose what

Approach Command Purpose When to use
Read new messages XREADGROUP ... STREAMS <key> > Only new (undelivered) messages Normal consumption flow
Read all pending XREADGROUP ... STREAMS <key> 0 All pending messages, oldest first Recovery, inspection, drain pending queue
Read pending per consumer XREADGROUP ... STREAMS <key> <id> Pending messages after a specific ID Incremental recovery, resume from checkpoint
Summary only XPENDING <key> <group> Count and IDs of pending entries Monitoring, alerting

Key tradeoff: XPENDING is lightweight for summaries. XREADGROUP returns full data but is heavier. Use XPENDING for frequent health checks, and XREADGROUP for actual reprocessing.

Troubleshooting & edge cases

Empty pending list

  • Symptom: XPENDING returns (empty list or set).
  • Fix: Ensure entries exist and were read but not acknowledged. If no entries have been read, pending list is empty.

Wrong consumer name

  • Symptom: XREADGROUP with STREAMS orders 0 returns zero entries, but XPENDING shows 2.
  • Cause: You specified a consumer name that doesn't own the pending entries.
  • Fix: Use the same consumer name that originally read the entries. Fetch pending consumers from XPENDING output.

ID too old

  • Symptom: XREADGROUP ... STREAMS orders 0 returns all pending entries even after acknowledging some.
  • Cause: 0 always starts from the oldest pending. If you want only new pending entries, pass > instead — but > gives new messages, not pending. For incremental pending reads, store the last processed pending ID and pass it as the stream ID.

Pending entries with no consumer

  • Situation: Consumer died; entries remain pending.
  • Solution: Use XCLAIM to transfer pending entries to another consumer, or read them as a different consumer using XREADGROUP GROUP <group> <new-consumer> STREAMS <key> 0. Redis will show them (they are still assigned to the old consumer) but you can process them and acknowledge.

What you learned & what's next

You can now read pending stream entries using XPENDING and XREADGROUP. You understand: - What pending entries are and why they happen. - How to inspect the pending queue summary. - How to read and reprocess pending messages. - How to compare reading strategies and troubleshoot common issues.

This is critical for building resilient stream consumers. Next, you'll learn XCLAIM — how to transfer ownership of pending entries from a dead consumer to a live one, enabling automatic recovery and scaling.

Practice recap

Open redis-cli and create a stream with 3 entries and a consumer group. Read one entry as consumer A without acknowledging it. Use XPENDING to confirm it's pending. Then read all pending entries using XREADGROUP as consumer A with ID '0'. Acknowledge it and verify the pending count drops to 0.

Common mistakes

  • Using XREADGROUP ... STREAMS orders > expecting to see pending entries — > returns only new messages; use '0' or a specific ID.
  • Forgetting to specify the correct consumer name in XREADGROUP; you'll get zero pending results if the name doesn't match the original consumer.
  • Assuming XPENDING returns the full entry data; it only returns a summary — use XREADGROUP with '0' for full entry content.
  • Not acknowledging entries after reprocessing them, causing the pending list to grow indefinitely.

Variations

  1. Use XPENDING with a range of IDs to inspect only a time window of pending entries.
  2. Use XPENDING with SUMMARY subcommand (Redis 7.0+) to get a cleaner summary.
  3. Combine XPENDING with XREADGROUP in a script to automatically claim and reprocess entries from a stalled consumer.

Real-world use cases

  • Recover from a consumer crash in a payment processing queue — read pending orders, check statuses, and re-route if necessary.
  • Monitor backlog in a job queue — use XPENDING every minute to alert if pending count exceeds a threshold.
  • Implement exactly-once delivery for a notification system — read pending messages, deduplicate, and acknowledge only after successful send.

Key takeaways

  • Pending entries are unacknowledged messages that were delivered to a consumer in a group.
  • Use XPENDING to get a lightweight summary of all pending entries per consumer.
  • Use XREADGROUP ... STREAMS <key> 0 to read the oldest pending entries from the group's pending list.
  • To read pending entries for a specific consumer, specify that exact consumer name in XREADGROUP.
  • Always acknowledge pending entries after successful processing to prevent indefinite growth.
  • Pending entries enable at-least-once delivery and fault recovery in Redis Streams.

Sponsored

Sponsored

Discussion

Questions, corrections, and tips help everyone reading this page.

0 comments

Add a comment

Shown publicly with your comment.

Be constructive · max 4,000 characters

No comments yet — start the thread.

Related tutorials, quizzes, and articles for this topic.