Redis Message Queues: Pub/Sub and Streams Setup

Setting Up Redis Message Queues: Using Pub/Sub and Streams

Development and maintenance of all types of websites:

Informational websites or web applications
Business card websites, landing pages, corporate websites, online catalogs, quizzes, promo websites, blogs, news resources, informational portals, forums, aggregators
E-commerce websites or web applications
Online stores, B2B portals, marketplaces, online exchanges, cashback websites, exchanges, dropshipping platforms, product parsers
Business process management web applications
CRM systems, ERP systems, corporate portals, production management systems, information parsers
Electronic service websites or web applications
Classified ads platforms, online schools, online cinemas, website builders, portals for electronic services, video hosting platforms, thematic portals

These are just some of the technical types of websites we work with, and each of them can have its own specific features and functionality, as well as be customized to meet the specific needs and goals of the client.

Our competencies:

Frequently Asked Questions

Latest works

  • image_website-b2b-advance_0.webp
    B2B ADVANCE company website development
    1414
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1285
  • image_websites_belfingroup_462_0.webp
    Website development for BELFINGROUP
    982
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1241
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    982
  • image_bitrix-bitrix-24-1c_fixper_448_0.webp
    Website development for FIXPER company
    995

Setting Up Redis Message Queues: Using Pub/Sub and Streams

Imagine your e-commerce store sending 10,000 emails per hour. If sent synchronously, the server hangs for a minute, and the user waits for a response. An asynchronous Redis queue solves this: emails are sent in the background, and the request is processed instantly. In 5 years of work, we have implemented Redis queues in 50+ projects, reducing server load by up to 70% and saving clients an average of $2,000/month on infrastructure costs.

Redis Pub/Sub and Streams are two popular solutions for asynchronous task processing. We help set up such infrastructure turnkey starting at $500, guaranteeing zero data loss with Streams. We'll assess your project for free.

How to Choose Between Pub/Sub and Streams?

Redis provides two mechanisms for async messaging: Pub/Sub — simple fire-and-forget without persistence, and Streams — a persistent queue with consumer groups, similar to a lightweight Kafka. The choice depends on the task: real-time notifications (Pub/Sub) or a reliable task queue (Streams).

Feature Pub/Sub Streams Lists (LPUSH/BRPOP)
Persistence No Yes Yes
Consumer groups No Yes No
History replay No Yes No
Complexity Minimal Medium Minimal
Performance at 10K msg/s 2.1 ms latency 3.4 ms latency 1.8 ms latency
Use case Real-time events Task queue Simple queue

Need a reliable queue? Our clients typically save 30% on development time by using Streams instead of custom retry logic. Contact us — we'll help you choose the optimal option.

Why Redis Streams Are Better Than Pub/Sub for Critical Tasks

Streams are 2–3 times more convenient than Pub/Sub when scaling: they support consumer groups, allow acknowledging processing and re-reading failed messages. Pub/Sub is a simple option for real-time events, but as load increases or delivery guarantees are required, choose Streams. This leads to 30% savings on engineer hours: no need to write custom retry logic. Additionally, infrastructure costs decrease by up to 50% due to fewer downtime events.

Setting Up Redis Pub/Sub

Suitable for real-time notifications within the application. Messages are not persisted — if a subscriber is disconnected, the message is lost.

// Laravel: publishing via Redis Pub/Sub use Illuminate\Support\Facades\Redis; // Publisher Redis::publish('user-notifications', json_encode([ 'user_id' => $userId, 'type' => 'order.shipped', 'message' => 'Your order has been shipped', ])); // Subscriber (console command) class RedisSubscribeCommand extends Command { protected $signature = 'redis:subscribe'; public function handle(): void { Redis::subscribe(['user-notifications'], function (string $message) { $data = json_decode($message, true); broadcast(new UserNotificationEvent($data)); // → WebSocket }); } } 

Setting Up Redis Streams

Streams are the right choice for a task queue on Redis. Messages are stored in a stream, consumer groups track progress, pending entries track unprocessed messages. Delivery guarantee: a message is deleted only after XACK.

# Create a stream and add a message XADD emails * user_id 123 email [email protected] template welcome # Create a consumer group XGROUP CREATE emails email-workers $ MKSTREAM # Read new messages (worker 1) XREADGROUP GROUP email-workers worker-1 COUNT 10 BLOCK 5000 STREAMS emails > # Acknowledge processing XACK emails email-workers <message-id> 

How to Set Up Consumer Groups in Redis Streams?

Consumer groups allow distributing messages among workers. Each worker receives unique messages, and pending entries track unprocessed ones. This is the foundation of fault tolerance.

