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.

Intermediate135–165 minutesXREAD replay and blocking labRedis Open Source 8.10.1Free/local-firstLast reviewed: September 6, 2026

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.

01

Use XREAD with explicit IDs for deterministic replay and incremental polling.

02

Explain the special dollar ID and why it should normally be used only on the first “new events only” read.

03

Use BLOCK and COUNT without confusing blocking with ownership, durability, or load balancing.

04

Explain fan-out behavior: independent XREAD clients can receive the same entries.

05

Label Redis 8.10 MAXCOUNT/MAXSIZE as current cross-stream reply-budget extensions.

Exact lab baseline

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.

redis-cli · deterministic replay from explicit IDs
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.

redis-cli · bounded live-tail wait
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.

text · correct client-state transition
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.

redis-cli · finite block for a safe lab
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.

redis-cli · two-stream replay
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.

redis-cli · checkpoint/replay verification
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

  1. What does XREAD return relative to the supplied ID?
  2. When is the special dollar ID appropriate?
  3. Do two XREAD clients compete so only one receives each entry?
  4. Who persists an XREAD checkpoint?
  5. 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

Keep knowledge open

Help the academy stay free and grow.

If these tutorials save you time, a small donation supports new lessons, technical review, diagrams, examples, and long-term maintenance.

ETHEthereum / ERC-20 only
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0

Send only Ethereum or ERC-20 compatible assets to this address.