How Buzz Implements Its Subscription System: A Deep Dive into the Relay Registry

Buzz implements its subscription system as a thread-safe, lock-free registry in crates/buzz-relay/src/subscription.rs that uses specialized DashMap indexes to achieve sub-linear fan-out of Nostr events to matching WebSocket connections.

The subscription system is the core message routing engine of block/buzz, a Nostr relay implementation written in Rust. It maintains every client-side REQ subscription in memory and determines precisely which WebSocket connections should receive each incoming event without scanning the entire connection pool. This architecture enables the relay to handle high-throughput workloads while respecting NIP-01 subscription semantics and strict scoping invariants.

Core Data Structures and Identifiers

The registry centers on two primary identifiers defined at lines 11-14: ConnId, a UUID that uniquely identifies a WebSocket connection, and SubId, a client-provided string that identifies a specific subscription within that connection.

Every subscription operates within a defined SubscriptionScope, an enum at lines 18-25 that distinguishes between Global subscriptions (no channel restriction) and Channels subscriptions (limited to specific channel UUIDs). This scope determines which indexes the registry queries during event fan-out.

To enable O(1) look-ups, the system uses an IndexKey struct (lines 50-57) composed of a channel_id: Uuid and a kind: Kind. These keys populate multiple concurrent hash maps that index subscriptions by their filter constraints.

Registry Architecture and Indexing Strategy

The SubscriptionRegistry struct (lines 84-100) contains six DashMap instances that store the subscription tree and its various indexes:

  • subs: The authoritative store mapping (ConnId, SubId) to the subscription record
  • channel_kind_index: Maps (channel_id, kind) to matching subscriptions
  • channel_wildcard_index: Captures channel-scoped subscriptions with no kind constraint
  • global_kind_index and global_p_kind_index: Handle global subscriptions by kind or by p-tag combinations
  • global_wildcard_index: Catches global subscriptions with no specific constraints

This multi-map design avoids O(N) scans by pre-sorting subscriptions into granular buckets based on their filter parameters.

Registering Subscriptions with NIP-01 Semantics

New subscriptions enter the registry through register_scoped or register_channels_scoped (lines 108-140). These methods implement NIP-01 replacement semantics: if a SubId already exists for a given ConnId, the old subscription is atomically removed before the new one is inserted.

The index population logic (lines 158-200) inspects each filter's constraints to determine optimal placement:

  • Channel-scoped filters without a kinds constraint enter the channel_wildcard_index
  • Channel-scoped filters with specific kinds create entries in channel_kind_index for each kind
  • Global filters utilizing p-tag constraints use the specialized global_p_kind_index when possible
  • Remaining global filters fall back to global_kind_index or global_wildcard_index

This classification ensures that fan_out_scoped can narrow candidate matches immediately without examining unrelated subscriptions.

Event Fan-Out and Delivery

When an event arrives, the registry invokes fan_out_scoped (lines 780-886) to determine routing. The method first checks whether the event is channel-scoped or global, then walks only the relevant indexes to collect candidate (ConnId, SubId) pairs.

Candidates undergo final validation in push_match (lines 635-661), which re-checks the authoritative subscription record in the subs map. This guards against stale snapshots: because DashMap provides lock-free concurrent access, a subscription might be modified between index traversal and event delivery. The double-check ensures that filters_match (defined in crates/buzz-core/src/filter.rs) and scope constraints are satisfied before the event is enqueued.

The implementation guarantees strict scoping invariants at lines 887-892: global subscriptions never receive channel-scoped events, and channel subscriptions never receive global events, even if their filters would technically match.

Subscription Lifecycle Management

Removal operations clean up both the primary store and secondary indexes. The remove_subscription method (lines 237-285) deletes a single subscription and invokes remove_from_index (lines 662-755) to purge its entries from all relevant indexes. For connection teardown, remove_connection wipes all subscriptions associated with a ConnId and adjusts the active-subscription gauge.

Both methods maintain consistency by using the same atomic operations available to the registration path, ensuring that removal does not block ongoing fan-out operations.

