Streams and consumer groups

Redis · Advanced · 15 min

Streams give Redis durable, replayable, at-least-once messaging for event processing and work queues.

A stream is an append-only log. XADD orders * item pen qty 2 appends an entry whose ID (* = auto) is <milliseconds>-<sequence>, so entries are time-ordered. XRANGE key - + reads a range, XREAD BLOCK 5000 STREAMS key $ waits for new entries, and XLEN counts them. Unlike Pub/Sub, entries stay until trimmed, so readers can replay history.

Consumer groups distribute entries among workers:

  • XGROUP CREATE orders billing $ MKSTREAM creates a group starting at new entries (0 to process history),
  • each worker calls XREADGROUP GROUP billing worker-1 COUNT 10 BLOCK 5000 STREAMS orders >; > means entries never delivered to this group, and each entry goes to only one consumer in the group,
  • delivered entries sit in the group's pending entries list (PEL) until the worker calls XACK.

If a worker crashes, its entries stay pending. Other workers find them with XPENDING and take them over with XAUTOCLAIM after an idle timeout, giving at-least-once delivery. Make handlers idempotent, because an entry may be processed twice.

Several groups can read the same stream independently, like separate subscriptions. Cap growth with XADD key MAXLEN ~ 100000 * ... or XTRIM; the ~ allows efficient approximate trimming. Never trim entries that are still pending if you need them.

Example

redis-cli XGROUP CREATE orders billing '$' MKSTREAM       # OK
redis-cli XADD orders '*' item pen qty 2                  # "1760700000000-0"
redis-cli XADD orders '*' item pad qty 1                  # "1760700000001-0"

redis-cli XREADGROUP GROUP billing w1 COUNT 1 STREAMS orders '>'
# 1) 1) "orders"
#    2) 1) 1) "1760700000000-0"
#          2) 1) "item" 2) "pen" 3) "qty" 4) "2"
redis-cli XPENDING orders billing                         # 1 pending, owned by w1
redis-cli XACK orders billing 1760700000000-0             # (integer) 1

# w1 died holding an entry? another worker claims entries idle > 60 s
redis-cli XAUTOCLAIM orders billing w2 60000 0-0 COUNT 10
redis-cli XADD orders MAXLEN '~' 100000 '*' item ink qty 5   # append and trim

Remember

  • XADD appends entries with time-based IDs; entries persist until trimmed
  • Consumer groups split entries among consumers; > reads new entries
  • Entries stay pending until XACK; XAUTOCLAIM recovers from crashed workers
  • Delivery is at-least-once: make handlers idempotent

Watch out for

  • Never calling XACK: the pending list grows forever and memory leaks.
  • Letting a stream grow unbounded without MAXLEN or XTRIM.

Check yourself

  1. A consumer reads an entry with XREADGROUP ... > and crashes before XACK. What happens to the entry?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: redis.io/docs/latest/develop/data-types/streams/

Builds on

Unlocks

Nothing builds on this yet.