Chapter 07 · Redis Streams and Consumer Groups
XREAD for Blocking/Nonblocking Consumption and Replay from Explicit IDs
Consume and replay Redis Streams with XREAD using explicit client checkpoints, bounded blocking, and correct dollar-ID semantics.
Learning outcomes
AtlasMart analytics wants to replay order events from a known
checkpoint, while a live dashboard wants to wait for events that
arrive after it connects. XREAD serves both cases
without creating consumer-group state. The client supplies a
last-seen ID for each stream, and Redis returns entries whose
IDs are greater than that position.
Use XREAD with explicit IDs for deterministic replay and incremental polling.
Explain the special dollar ID and why it should normally be used only on the first “new events only” read.
Use BLOCK and COUNT without confusing blocking with ownership, durability, or load balancing.
Explain fan-out behavior: independent XREAD clients can receive the same entries.
Label Redis 8.10 MAXCOUNT/MAXSIZE as current cross-stream reply-budget extensions.
All Chapter 07 mandatory labs reuse the disposable Chapter 01
environment: Redis Open Source 8.10.1 from Docker
Official Image redis:8.10.1, container
atlasmart-redis-ch01, standalone topology, host
publication 127.0.0.1:6379, TLS disabled only
because traffic stays on loopback, default ACL user disabled,
named ACL users atlasmart-app and
academy-admin, logical database 0, AOF with
appendfsync everysec plus RDB snapshots,
persistent /data volume, and no explicit Redis
maxmemory limit or eviction policy. The primary
interface is the redis-cli shipped in the same
pinned image. Mandatory examples use only bounded synthetic
keys under atlasmart:ch07:*. No managed service,
paid broker, or external API is required.
1. XREAD position means “strictly greater than this ID”
Unlike XRANGE, which asks for an ID interval,
XREAD asks for entries after a checkpoint. The
client must remember the last ID it processed and supply that ID
on the next call.
docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app DEL atlasmart:ch07:xreaddocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app XADD atlasmart:ch07:xread 1000-0 event createddocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app XADD atlasmart:ch07:xread 2000-0 event paiddocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app XADD atlasmart:ch07:xread 3000-0 event shippeddocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app XREAD COUNT 2 STREAMS atlasmart:ch07:xread 0-0docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app XREAD COUNT 2 STREAMS atlasmart:ch07:xread 2000-0
The first read returns IDs greater than 0-0, capped at two. The second returns only entries after 2000-0—here, 3000-0. Persisting the checkpoint is an application responsibility when restart replay matters.
2. Incomplete IDs and exact checkpoints
For XREAD, an incomplete ID such as 2000 is
interpreted as 2000-0. For production
checkpointing, storing the complete ID avoids ambiguity and
makes logs/reconciliation easier to reason about.
3. The dollar ID means “start with future arrivals”
The special $ position says: use the stream's
current top entry as the starting point, so only entries
appended after this XREAD begins are eligible. This is useful
for live tails, not history replay.
docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app XREAD BLOCK 1000 COUNT 10 STREAMS atlasmart:ch07:xread '$'
If no new entry arrives during the timeout, the reply is null. That is expected—not an error. To see the command unblock, run it in one terminal and append a new entry from another terminal before the timeout expires.
4. Wrong approach: use dollar on every loop
If a client receives an entry and then starts its next read with
$ again, it discards the precise last-seen
checkpoint. Entries that arrive between loops can be skipped.
The correct pattern is: use $ only for the initial
“new events from now on” decision, then use the last returned ID
on every subsequent read.
first call: XREAD ... STREAMS orders '$'after reply: remember 1720000000123-0next call: XREAD ... STREAMS orders 1720000000123-0next reply: update checkpoint to the last returned ID
5. BLOCK reduces polling, but it creates a blocked connection
BLOCK milliseconds waits when there is no
immediately eligible data. BLOCK 0 means wait
indefinitely. The connection is occupied by the blocking
operation until data arrives or the timeout ends. Client
libraries must therefore define connection-pool/multiplexing
behavior and cancellation/timeouts explicitly.
docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app XREAD BLOCK 500 COUNT 5 STREAMS atlasmart:ch07:xread 3000-0
The expected null response after about the timeout proves only that no later entry arrived during that wait. Redis timeout resolution and end-to-end latency are environment-dependent; do not use this as a benchmark.
6. XREAD is fan-out, not competing-consumer distribution
Two independent clients reading the same stream from the same ID can both receive the same entries. XREAD does not assign ownership or maintain a Pending Entries List. When AtlasMart needs “one worker from the group owns this attempt,” consumer groups are the appropriate Stream mechanism.
7. Multiple streams: one request, one position per key
XREAD can wait on multiple streams. The command lists all keys first, then one ID for each key in the same order. This is useful for a client combining several feeds, but Cluster routing and client support must be verified for multi-key use.
docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app DEL atlasmart:ch07:inventory atlasmart:ch07:paymentsdocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app XADD atlasmart:ch07:inventory 1000-0 sku A change -1docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app XADD atlasmart:ch07:payments 1500-0 order 5001 status authorizeddocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app XREAD COUNT 10 STREAMS atlasmart:ch07:inventory atlasmart:ch07:payments 0-0 0-0
Each stream is returned with entries greater than its own supplied position. XREAD does not globally sort entries from different streams into one total order.
8. Redis 8.10 MAXCOUNT and MAXSIZE are cross-stream reply budgets
Redis 8.10 added MAXCOUNT (total entries across all
streams) and MAXSIZE (reply-size budget in bytes)
to XREAD. These are useful for controlling response
amplification in multi-stream reads. They are version-sensitive;
clients may expose them later than the server.
COUNT remains a per-stream limit.
9. A replay checkpoint is state and needs a durability policy
Without consumer groups, Redis does not remember where each XREAD client stopped. If a process crashes after processing entry 2000-0 but before persisting its checkpoint, it may replay that entry after restart. If it persists 2000-0 before the business side effect completes, it can skip unfinished work. This is the same fundamental “effect versus checkpoint” ordering problem that consumer groups make more visible through PEL state; idempotency is still required for robust processing.
10. Retention limits how far replay can go
A checkpoint older than the retained first entry cannot resurrect data that trimming removed. A client can resume from its ID, but only entries still present in the Stream are returned. Therefore replay horizon and retention horizon must be designed together.
11. Reproducible XREAD lab
Use explicit IDs so all expected results are deterministic.
docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app DEL atlasmart:ch07:replay-labdocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app XADD atlasmart:ch07:replay-lab 10-0 n 1docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app XADD atlasmart:ch07:replay-lab 20-0 n 2docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app XADD atlasmart:ch07:replay-lab 30-0 n 3docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app XREAD COUNT 1 STREAMS atlasmart:ch07:replay-lab 0-0docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app XREAD COUNT 10 STREAMS atlasmart:ch07:replay-lab 10-0docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app XRANGE atlasmart:ch07:replay-lab - +
Verify that the first XREAD returns 10-0, and the second starts strictly after 10-0. XRANGE is the independent history check.
12. Production judgment
XREAD is appropriate for simple tails/replays where every reader maintains its own checkpoint and duplicate-tolerant processing. Use finite blocking timeouts when operational cancellation matters, bound replies, persist checkpoints deliberately, and measure lag externally as “latest retained/produced ID versus client checkpoint.” Use consumer groups when ownership, pending state, acknowledgment, and work distribution are required.
13. Summary and next step
XREAD moves the replay cursor into the client. Next, consumer groups move delivery ownership and pending/acknowledgment state into Redis.
Check your understanding
- What does XREAD return relative to the supplied ID?
- When is the special dollar ID appropriate?
- Do two XREAD clients compete so only one receives each entry?
- Who persists an XREAD checkpoint?
- What did Redis 8.10 add to XREAD?
Review the answers
Entries with IDs strictly greater than the supplied ID.
Normally for the first call when a client intentionally wants only entries arriving from now onward.
No. Independent XREAD clients may each receive the same entries.
The application/client does.
MAXCOUNT and MAXSIZE cross-stream reply-budget options.
Authoritative references
- Redis Streams — stream entries, consumer groups, pending entries, acknowledgments, and recovery
- XADD — entry IDs, trimming, Redis 8.2 reference policies, and Redis 8.6 idempotent production options
- XRANGE — ordered range inspection and exclusive continuation IDs
- XREAD — blocking/nonblocking reads, explicit IDs, and the special dollar ID
- XGROUP CREATE — consumer-group starting position and MKSTREAM
- XREADGROUP — group delivery, pending history, and new-message marker
- XPENDING — PEL summary/details, idle time, owner, and delivery count
- XACK — acknowledgment semantics
- XCLAIM — explicit ownership transfer and retry-count behavior
- XAUTOCLAIM — cursor-like stale-pending recovery
- XINFO GROUPS — pending count, last-delivered ID, entries-read, and lag
- XTRIM — exact/approximate retention and PEL reference policies
- Redis 8.10 release notes — pinned server release family