Redis / Intermediate: application correctness and coordination / Lesson 11
Streams, consumer groups and replay
Use stream history and pending-entry state without confusing delivery, acknowledgment and exactly-once side effects. Define retention and recovery before consumer failure.
On this page
Lesson goals
- Explain stream IDs, consumer groups and pending entries.
- Design idempotent replay and bounded history lifecycle.
A stream records ordered entries with stable identities
A Redis stream is an append-oriented structure with entry IDs and field/value content. It can support event history and consumer-group delivery, but its durability still depends on persistence and replication choices. Entry order does not establish an external event’s true business timestamp or globally synchronize every producer clock. Include the event identity and any required occurrence time explicitly. Consumer groups track delivery progress and pending entries for their consumers; they are not automatic acknowledgments that an external system completed the job. Choose one or multiple independent groups according to the service’s consumption requirements.
Separate new delivery from acknowledgment and deletion
Assume the named stream and group do not exist. The explicit entry ID keeps the expected teaching identity deterministic. Creating a group at zero allows the earlier entry to be delivered. Reading with > asks for new entries for this group, and acknowledgment removes the entry from its pending list rather than from the stream itself. XLEN therefore remains one after a successful acknowledgment. These commands are only a non-runnable sequence; no group, consumer, key or worker is created here. Existing fixture state would change the outcomes and can cause errors.
XADD lab:events 1-0 kind created
XGROUP CREATE lab:events workers 0
XREADGROUP GROUP workers worker-a COUNT 1 STREAMS lab:events >
XACK lab:events workers 1-0
XLEN lab:events
Delivered ID: 1-0
Acknowledged pending entries: 1
Remaining stream length: 1
The read delivers 1-0, XACK returns 1 for that pending entry, and XLEN remains 1. Acknowledgment is not stream deletion.
Pending work needs ownership and replay rules
If a consumer fails after delivery, its entry can remain pending. XPENDING and supported claiming commands expose or transfer pending ownership according to the protocol. XAUTOCLAIM was introduced in Redis 6.2 and does not by itself prove that the original worker has stopped or that a side effect never occurred. Define idle thresholds, consumer identity and duplicate handling from the workload, not a universal copied timeout. A slow worker and a dead worker are different cases. Retrying an entry after takeover may repeat an already completed external action, so stable business idempotency remains necessary.
Model acknowledgment state without losing history
The offline dictionaries separate stream history from a group’s pending set. Acknowledging an entry removes only the pending marker in this model, preserving the original event. Real Redis group metadata, replay, trimming and deletion have more detailed versioned behavior and must be checked against the deployed command set. The simple assertion is useful because it catches an application misconception that acknowledgment equals destructive removal. It does not implement distributed delivery, persistence, ownership transfer or exactly-once processing, and it creates no actual stream.
history = {'1-0': {'kind': 'created'}}
pending = {'1-0'}
pending.remove('1-0') # local model of acknowledgment
assert '1-0' not in pending
assert history['1-0']['kind'] == 'created'
assert len(history) == 1
The entry remains in the history while its pending marker is gone. Only local synthetic state is checked.
Retention, lag and rebuild belong to the same design
Trimming or deleting stream history can make replay impossible or remove payloads needed by pending consumers. Newer versions add commands and options for interactions between stream entries and group references; use the exact baseline rather than assuming every deployment supports them. Monitor oldest pending work, consumer lag, retention horizon and memory together. Choose how to rebuild from an authoritative source if required history is lost. Pub/Sub is a different delivery primitive and does not replace retained stream history. A group name or high acknowledgment rate is not proof that business effects are complete or recoverable.
Common errors and fixes
An acknowledged entry is assumed deleted
- Why it happens
- Pending state was confused with stream history.
- How to fix it
- Review XACK semantics and separate cleanup/retention policy.
- Prevent it next time
- Test history and pending counts independently.
Claimed work produces duplicate side effects
- Why it happens
- Ownership transfer was treated as proof that processing never happened.
- How to fix it
- Use stable event identity and an idempotent effect protocol.
- Prevent it next time
- Test crash after effect but before acknowledgment.
Consumers cannot replay required old events
- Why it happens
- Retention was shorter than tolerated lag or recovery time.
- How to fix it
- Use the documented rebuild path and review retention capacity.
- Prevent it next time
- Monitor the gap between consumer progress and history horizon.
Check your understanding
1. A consumer handles an entry and crashes before XACK. What can a recovery consumer safely conclude?
Show worked solution
The entry may remain pending, but that does not prove its business effect was absent. A recovery consumer must use the event’s stable identity and the designed effect/checkpoint protocol before reapplying it. Claiming and acknowledging support delivery management; they do not atomically coordinate an external system with Redis.
2. After XACK, XLEN remains unchanged. Is that evidence acknowledgment failed?
Show worked solution
Not necessarily. XACK removes the pending reference for the group, not the stream entry itself. Inspect the pending state and the command’s result, then evaluate retention separately. Deleting history just to make XLEN fall can destroy replay needed by other consumers or groups, so cleanup must follow an explicit policy.
Key takeaways
- History, delivery, pending ownership and business completion are distinct.
- Replay requires idempotency and a sufficient retention horizon.