# How Buzz Uses Redis for Pub/Sub: Architecture and Implementation Guide

> Discover how Buzz leverages Redis Pub/Sub for real-time messaging. Explore its architecture, implementation, and features like presence tracking and rate limiting.

- Repository: [Block Open Source/buzz](https://github.com/block/buzz)
- Tags: architecture
- Published: 2026-08-28

---

**Buzz implements real-time messaging through a dedicated `buzz-pubsub` crate that wraps Redis Pub/Sub, providing publishers, subscribers, presence tracking, rate limiting, and cache invalidation through a shared connection pool managed by `PubSubManager`.**

The **block/buzz** repository relies on Redis as the backbone for its real-time event distribution. Rather than scattering Redis client code throughout the codebase, the project centralizes all pub/sub logic in the `buzz-pubsub` crate. This design allows the relay service (`buzz-relay`) and other components to broadcast Nostr events, track user presence, and enforce rate limits without managing raw Redis connections directly.

## Core Pub/Sub Architecture

### PubSubManager

The entry point for all Redis operations is `PubSubManager`, defined in [`crates/buzz-pubsub/src/lib.rs`](https://github.com/block/buzz/blob/main/crates/buzz-pubsub/src/lib.rs). This struct maintains a shared Redis connection pool and acts as a factory for publishers and subscribers. It reads configuration from the environment variable `REDIS_URL` (e.g., `redis://:password@redis:6379/0`) through the `PubSubConfig` struct, which supports optional authentication and database selection.

```rust
use buzz_pubsub::PubSubManager;

// Initialize from environment (reads REDIS_URL)
let manager = PubSubManager::new_from_env()?;

// Create publisher and subscriber instances on demand
let publisher = manager.publisher(topic_key);
let subscriber = manager.subscriber(topic_key);

```

### Connection Lifecycle

The manager lazily initializes connections from the pool, ensuring that services only hold Redis connections when actively publishing or subscribing. This approach keeps the relay lightweight during idle periods while maintaining low-latency access when event throughput spikes.

## Publishing and Subscribing to Events

### The Publisher Component

Located in [`crates/buzz-pubsub/src/publisher.rs`](https://github.com/block/buzz/blob/main/crates/buzz-pubsub/src/publisher.rs), the `Publisher` struct serializes Nostr events into JSON payloads and broadcasts them to Redis channels. The channel name is deterministic, generated by `EventTopicKey::redis_channel()` in [`crates/buzz-pubsub/src/topic.rs`](https://github.com/block/buzz/blob/main/crates/buzz-pubsub/src/topic.rs) based on the event's topic (such as a channel UUID).

```rust
use buzz_pubsub::{PubSubManager, Publisher};
use buzz_core::event::Event;

let manager = PubSubManager::new_from_env()?;
let topic_key = EventTopicKey::for_channel(channel_uuid);
let publisher: Publisher = manager.publisher(topic_key);

// Serialize and publish
let nostr_event = Event::new(...);
publisher.publish(&nostr_event)?;

```

### The Subscriber Component

The `Subscriber` struct in [`crates/buzz-pubsub/src/subscriber.rs`](https://github.com/block/buzz/blob/main/crates/buzz-pubsub/src/subscriber.rs) connects to the same Redis channel, deserializes incoming JSON into Nostr events, and forwards them to the relay's event-handling pipeline. It implements a streaming interface compatible with asynchronous Rust patterns.

```rust
use buzz_pubsub::{PubSubManager, Subscriber};

let manager = PubSubManager::new_from_env()?;
let topic_key = EventTopicKey::for_channel(channel_uuid);
let mut subscriber: Subscriber = manager.subscriber(topic_key);

tokio::spawn(async move {
    while let Some(msg) = subscriber.next().await {
        match msg {
            Ok(event) => handle_event(event),
            Err(e) => eprintln!("PubSub error: {}", e),
        }
    }
});

```

### Topic Key Generation

`EventTopicKey` in [`crates/buzz-pubsub/src/topic.rs`](https://github.com/block/buzz/blob/main/crates/buzz-pubsub/src/topic.rs) ensures that channels follow a consistent naming convention, preventing collisions between different event types while enabling efficient pattern-based subscriptions where needed.

## Advanced Redis Patterns in Buzz

### Presence Tracking

The `Presence` service in [`crates/buzz-pubsub/src/presence.rs`](https://github.com/block/buzz/blob/main/crates/buzz-pubsub/src/presence.rs) uses a separate Redis key-space to track online users per community. It publishes lightweight heartbeat events and maintains an in-memory cache that the UI queries for real-time user lists.

```rust
use buzz_pubsub::Presence;

let presence = Presence::new_from_env()?;
presence.heartbeat(user_id, community_id)?;

```

### Sliding-Window Rate Limiting

`RedisRateLimiter` in [`crates/buzz-pubsub/src/rate_limiter.rs`](https://github.com/block/buzz/blob/main/crates/buzz-pubsub/src/rate_limiter.rs) implements distributed rate limiting using Redis sorted sets. It stores timestamps in a sliding window keyed by user and action, atomically checking whether new requests exceed configured limits before allowing execution.

```rust
use buzz_pubsub::RedisRateLimiter;
use std::time::Duration;

let limiter = RedisRateLimiter::new_from_env()?;
let key = format!("user:{}:send_message", user_id);

if limiter.allow(&key, 5, Duration::from_secs(60))? {
    // Proceed - user under limit
} else {
    // Reject - rate limit exceeded
}

```

### Cache Invalidation

`ScopedCacheInvalidation` in [`crates/buzz-pubsub/src/cache_invalidation.rs`](https://github.com/block/buzz/blob/main/crates/buzz-pubsub/src/cache_invalidation.rs) publishes invalidation messages across the cluster. Downstream services listen to these messages to clear stale data from local caches, ensuring consistency in distributed deployments.

## Configuration and Deployment

Buzz expects a single Redis instance configured via the `REDIS_URL` environment variable. For local development, [`docker-compose.yml`](https://github.com/block/buzz/blob/main/docker-compose.yml) declares a `redis:7-alpine` service, while production deployments use [`deploy/compose/compose.yml`](https://github.com/block/buzz/blob/main/deploy/compose/compose.yml) with password protection and persistence enabled. The `buzz-pubsub` crate ships with a mock implementation for unit testing, allowing services to test pub/sub logic without running a live Redis server.

## Summary

- **Centralized Abstraction**: All Redis pub/sub logic lives in the `buzz-pubsub` crate, exposing `PubSubManager`, `Publisher`, and `Subscriber` types.

- **Event-Driven Architecture**: Nostr events serialize to JSON and publish to deterministic channels via [`crates/buzz-pubsub/src/publisher.rs`](https://github.com/block/buzz/blob/main/crates/buzz-pubsub/src/publisher.rs), with deserialization handled by [`crates/buzz-pubsub/src/subscriber.rs`](https://github.com/block/buzz/blob/main/crates/buzz-pubsub/src/subscriber.rs).

- **Topic Management**: `EventTopicKey::redis_channel()` in [`crates/buzz-pubsub/src/topic.rs`](https://github.com/block/buzz/blob/main/crates/buzz-pubsub/src/topic.rs) generates consistent channel names for routing.

- **Auxiliary Services**: Redis supports presence tracking ([`presence.rs`](https://github.com/block/buzz/blob/main/presence.rs)), sliding-window rate limiting ([`rate_limiter.rs`](https://github.com/block/buzz/blob/main/rate_limiter.rs)), and distributed cache invalidation ([`cache_invalidation.rs`](https://github.com/block/buzz/blob/main/cache_invalidation.rs)).

- **Configuration**: The system reads `REDIS_URL` for connection details, with Docker Compose files providing development and production Redis deployments.

## Frequently Asked Questions

### How does Buzz handle Redis connection pooling?

`PubSubManager` in [`crates/buzz-pubsub/src/lib.rs`](https://github.com/block/buzz/blob/main/crates/buzz-pubsub/src/lib.rs) maintains a shared connection pool that it lazily initializes from the `REDIS_URL` environment variable. Publishers and subscribers borrow connections from this pool on demand, ensuring efficient resource usage across the relay and supporting services.

### What Redis data structures does Buzz use beyond pub/sub channels?

Beyond standard pub/sub, Buzz uses Redis sorted sets for rate limiting (`RedisRateLimiter` in [`crates/buzz-pubsub/src/rate_limiter.rs`](https://github.com/block/buzz/blob/main/crates/buzz-pubsub/src/rate_limiter.rs)) and simple key-value storage for presence tracking (`Presence` in [`crates/buzz-pubsub/src/presence.rs`](https://github.com/block/buzz/blob/main/crates/buzz-pubsub/src/presence.rs)). Cache invalidation uses the pub/sub mechanism to broadcast clear commands across the cluster.

### Can Buzz work with Redis Sentinel or Cluster mode?

The current implementation in `buzz-pubsub` targets a single Redis instance as configured in [`docker-compose.yml`](https://github.com/block/buzz/blob/main/docker-compose.yml) and [`deploy/compose/compose.yml`](https://github.com/block/buzz/blob/main/deploy/compose/compose.yml). While the abstraction layer could theoretically support Sentinel or Cluster configurations through connection string updates, the source code shows connections established via standard `REDIS_URL` pointing to a single primary instance.

### How does Buzz ensure message ordering in Redis pub/sub?

Redis guarantees that messages arrive to subscribers in the order published to a specific channel. Buzz leverages this by publishing serialized Nostr events atomically through `Publisher::publish()` in [`crates/buzz-pubsub/src/publisher.rs`](https://github.com/block/buzz/blob/main/crates/buzz-pubsub/src/publisher.rs). The `Subscriber` processes messages sequentially as they stream from Redis, maintaining event order for clients connected to the same relay instance.