Guide

Pub/Sub and Simple Queues

Part 5 of 6By Amith Kumar8 min read
Part 5 of 6Redis: Caching, Sessions and Queues: A Hands-On Guide
  1. 1Install Redis and the Data Model
  2. 2Caching Patterns: Cache-Aside, TTL and Eviction
  3. 3Persistence and Durability: RDB and AOF
  4. 4Session Store and Rate Limiter
  5. 5Pub/Sub and Simple Queues
  6. 6Security Hardening
Verified 22 Jul 2026 · Ubuntu 24.04 LTS · Redis 7.0.15

Where redis pub/sub fits, and where a queue fits instead

So far every value has sat still, waiting to be read. Once data has to move between processes instead, you reach for one of three tools, and redis pub/sub is the first. Pub/sub fans a message out to whoever is listening right now. A list turns Redis into a work queue that one worker drains in order. Streams add durability when losing a message is not acceptable. This chapter runs all three on a live Ubuntu 24.04 server so you can see which fits which job.

Key takeaways
  • Pub/sub delivers a published message to every current subscriber and to nobody else, so a subscriber that was offline never sees it.
  • A list with LPUSH and BRPOP is a durable work queue: the job waits in Redis until a worker blocks on BRPOP and pulls it.
  • BRPOP blocks the worker until a job arrives or a timeout passes, so the worker idles cheaply instead of polling in a loop.
  • Streams keep an append-only log with consumer groups and acknowledgements, the right upgrade when a job must not be lost.

The distinction that trips people up is delivery. Pub/sub is fire-and-forget: fast, simple, and lossy, because a message goes only to connections subscribed at that moment. A queue is the opposite, since the job sits and waits until something takes it. Choosing wrongly means either dropped work or a design fighting the tool.

We will start with pub/sub across two clients, build a list queue a worker drains, then meet Streams as the durable version. Two redis-cli sessions stand in for two processes.

Let's build.

Prerequisites

Before you start
  • An Ubuntu 24.04 LTS box running Redis 7.0.15, ready since part one.
  • Two terminals into the box, so one can subscribe while the other publishes.
  • The list commands from part one, since the work queue is built on them.

Redis pub/sub fans a message out to live subscribers

Pub/sub has two roles. A subscriber connects and listens on a channel. A publisher sends a message to that channel, and Redis pushes it to every current subscriber. Open two sessions: in the first, subscribe and wait; in the second, publish.

# terminal 1: subscribe and wait for messages
redis-cli SUBSCRIBE alerts

# terminal 2: publish two messages to the same channel
redis-cli PUBLISH alerts "disk at 85% on cache node"
redis-cli PUBLISH alerts "backup completed"
One redis-cli session subscribed to a channel receiving two messages the moment a second session publishes them

The subscriber prints a confirmation, then the two messages the instant they are published. Each PUBLISH returns the number of subscribers that received it, one here. Turn off the subscriber, publish again, turn it back on, and that message is gone: pub/sub keeps no history. It suits live fan-out, a dashboard tailing events or cache-invalidation signals, where only listeners that are connected need to care.

A work queue with LPUSH and BRPOP

A queue is the other shape. A producer pushes jobs onto a list and they wait there. A worker pulls them one at a time and processes each. Push on the left with LPUSH, pull from the right with BRPOP, and the list behaves as first in, first out.

redis-cli LPUSH q:emails "email:welcome:1042" "email:receipt:1043" "email:digest:1044"
redis-cli LLEN q:emails
redis-cli BRPOP q:emails 5
redis-cli BRPOP q:emails 5
redis-cli LLEN q:emails
Three jobs pushed onto a list with LPUSH and a worker pulling them one at a time in order with a blocking BRPOP

LLEN shows three jobs waiting. Each BRPOP returns the channel name and the oldest job, draining the list in the order the jobs were pushed. The B is the point: BRPOP blocks for up to five seconds waiting for a job rather than returning empty. A real worker calls it with a timeout of zero, so it sleeps until work arrives and wakes the moment it does, with no busy polling. Unlike pub/sub, a job pushed while no worker is running still sits in the list, waiting.

Checkpoint

LLEN q:emails should read 3 after the push, then 1 after two BRPOP calls drain the two oldest jobs. If a BRPOP hangs for the full five seconds and returns nothing, the list was already empty, so push the jobs again before draining.

Warning A plain list queue drops a job if the worker crashes after BRPOP removes it but before it finishes. When a job must not be lost, use BLMOVE to move it onto a processing list until it is done, or use Streams below, which track delivery and let you re-claim a job a worker never acknowledged.

Streams are the durable upgrade

Streams are an append-only log that keeps its entries. Producers append with XADD, and each entry gets an ID and a set of fields. Readers can replay from any point, and consumer groups let several workers share a stream while Redis tracks who processed what.

