Pub/sub, queues and persistence

Fire-and-forget messaging versus durable streams, reliable queue patterns with lists, and the real durability guarantees of RDB and AOF.

Pub/sub and streams

# pub/sub: at most once, no storage, offline subscribers miss the message
SUBSCRIBE events:orders
PSUBSCRIBE events:*
PUBLISH events:orders '{"id":42,"status":"paid"}'

# streams: durable, replayable, with consumer groups and acknowledgements
XADD    stream:orders '*' id 42 status paid
XLEN    stream:orders
XRANGE  stream:orders - +
XREAD   COUNT 10 BLOCK 5000 STREAMS stream:orders $

XGROUP  CREATE stream:orders workers $ MKSTREAM
XREADGROUP GROUP workers w1 COUNT 10 BLOCK 5000 STREAMS stream:orders >
XACK    stream:orders workers 1726585200000-0
XPENDING stream:orders workers          # what was delivered but not acked
XAUTOCLAIM stream:orders workers w1 60000 0-0   # reclaim idle messages
MechanismDeliverySurvives restart
Pub/sub channelAt most once, only to connected subscribersNo — messages are not stored
StreamsAt least once with consumer groups and XACKYes, subject to persistence config
List as a queueAt most once if the worker crashes after poppingDepends on persistence
List with a processing listAt least once via LMOVE and manual cleanupDepends on persistence
⚠️
Pub/sub silently drops messages for any subscriber that is offline or slower than the publisher. If losing a message matters, use a stream with a consumer group — a channel is a notification, not a queue.

Reliable queue patterns

# naive: the message is gone if the worker dies after BRPOP returns
BRPOP queue:email 5

# reliable: move to a per-worker processing list atomically, then remove on success
LMOVE queue:email processing:w3 LEFT RIGHT
LREM  processing:w3 1 "m-1"         # on success
LPUSH queue:email "m-1"            # on failure: put it back

# a separate sweeper re-queues anything stuck too long
LRANGE processing:w3 0 -1
  • BRPOP removes the element before the work is done, which makes it at-most-once. LMOVE into a processing list turns it into at-least-once, at the cost of a sweeper for stuck entries.
  • At-least-once means your handler must be idempotent — the same message can be delivered twice after a retry.
  • Streams give you the same guarantee without hand-rolling the processing list: XPENDING and XAUTOCLAIM cover worker failure.
  • Never put cache keys and job keys that must not be lost on the same instance with an allkeys-* eviction policy.

Persistence and replication

# redis.conf
save 900 1            # RDB snapshot after 1 change in 900s
save 300 10
appendonly yes        # AOF on
appendfsync everysec  # fsync once per second: at most ~1s of writes lost
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb

# on a replica
REPLICAOF 10.0.0.10 6379
WAIT 1 100            # block until 1 replica acknowledges, max 100 ms
OptionHow it worksWhat you can lose
RDB onlyForked point-in-time snapshots, fast restartEverything since the last snapshot
AOF everysecAppend each write, fsync about once per secondUp to about one second of writes
AOF alwaysfsync on every writeAlmost nothing, but much slower
AOF noLet the operating system flushWhatever the OS has not written — can be a lot
RDB + AOFAOF used for replay, snapshot for fast bootstrapSame as the AOF setting

Replication is asynchronous by default, so a primary that fails can lose writes that no replica has yet received. WAIT narrows the window but does not eliminate it, and it is not a substitute for a transactional commit protocol.

FAQ

Is Redis a database or a cache?
It can be either, and the durability settings are where you decide. With appendfsync always and a replica you get a durable store; with RDB snapshots and an LRU policy you have a cache that happens to survive restarts sometimes.
How do I process a stream from several workers?
Create one consumer group, then let each worker issue XREADGROUP ... > under its own consumer name. Messages delivered but not acknowledged stay in the pending list, where XAUTOCLAIM can hand them to a healthy worker.

Expiry and caching patterns Data types and the commands that matter

Last refreshed 2026-09-18.