Pub/Sub Patterns

While Redis Streams are great for durable logs, sometimes you just need speed. You want to broadcast a notification to 1,000 connected clients instantly, and you don’t care if they miss it while offline. This is where Pub/Sub shines.

Pub/Sub (Publish/Subscribe) is a messaging pattern where senders (publishers) do not program messages to be sent directly to specific receivers (subscribers). Instead, messages are published to abstract “channels”.

1. Fire-and-Forget

The most defining characteristic of Redis Pub/Sub is that it is ephemeral.

  • No Persistence: If a subscriber is offline, they will never receive the message.
  • Zero Storage: Redis does not store the message. It simply pushes it to all connected sockets and discards it.
  • Speed: Without disk I/O, Pub/Sub offers microsecond latency.

2. Commands

Basic Messaging

# Client 1: Subscribe to a channel
SUBSCRIBE news
# Client 2: Publish to that channel
PUBLISH news "Breaking News: Redis is fast!"
# Returns: (integer) 1 (number of clients that received it)

Pattern Matching (PSUBSCRIBE)

You can also subscribe to patterns using glob-style wildcards.

# Listen to all user updates
PSUBSCRIBE user:*:update

3. Interactive: Broadcasting

Visualize how a single message is instantly broadcast to all active subscribers.

Redis Server
Channel: "news"
Sub 1
Waiting...
Sub 2
Waiting...

4. Pub/Sub vs. Streams

When should you use which?

Feature Pub/Sub Streams
Persistence None (Fire-and-Forget) Disk-backed (AOF/RDB)
History No Yes (Infinite Log)
Delivery Guarantee At-most-once At-least-once (with Consumer Groups)
Fan-out Yes (All subscribers get it) Yes (Multiple Consumer Groups)
Use Case Real-time chat, Live notifications Event Sourcing, Job Queues, Audit Logs

5. Code Examples: Chat Room

package main

import (
    "context"
    "fmt"
    "github.com/redis/go-redis/v9"
)

func main() {
    ctx := context.Background()
    rdb := redis.NewClient(&redis.Options{Addr: "localhost:6379"})

    // Subscriber
    pubsub := rdb.Subscribe(ctx, "news")
    defer pubsub.Close()

    // Go channel to receive messages
    ch := pubsub.Channel()

    go func() {
        for msg := range ch {
            fmt.Println("Received:", msg.Payload)
        }
    }()

    // Publisher
    rdb.Publish(ctx, "news", "Hello World!")

    // Block to keep main alive for demo
    select {}
}

6. Summary

Pub/Sub is the tool of choice for live, ephemeral data. It complements Streams perfectly: use Streams for data you can’t afford to lose, and Pub/Sub for data that needs to be everywhere, instantly.