Migrating production infrastructure? Get up to ₹10,000 in migration credits. Apply now
voroaBlog
Key Value

Redis Pub Sub Explained: How It Works and Where It Breaks

· · 9 min read

Redis Pub/Sub explained with tests: offline subscribers, fan-out latency and slow subscribers

Redis pub sub lets one part of your system send a message to every other part that is listening, through a named channel, in under a millisecond when both sides are close to the server. It is the simplest way to broadcast events between processes, and it is also the feature people most often use for the wrong job. This guide is for developers wiring up real-time updates, cache invalidation or chat. It shows the commands, and then what we saw when we tested the behaviour that tutorials skip: a subscriber that goes offline, a hundred subscribers at once, and a subscriber that cannot keep up.

On this page

Advertisement

What Pub/Sub is, in one paragraph

A publisher sends a message to a channel with PUBLISH. Every client subscribed to that channel at that moment receives it. Redis does not store the message anywhere: if nobody is listening, it is gone, and PUBLISH tells you how many clients received it. The official documentation calls this at-most-once delivery: a message arrives once, or not at all.

Diagram: a publisher sends one message to a channel; three connected subscribers receive it; a disconnected subscriber receives nothing and the message is not stored

The commands, with working code

Command What it does
SUBSCRIBE orders Listen to one or more channels
PSUBSCRIBE orders.* Listen to every channel matching a pattern
PUBLISH orders "..." Send a message; returns the number of receivers
UNSUBSCRIBE / PUNSUBSCRIBE Stop listening
SSUBSCRIBE / SPUBLISH Sharded Pub/Sub for clusters (Redis 7 and later)
PUBSUB CHANNELS / PUBSUB NUMSUB See which channels have subscribers

Over the RESP2 protocol, which most clients use by default, a connection that has subscribed can only run subscription commands, so a client needs a separate connection for its normal reads and writes.

Node.js (with ioredis):

js
import Redis from 'ioredis';

const sub = new Redis(process.env.REDIS_URL);   // subscribe-only connection
const pub = new Redis(process.env.REDIS_URL);   // normal connection

await sub.subscribe('orders');
sub.on('message', (channel, message) => {
  const order = JSON.parse(message);
  console.log('new order', order.id);
});

const receivers = await pub.publish('orders', JSON.stringify({ id: 42 }));
console.log('delivered to', receivers, 'subscribers');

Python (with redis-py):

python
import os, json, redis

r = redis.from_url(os.environ["REDIS_URL"])
p = r.pubsub()
p.subscribe("orders")

r.publish("orders", json.dumps({"id": 42}))

for msg in p.listen():
    if msg["type"] == "message":
        print("new order", json.loads(msg["data"])["id"])

Test 1: a subscriber goes offline

Setup for all three tests: Redis 8.10.3 in a Linux virtual machine (WSL2) on a laptop, with a Node.js 24 client using ioredis on the same laptop. Default configuration. Measured 7 November 2026.

We connected a subscriber and published 100 messages: it received all 100. Then we disconnected it, published 100 more, and reconnected.

Step Result
Subscriber online, 100 messages published 100 received
Subscriber offline, 100 messages published PUBLISH returned 0 receivers, 100 times
Subscriber reconnects 0 of those 100 messages received

This is the single most important fact about Pub/Sub. A deploy, a network blip, or a client reconnecting after a Redis restart all create a gap, and every message published in that gap is lost for good. If a missed message means a missed payment, a missed email or an order nobody sees, Pub/Sub is the wrong tool. Use a Redis job queue or Streams instead.

The return value of PUBLISH is your only signal. If it is 0 when you expected listeners, log it.

Test 2: one message, a hundred listeners

We published 1,000 messages to 1, 10 and 100 subscribers, each message carrying the time it was sent, and measured how long each one took to arrive.

Subscribers Messages delivered Median delivery time 99th percentile
1 1,000 of 1,000 0.38 ms 0.81 ms
10 10,000 of 10,000 0.76 ms 7.06 ms
100 100,000 of 100,000 7.09 ms 25.51 ms

Every message reached every subscriber. Delivery time grew with the number of listeners, but read this table carefully: all our subscribers ran inside one Node.js process, so the higher numbers include our own client working through 100 connections one by one. We expect, but did not measure, that subscribers spread across separate processes or servers would each see something closer to the single-subscriber figure, plus the one-way network time from Redis.

Raw throughput is high. Publishing 100,000 small messages in a pipeline to one subscriber, all of them arrived in 664 ms, about 150,000 messages a second.

Test 3: the subscriber that cannot keep up

This one is rarely tested, and it causes real outages. Redis buffers outgoing messages for each subscriber. If a subscriber reads more slowly than messages arrive, its buffer grows. Redis protects itself with a limit, and the default for Pub/Sub clients is pubsub 32mb 8mb 60: disconnect the client if its buffer passes 32 MB, or stays above 8 MB for 60 seconds. We confirmed both Redis 8.10.3 and Valkey 9.1.3 ship with that default.

