When Should You Use Redis as a Message Broker?

Discover the ideal scenarios for leveraging Redis as a message broker using its two primary features: Pub/Sub and Streams. This comprehensive guide details the performance advantages, low latency, and infrastructure benefits of Redis messaging. Learn the crucial differences between ephemeral Pub/Sub and durable Streams, understand their limitations compared to dedicated brokers like Kafka, and find actionable use cases—from simple cache invalidation to robust, lightweight task queues—to help you choose the right tool for your asynchronous communication needs.

When Should You Use Redis as a Message Broker?

Redis can be a good message broker when the job is small, fast, and close to the data you already keep in Redis. It can also be the wrong tool if you need strong delivery guarantees, long retention, routing features, or a broker that keeps working comfortably when message history is much larger than memory.

The decision gets easier if you stop asking "Can Redis do messaging?" and ask "Which Redis messaging primitive matches the failure mode I can tolerate?"

Redis gives you several patterns, but two matter most for this question:

  • Pub/Sub for live broadcasts.
  • Streams for durable, log-like message processing with consumer groups.

They feel similar from far away. They are not similar operationally.

Use Pub/Sub when missing a message is acceptable

Redis Pub/Sub is a live broadcast. A publisher sends to a channel. Connected subscribers receive the message. Redis does not store that message for disconnected subscribers, and there is no built-in acknowledgment.

That is perfect for some jobs:

SUBSCRIBE cache:invalidations
PUBLISH cache:invalidations 'product:123'

If one application instance misses that invalidation because it restarted at the wrong moment, the world should not end. The local cache should have TTLs, version checks, or another way to recover. Pub/Sub is a notification path, not the source of truth.

Good Pub/Sub use cases:

  • Cache invalidation between application instances.
  • Live UI updates where clients only care about current state.
  • Presence signals such as "user is typing" or "worker heartbeat changed."
  • Lightweight deployment or config-change notifications.
  • Fan-out events where missed messages are tolerable.

Bad Pub/Sub use cases:

  • Payment processing.
  • Email jobs that must eventually send.
  • Inventory updates that must not be skipped.
  • Audit logs.
  • Anything where a disconnected consumer must catch up later.

Pub/Sub is fast because it does less. That is the trade.

Use Streams when consumers need to catch up

Redis Streams store entries in a stream data structure:

XADD orders:events * order_id 42 status paid

Consumers can read from a position:

XREAD COUNT 10 STREAMS orders:events 0

Consumer groups let multiple workers share work:

XGROUP CREATE orders:events order-workers 0 MKSTREAM
XREADGROUP GROUP order-workers worker-1 COUNT 10 STREAMS orders:events >
XACK orders:events order-workers 1740000000000-0

With consumer groups, a message delivered to a worker stays in the pending entries list until acknowledged with XACK. If a worker dies after reading but before acknowledging, another worker can inspect and claim pending work. That gives you at-least-once processing when you build the consumer correctly.

At-least-once means duplicates are possible. Your worker must be idempotent. For example, an email worker should record that email_job_id=abc123 was sent before attempting to send again. An order worker should avoid charging twice if it sees the same stream entry twice.

Good Streams use cases:

  • Lightweight background jobs.
  • Internal service events that need replay after short outages.
  • Small to medium event logs.
  • Worker pools where each job should be processed by one worker in a group.
  • Activity feeds or state-change logs with bounded retention.

Streams are not free. Entries live in Redis memory unless trimmed or expired by your design. If you never trim a busy stream, the stream becomes your next memory incident.

Use trimming:

XADD orders:events MAXLEN ~ 100000 * order_id 42 status paid
XTRIM orders:events MAXLEN ~ 100000

Approximate trimming with ~ is usually cheaper than exact trimming. Pick retention based on recovery needs, not hope.

Redis Lists are still useful for simple queues

Before Streams existed, many Redis queues used lists:

LPUSH jobs:email '{"to":"[email protected]"}'
BRPOP jobs:email 5

Lists are still fine for very simple queues, especially when you need blocking pop behavior and do not need consumer groups or history. The limitation is recovery. If a worker pops a job and crashes before finishing, that job is gone unless you add extra bookkeeping.

There are patterns using BRPOPLPUSH or BLMOVE to move jobs into a processing list, then remove them after success. Those patterns can work, but once you need pending tracking, retries, and multiple consumers, Streams usually gives you a clearer starting point.

Choose Redis when simplicity matters more than broker features

Redis messaging is attractive when Redis is already part of your stack and the workload is modest. You avoid operating another distributed system. Developers already understand Redis clients, monitoring, credentials, and deployment paths.

That is a valid reason. Operational simplicity has real value.

Redis is also very low latency. If your application and Redis are in the same region or private network, publishing a tiny notification is usually cheap and quick. For cache invalidation or live status updates, a heavier broker may be unnecessary.

Redis also lets you combine state changes and messages carefully. A Lua script or transaction can update a key and append to a stream in one Redis-side operation. That can be useful for small systems where Redis is the central state holder.

The catch is that Redis should not become your accidental everything-broker. If every service starts adding high-volume streams with no retention plan, the "simple" choice becomes an overloaded in-memory log store.

