Module F-9·24 min read

XADD/XREAD/XRANGE, the XREADGROUP consumer group model, XACK and the Pending Entry List, XAUTOCLAIM for crash recovery, dead-letter handling, and when Redis Streams beats Kafka or BullMQ.

JJS
Written by Jatin Jain Saraf · Senior Software Engineer

F-9 — Streams: Append-Only Logs and Consumer Groups

Who this module is for: You have outgrown Pub/Sub — you need messages to survive subscriber disconnections, multiple consumers to share the work of processing a stream, and the ability to replay past events. Redis Streams is the answer. This module covers the full Stream data model, the XADD/XREAD/XRANGE command family, consumer groups, acknowledgement semantics, and how Streams compare to Kafka and traditional queues.


What Redis Streams Are

A Redis Stream is an append-only log — a sequence of entries, each identified by a unique ID and containing a set of field-value pairs. Unlike Pub/Sub, entries are stored durably in Redis (subject to your persistence configuration) and remain available for consumers to read at any time, including after reconnection.

The model is inspired by Apache Kafka's partitioned log, but implemented as a single-node (or clustered) Redis data structure with simpler semantics and lower throughput at extreme scale.

text

Stream Entry IDs

Every stream entry has an ID in the format {milliseconds}-{sequence}:

  • 1716000000001-0 — millisecond timestamp 1716000000001, sequence 0
  • If two entries arrive in the same millisecond, the sequence increments: 1716000000001-1, 1716000000001-2

Redis generates IDs automatically using the current time when you pass *:

text

You can also provide explicit IDs for deterministic streams or replaying historical data:

XADD mystream 1716000000001-0 field1 value1

IDs must be monotonically increasing — you cannot add an entry with an ID earlier than the stream's last entry.


Core Commands

Writing to a Stream

XADD key [NOMKSTREAM] [MAXLEN|MINID [=|~] threshold [LIMIT count]] id field value [field value ...]
text

MAXLEN ~ 1000 (tilde = approximate) trims the stream to approximately 1000 entries. It is more efficient than exact trimming because it trims at listpack node boundaries rather than individual entries. Use approximate trimming in production.

Reading a Range

text

Special IDs:

  • - = minimum possible ID (start of stream)
  • + = maximum possible ID (end of stream)
text

Stream Metadata

text

Trimming

text

Use XTRIM MAXLEN ~ 10000 periodically (or via XADD MAXLEN ~) to prevent unbounded stream growth.


Reading Without Consumer Groups (XREAD)

XREAD lets you read new entries from one or more streams, starting from a given ID:

XREAD [COUNT count] [BLOCK milliseconds] STREAMS key [key ...] id [id ...]
text

The special ID $ means "only entries added after this XREAD command was issued" — useful for a simple consumer that only cares about new events.

XREAD without consumer groups is fan-out, not competing consumers. Two clients both running XREAD BLOCK 0 STREAMS mystream $ will both receive every new entry. This is like Pub/Sub but with persistence — missed-while-blocked messages are available when you reconnect.


Consumer Groups

Consumer groups are the killer feature of Redis Streams. A consumer group allows multiple consumer instances to share the work of processing a stream — each entry is delivered to exactly one consumer in the group, not all of them.

Sign in to keep reading

The rest of this module is free — sign in with Google to unlock it and track your progress.

Sign in & Register

Discussion

0

Join the discussion

Loading comments...

© 2026 Jatin Jain Saraf (JJS). All rights reserved.