We subscribed with a client that stopped reading its socket, then published 1 KB messages as fast as we could.

What happened Result
Messages published before Redis disconnected the subscriber 34,486 (about 34 MB)
Time to disconnection about 10 seconds
client_output_buffer_limit_disconnections in INFO stats 1

Redis closed the subscriber’s connection. A normal client such as ioredis sees that close, reconnects and subscribes again, so from the outside it looks healthy. But everything that was sitting in the 32 MB buffer, and everything published while it reconnected, is lost, exactly as in Test 1. In production this looks like “live updates stop working under load”, and the cause is a subscriber doing slow work, such as a database write, inside its message handler.

Two habits prevent it:

  • Do almost nothing in the handler. Push the message onto an in-process queue or a job queue and return. Never wait on a database or an API inside on('message').
  • Watch the counter. client_output_buffer_limit_disconnections going up means a subscriber is being cut off. See the client output buffer settings in the Redis documentation.

Pub/Sub, Streams or a job queue?

Pub/Sub Streams Job queue (built on lists or streams)
Message stored? No Yes, until trimmed Yes, until processed
Offline consumer catches up? No Yes Yes
Every listener gets every message? Yes Yes, or split across a consumer group No, each job goes to one worker
Acknowledgement and retry No Yes (consumer groups) Yes
Typical use Live updates, cache invalidation Event logs, activity feeds, audit trails Emails, PDFs, image processing

The rule of thumb: Pub/Sub for things that only matter right now, Streams for events you might need to replay, a job queue for work that must happen even if it occasionally runs twice.

What it is good at

  • WebSocket fan-out across servers. When you run two or more instances of a chat or live-dashboard app, a message received by one instance has to reach users connected to the others. Socket.IO’s Redis adapter does exactly this with Pub/Sub.
  • Cache invalidation. When one service updates a record, it publishes the key, and every instance drops its in-memory copy. A missed message only means one stale cache entry until its expiry.
  • Live progress. A worker publishes “50% done” and the web server pushes it to the browser. If one update is lost, the next one replaces it.
  • Keyspace notifications. Redis can publish an event when a key expires or changes, which is useful for light-weight triggers. They are off by default (notify-keyspace-events), a managed host may not let you turn them on, and they are fire-and-forget too, so do not build billing on them.

In every one of these, losing an occasional message is harmless. That is the test to apply.

Running it on a managed store

Count your connections. Every subscriber holds a connection open for as long as it listens, and it needs a second one for normal commands. Ten app instances with one subscriber and one normal connection each use 20 connections before any job queue or admin tool. Check your plan’s connection limit; Voroa’s free key value size allows 50, and paid sizes from the Rs 425 one upward allow 250 or more.

Keep subscribers close. Pub/Sub delivery is one network hop from Redis to each subscriber. From a server in Mumbai we measured about 9 ms to a Mumbai region and about 86 ms to Singapore, per round trip; the numbers are in our free Redis hosting comparison. Put the store in the same region as the app.

Expect reconnects. A managed store restarts for maintenance and upgrades. ioredis resubscribes automatically after a reconnect by default; check how your own client behaves. Either way, messages sent during the gap are gone, as Test 1 showed.

Engine choice does not matter here. Pub/Sub is part of the core both engines share, and we confirmed Valkey 9.1.3 ships the same buffer default; our tests themselves ran on Redis. See Valkey vs Redis for where they do differ.

On Voroa, your web services and background workers reach a key value store over the private network in Mumbai, using the connection URL you add as an environment variable. Setup details are in the Key Value guide.

FAQ

Does Redis store Pub/Sub messages? No. A message goes only to clients subscribed at that moment. If none are, it is discarded and PUBLISH returns 0.

Is Redis Pub/Sub reliable? It is fast and simple, but delivery is at-most-once. In our tests, messages sent while a subscriber was offline were never delivered, and a subscriber that fell 32 MB behind was disconnected.

What is the difference between Redis Pub/Sub and Streams? Streams store messages, so a consumer can read what it missed, acknowledge messages and retry. Pub/Sub stores nothing.

How many subscribers can Redis Pub/Sub handle? Many; the practical limit is connections and network. In our test, 100 subscribers each received all 1,000 messages. Plan around your store’s connection limit.

Why does my subscriber stop receiving messages? Usually a reconnect gap or a slow handler that pushed its buffer past the limit. Check client_output_buffer_limit_disconnections in INFO stats.

Can I use Pub/Sub as a job queue? No. A job published while no worker is listening is lost, and two workers would both run it. Use a queue library or Streams with a consumer group.

Add a key value store for live updates

A Redis-compatible store in Mumbai, next to the services you deploy, with a free size to start. See the sizes on the managed Redis page.

Tested 7 November 2026 on Redis 8.10.3 in WSL2 on a laptop; Valkey 9.1.3’s buffer default confirmed. Found something out of date? Write to [email protected].

Advertisement