Choose a dedicated broker when failure handling is the product

Kafka, RabbitMQ, Pulsar, NATS JetStream, and cloud queue services exist because messaging gets complicated quickly.

Use a dedicated broker when you need features such as:

  • Long retention measured in weeks, months, or years.
  • Message history that is much larger than memory.
  • Dead-letter queues and retry policies built into the broker.
  • Delayed delivery, priorities, routing keys, exchanges, or topic partitioning.
  • Cross-region replication patterns designed for messaging.
  • Many independent consumer groups replaying the same event history.
  • Stronger tooling around lag, offsets, rebalancing, and audits.

Kafka is usually a better fit for high-volume event pipelines and replayable logs. RabbitMQ is often a better fit for sophisticated routing, acknowledgments, and work queues. Cloud queues are often better when you want managed durability and simple operational boundaries.

Redis Streams can handle useful production workloads, but it is still Redis. Its data is memory-centered, its persistence settings must be understood, and its broker features are intentionally smaller than dedicated systems.

A concrete way to decide

Ask these questions before choosing Redis:

  1. Can a consumer miss a message without data loss?
  2. Does a disconnected consumer need to catch up?
  3. How long must messages be retained?
  4. Can the retained message data fit comfortably in Redis memory?
  5. Do workers handle duplicate messages safely?
  6. Do you need dead-letter queues, delayed retries, priorities, or routing rules?
  7. Will this traffic interfere with Redis caching or sessions?

If missed messages are acceptable, Pub/Sub may be enough.

If consumers need catch-up and retention is bounded, Streams may be enough.

If message data needs long retention, replay by many teams, complex broker behavior, or strong operational separation, use a dedicated broker.

Example: cache invalidation

An application stores product pages in local process memory and Redis. When a product changes, the admin service publishes:

PUBLISH cache:invalidate product:123

Every web instance subscribed to cache:invalidate deletes its local copy. If one web instance misses the message, its local entry still has a five-minute TTL and it also checks a product version field on the next request. Pub/Sub is fine because there is a recovery path.

Using Kafka here would probably add more operational weight than value.

Example: background email jobs

A user signs up and you need to send a welcome email. If the worker is down for a minute, the job must still send later. Pub/Sub is a poor fit.

A Redis Stream can work:

XADD email:jobs MAXLEN ~ 100000 * job_id abc123 type welcome user_id 42

Workers read through a consumer group, send the email, record job_id as completed, and call XACK. A monitor checks pending jobs and reclaims old ones. This is reasonable for a modest internal queue.

If email delivery becomes large-scale, needs delayed retries, dead-letter handling, per-customer rate limits, and rich operational dashboards, a dedicated queue starts to look better.

Example: audit events

Audit events usually need durability, search, retention, and sometimes legal or compliance handling. Redis Streams may help as a short buffer, but Redis should not be the final audit store. Use a durable log, database, object storage pipeline, or managed event service designed for retention and review.

Operational notes if you choose Redis

For Pub/Sub:

  • Configure client-output-buffer-limit pubsub.
  • Use dedicated subscriber connections.
  • Build reconnect and resubscribe behavior.
  • Treat messages as hints, not durable facts.

For Streams:

  • Set a retention policy with MAXLEN, MINID, or explicit trimming.
  • Monitor pending entries.
  • Make consumers idempotent.
  • Use XACK only after work succeeds.
  • Plan how stalled messages are claimed and retried.
  • Watch memory, persistence, and replication lag.

Redis is a good message broker when you pick the part of Redis that matches the job. Pub/Sub is a live signal. Streams are a bounded durable log. Neither should be chosen just because Redis is already running, but both can be the simplest correct answer when their failure model matches your application.

The uncomfortable middle ground

Many teams land in the middle: Pub/Sub is too lossy, Kafka feels too large, and RabbitMQ feels like one more system to operate. Redis Streams can be a good answer there, but only if you treat it as a real queue and not a magic list.

A healthy Streams design has ownership around these details:

  • Who creates the stream and consumer group?
  • How many consumers are expected?
  • What is the maximum pending age before a message is reclaimed?
  • What happens after repeated failure?
  • How much stream history is retained?
  • What dashboard or alert shows growing lag?

Without those answers, Streams can fail quietly. A worker may read messages and crash before XACK, leaving entries pending forever. Another worker may never claim them. The stream length may keep growing because nobody configured trimming. Redis memory rises, but the application team thinks "the queue is durable," so they do not notice until the instance is under pressure.

A simple worker should usually do this loop:

read a small batch
process each message idempotently
ack only successful messages
periodically inspect pending messages
claim stale pending messages
trim according to retention policy

That is more work than Pub/Sub, and that is the point. Durability always moves complexity somewhere. Redis Streams keeps the broker side fairly small, but the application still owns retries, dead-letter behavior, and idempotency.

If nobody on the team wants to own those details, a managed queue may be cheaper in the long run even if it looks heavier on day one. The best broker is not the fastest one in a benchmark. It is the one whose failure behavior your team can operate at 3 a.m. without guessing.