redis-cli XADD stream:orders "*" order_id 5573 amount 349
redis-cli XADD stream:orders "*" order_id 5574 amount 1299
redis-cli XLEN stream:orders
redis-cli XRANGE stream:orders - + COUNT 5
Two entries appended to a Stream, each with an auto generated ID, and XRANGE reading both back with their fields

Each XADD returns an ID like a millisecond timestamp with a sequence suffix, and the star tells Redis to generate it. XLEN counts the entries, and XRANGE reads them back with their fields, oldest first. Because the entries persist, a worker that restarts resumes from where it stopped instead of losing everything, which is the gap a plain list leaves open. Streams cost more memory and more care than a list, so reach for them when delivery guarantees are worth it.

Choosing between the three

The quick rule
  • Pub/sub: live fan-out to current listeners, and it is fine to miss messages while offline.
  • List queue: one worker pool draining jobs in order, simple and good enough for most background work.
  • Streams: many workers, replay, and acknowledgements, when a lost job is a real problem.

Most systems use more than one. A list queue handles the bulk of background jobs, pub/sub carries live signals, and Streams take the handful of flows where losing a message would cost money or correctness.

Going further: sharing a stream with a consumer group

A plain XRANGE reads a stream but does not track who has processed what, so two workers reading the same stream would both do every job. Consumer groups solve that. A group hands each entry to exactly one member and remembers which entries are still unacknowledged, so several workers share the load and a crashed worker's jobs can be reclaimed.

redis-cli XGROUP CREATE stream:orders billing 0
redis-cli XREADGROUP GROUP billing worker-1 COUNT 1 STREAMS stream:orders ">"
redis-cli XACK stream:orders billing 1784666755348-0

XREADGROUP delivers the next unread entry to worker-1 and moves it into that worker's pending list. The job is not done until XACK confirms it, and until then XPENDING will show it as owed, so a worker that dies mid-job leaves a claimable entry rather than a lost one. This is the machinery that turns a Stream into a real work queue with at-least-once delivery, and it is the reason to pay the extra memory a Stream costs over a plain list.

Frequently asked questions

Does redis pub/sub guarantee a message is delivered?

No. Pub/sub delivers only to subscribers connected at the moment of publishing and keeps no history, so a subscriber that is offline or reconnecting misses the message entirely. If you need every consumer to eventually see every message, use a list queue or a Stream, which hold the data until it is taken.

Why use BRPOP instead of RPOP in a loop?

RPOP returns immediately whether or not a job is present, so a worker using it must poll in a tight loop, which wastes CPU and adds latency. BRPOP blocks until a job arrives or a timeout passes, so the worker sleeps cheaply and wakes the instant work appears, which is both faster to react and lighter on the server.

When should I move from a list queue to Streams?

Move to Streams when losing a job is unacceptable or when several workers must share a queue with tracked progress. Streams keep entries after they are read, support consumer groups with acknowledgements, and let a restarted worker re-claim jobs that were never confirmed. For simple best-effort background work, a list is lighter and enough.

Can I run Redis pub/sub and a queue on the same server?

Yes, and it is common. They are independent features on the same instance, using different commands and key spaces, so one Redis can carry live pub/sub signals, background list queues, and Streams at once. Watch total memory and consider a separate instance only when one workload is large enough to affect the others.

What you have, and what comes next

You now have three ways to move data between processes and a rule for choosing: pub/sub for live fan-out, a list for a plain work queue, and Streams when a job must survive. You watched a message cross between two clients, drained a queue in order, and appended to a durable log, all on one server.

Every chapter so far has assumed the only clients are trusted ones on the same box. The final chapter removes that assumption. We will require a password, keep Redis on loopback, rename the commands that can wipe or reconfigure it, prove TLS encrypts the connection, and put a firewall in front of the port.


Add Redis on your own slice

Run Redis on a slice

Caching, sessions and queues want a fast in-memory store next to your app. Launch a slice, add Redis from this guide, and make your app quicker and cheaper to run.

Get early access

This guide is provided for educational purposes and offered as is, without warranty of any kind. The commands change the configuration of a server you control, and some can lock you out if run out of order. Test on a non-production machine, keep a second session open, and take a snapshot or backup before you begin. You are responsible for changes made to your own systems; ServerCake accepts no liability for any loss, downtime, lockout, or damage arising from following this guide or running the accompanying script.

Was this guide helpful?
Share

Spotted something out of date or have a question?

Built by the ServerCake team

Cloud that speaks India.

ServerCake is India's KYC-verified cloud: VMs and managed databases priced in ₹, billed with GST, on India-resident infrastructure. Reserve your spot for early access.

Reserve your spot