Concurrency and Thread Safety

All registry state uses DashMap, a lock-free concurrent hash map that allows register, fan-out, and removal operations to run in parallel without mutex contention. The architecture treats index snapshots as merely "hints"—the authoritative check in push_match means stale candidate lists are harmless and never result in incorrect delivery.

The multithreaded test case at lines 538-605 verifies these guarantees under concurrent subscription replacement and high-volume event fan-out.

use buzz_relay::subscription::SubscriptionRegistry;
use nostr::{Filter, Kind};
use uuid::Uuid;

// Initialize the registry
let registry = SubscriptionRegistry::new();
let conn_id = Uuid::new_v4();
let channel_id = Uuid::new_v4();
let sub_id = "sub_001".to_string();

// Register a channel-scoped subscription for TextNote events
let filters = vec![Filter::new().kind(Kind::TextNote)];
registry.register_scoped(
    buzz_core::CommunityId::from_uuid(Uuid::nil()),
    conn_id,
    sub_id.clone(),
    filters,
    Some(channel_id),
);

// Simulate an incoming event
let event = buzz_core::StoredEvent::with_received_at(
    nostr::EventBuilder::new(Kind::TextNote, "hello world", [])
        .sign_with_keys(&nostr::Keys::generate())
        .unwrap(),
    chrono::Utc::now(),
    Some(channel_id),
    true,
);

// Retrieve matching connections
let matches = registry.fan_out_scoped(
    buzz_core::CommunityId::from_uuid(Uuid::nil()),
    &event
);
assert_eq!(matches, vec![(conn_id, sub_id.clone())]);

// Cleanup on disconnect
registry.remove_subscription(conn_id, &sub_id);

Summary

  • Core Location: The subscription system resides in crates/buzz-relay/src/subscription.rs as the SubscriptionRegistry struct.
  • Identifiers: Uses ConnId (UUID) for connections and SubId (String) for client subscriptions, scoped via the SubscriptionScope enum.
  • Indexing Strategy: Six separate DashMap indexes categorize subscriptions by (channel, kind), (community, kind, #p), or wildcard status to eliminate full-table scans.
  • Registration: register_scoped implements NIP-01 replacement semantics and populates indexes based on filter constraints at lines 108-200.
  • Fan-Out: fan_out_scoped queries scoped indexes and validates candidates via push_match to prevent stale data delivery (lines 635-886).
  • Concurrency: Lock-free DashMap structures enable parallel registration, removal, and fan-out without blocking.

Frequently Asked Questions

How does Buzz handle concurrent subscription updates?

Buzz uses DashMap lock-free concurrent hash maps for all registry state, allowing registration, fan-out, and removal to execute in parallel. When a subscription is updated, the register_scoped method atomically replaces the old entry (per NIP-01 semantics), and the push_match validation step during fan-out filters out any stale candidates that might have been collected from index snapshots.

What is the difference between channel-scoped and global subscriptions?

Channel-scoped subscriptions, defined by the SubscriptionScope::Channels variant, restrict event delivery to specific channel UUIDs and are indexed separately from global subscriptions. Global subscriptions (SubscriptionScope::Global) receive only non-channel events. The fan_out_scoped method enforces this boundary at lines 887-892, ensuring events never cross between these domains even if filter criteria overlap.

Why does the system use multiple indexes instead of a single map?

Multiple indexes provide sub-linear lookup performance by pre-sorting subscriptions into buckets based on their filter constraints. Instead of scanning all subscriptions for every event, fan_out_scoped queries only the relevant index—such as channel_kind_index for a specific event kind—reducing lookup time from O(N) to O(1) relative to the subscription count.

How are subscriptions cleaned up when a client disconnects?

The remove_connection method efficiently removes all subscriptions associated with a ConnId and purges their entries from every secondary index using remove_from_index. This bulk cleanup operation adjusts internal gauges and ensures no orphaned entries remain in the wildcard or kind-specific indexes.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →