# Acknowledgements and the Pending Entries List — Redis Pub/Sub & Streams

Source: https://www.skillbyai.com/en/redis-streams/g-ack

> Acknowledge processed entries and inspect pending work with XPENDING.

## Delivered is not done

When a consumer receives an entry through `XREADGROUP`, Redis records it in the group's **Pending Entries List (PEL)**: the entry ID, which consumer has it, when it was delivered and how many times (**delivery count**). The entry stays pending until the consumer calls **`XACK stream group id`** after successfully processing it. This gives **at-least-once** delivery: if a consumer crashes before acknowledging, the entry remains pending and can be recovered. A restarted consumer can re-read **its own** pending entries by calling `XREADGROUP` with ID **`0`** instead of `>`, a good first step at start-up. **`XPENDING`** shows the summary (count, smallest and largest IDs, per-consumer counts) or, with arguments, details per entry including idle time and delivery count. A growing PEL means consumers are failing or forgetting to acknowledge. Since entries can be delivered more than once, consumers must be **idempotent**.

## A reliable consumer loop in Python

Process pending entries first, then new ones; acknowledge only after success.

```python
STREAM, GROUP, CONSUMER = "orders", "billing", "worker-a"

def handle(entry_id, fields):
    if already_processed(entry_id):           # idempotency check (e.g. DB unique key)
        return
    create_invoice(fields)

def run():
    # 1. recover our own unacknowledged entries from before a crash
    start = "0"
    while True:
        resp = r.xreadgroup(GROUP, CONSUMER, {STREAM: start}, count=50, block=5000)
        entries = resp[0][1] if resp else []
        if start == "0" and not entries:
            start = ">"                        # 2. pending drained: switch to new entries
            continue
        for entry_id, fields in entries:
            try:
                handle(entry_id, fields)
                r.xack(STREAM, GROUP, entry_id)
            except Exception:
                log.exception("failed %s", entry_id)   # stays pending for retry / claiming
```

## A signed delivery note

Handing a parcel to a courier adds it to the "out for delivery" list (the PEL). Only when the customer signs (XACK) does it leave the list. Parcels that stay on the list too long get investigated.

**Quiz:** What does calling XREADGROUP with ID `0` instead of `>` return?

- [ ] All entries in the stream
- [ ] Only the newest entry
- [ ] Entries from other groups
- [x] Entries already delivered to this consumer but not yet acknowledged

*Answer:* Entries already delivered to this consumer but not yet acknowledged. Using 0 reads the consumer's own pending history, useful for recovery after restarts.