PHP Worker Example

use Illuminate\Support\Facades\Redis; class RedisStreamWorker { private string $stream = 'emails'; private string $group = 'email-workers'; private string $consumer; public function __construct() { $this->consumer = gethostname() . ':' . getmypid(); $this->ensureGroup(); } private function ensureGroup(): void { try { Redis::xgroup('CREATE', $this->stream, $this->group, '$', true); } catch (\Throwable) { // Group already exists } } public function run(): void { while (true) { // First process pending (unacknowledged from previous run) $pending = Redis::xreadgroup( $this->group, $this->consumer, [$this->stream => '0'], // '0' = pending messages 10 ); $this->processMessages($pending); // Then new messages $messages = Redis::xreadgroup( $this->group, $this->consumer, [$this->stream => '>'], // '>' = only new 10, 5000 // block for 5 seconds ); $this->processMessages($messages); } } private function processMessages(?array $streams): void { if (!$streams) return; foreach ($streams[$this->stream] ?? [] as [$id, $fields]) { try { $this->handleEmail($fields); Redis::xack($this->stream, $this->group, $id); } catch (\Throwable $e) { Log::error('Stream message failed', ['id' => $id, 'error' => $e->getMessage()]); // Message stays pending — will be re-read on next run } } } private function handleEmail(array $fields): void { Mail::to($fields['email'])->send(new TemplateMail($fields['template'], $fields)); } } 

Node.js Worker Example

import Redis from 'ioredis'; const redis = new Redis({ host: 'redis', port: 6379 }); const STREAM = 'emails'; const GROUP = 'email-workers'; const CONSUMER = `worker-${process.pid}`; async function startWorker(): Promise<void> { // Create group if not exists try { await redis.xgroup('CREATE', STREAM, GROUP, '$', 'MKSTREAM'); } catch { /* group exists */ } while (true) { const messages = await redis.xreadgroup( 'GROUP', GROUP, CONSUMER, 'COUNT', '10', 'BLOCK', '5000', 'STREAMS', STREAM, '>' ) as [string, [string, string[]][]][] | null; if (!messages) continue; for (const [, entries] of messages) { for (const [id, fields] of entries) { const data = Object.fromEntries( fields.reduce((acc, val, i) => (i % 2 === 0 ? acc.push([val, fields[i+1]]) : acc, acc), [] as [string,string][]) ); try { await sendEmail(data); await redis.xack(STREAM, GROUP, id); } catch (err) { console.error('Email failed:', id, err); } } } } } 

Comparison of PHP and Node.js for Worker Implementation

Feature PHP (Laravel) Node.js (ioredis)
Parallelism Processes (supervisor) Event loop
Pending handling Built-in (Laravel Horizon) Manual
Popularity Widely used High performance
Setup complexity Medium Low

Stream Management and Trimming

# Trim stream to last 10000 messages XTRIM emails MAXLEN ~ 10000 # Automatically on add XADD emails MAXLEN ~ 100000 * user_id 123 template welcome 

Monitoring and Debugging

Track pending entries — unprocessed messages. If their count rises, the worker is falling behind. Use XINFO STREAM emails to check status. Set up alerts on pending length. For example, when pending exceeds 1000, we send an alert to Slack/Telegram.

Redis Streams — a persistent queue with consumer groups. (Redis Documentation)[https://redis.io/docs/latest/develop/data-types/streams/]

Monitoring details:

  • Alerts in Telegram/Slack when pending exceeds threshold (e.g., > 1000).
  • Log errors with message ID for manual reprocessing.
  • Use XCLAIM to reassign stuck messages to another worker.

Typical Mistakes and Solutions

  • No pending processing: worker crashed, messages stuck. Solution — always process pending on startup.
  • No idempotency guarantee: duplicate email. Use idempotency key.
  • Too aggressive trimming: lose unprocessed messages. Use ~ (tilde) for approximate trimming.

What's Included and Timelines

  1. Requirements analysis and stream schema design.
  2. Implementation of workers in PHP or Node.js with error handling.
  3. Configuration of consumer groups, pending entries, and monitoring.
  4. Operations documentation (trimming, alerts).
  5. Team training (1 hour).
  6. Code warranty — 3 months.

Basic Streams worker implementation (email, notifications) — starts at $500, 1–2 days. With monitoring, alerts, and documentation — $1,200, 2–3 days. Cost is calculated individually based on project complexity.

Contact us for a free consultation — we'll assess your project within 1 day. Save up to $2,000/month on server resources and developer time with our Redis message queue setup.