Skip to content

Subscriptions

A subscription is a named rule that determines how messages from a topic are delivered to consumers. Danube supports multiple producers and subscribers on the same topic, and subscription types can be combined to build queuing or fan-out messaging patterns.

Exclusive

One consumer per subscription. Guarantees strict message ordering.

  • Ordered delivery
  • No horizontal scaling
  • Use case: sequential workflows

Shared

Multiple consumers, round-robin delivery. Scales horizontally.

  • Load-balanced across consumers
  • No ordering guarantee
  • Use case: parallel processing

Failover

One active consumer with standby. Automatic failover preserves ordering.

  • Ordered delivery + HA
  • Automatic failover
  • Use case: critical services

Key-Shared

Multiple consumers, routed by key. Per-key ordering with horizontal scaling.

  • Per-key ordering guaranteed
  • Consistent hashing + glob filters
  • Use case: per-entity processing

Only one consumer can attach to the subscription. If a second consumer tries to subscribe with the same name, it receives an error. The single consumer receives all messages in order.

One consumer handles all messages from the topic.

One consumer handles messages from all partitions of the topic.


Multiple consumers attach to the same subscription. Messages are delivered round-robin across consumers, and each message goes to exactly one consumer.

Messages are distributed among all consumers in round-robin fashion.

Messages from all partitions are distributed round-robin across all consumers. Each message from any partition goes to exactly one consumer.


Multiple consumers attach, but only one is active at a time. If the active consumer disconnects, another consumer takes over automatically. This preserves ordering while providing high availability.

One active consumer processes all messages. Additional consumers are on standby.

One active consumer per partition. Other consumers remain on standby for each partition. Failover occurs independently per partition.


Multiple consumers attach to the same subscription, where each message is routed based on its routing key. All messages with the same key go to the same consumer, in order.

This combines the parallelism of Shared subscriptions with per-key ordering guarantees.

  • Key routing: messages are assigned to consumers using consistent hashing. Each consumer gets virtual nodes on a hash ring, and the routing key determines which consumer owns it
  • Per-key ordering: at most one message per routing key is in-flight at any time. Additional messages for the same key are queued until the in-flight message is acknowledged
  • Consumer elasticity: when consumers join or leave, only approximately 1/N of existing key assignments are remapped. The rest remain stable

Consumers can declare key filter patterns to receive only specific subsets of messages. Filters use glob syntax:

PatternMatches
"payment"Exact match only
"ship*""shipping", "shipment", etc.
"eu-west-?""eu-west-1", "eu-west-2", etc.
"*"Everything (same as no filter)

If a consumer has no filters, it accepts all keys. If a message’s routing key matches no consumer’s filters, the message is skipped by that subscription.

Producers tag messages with a routing key using send_with_key():

Sending with routing keys
// All "payment" messages go to the same consumer
producer.send_with_key(data, None, "payment").await?;
producer.send_with_key(data, None, "shipping").await?;

If a message is sent without a routing key (using send()), the producer name is used as the fallback key.

Key-Shared consumer
// Automatic key distribution — no filters
let mut consumer = client
.new_consumer()
.with_topic("/default/orders")
.with_consumer_name("worker_1")
.with_subscription("orders_sub")
.with_subscription_type(SubType::KeyShared)
.build()?;
// Explicit key filtering — only receive matching keys
let mut consumer = client
.new_consumer()
.with_topic("/default/orders")
.with_consumer_name("payments_worker")
.with_subscription("orders_sub")
.with_subscription_type(SubType::KeyShared)
.with_key_filter("payment")
.with_key_filter("invoice")
.build()?;
  • Order processing: route all events for the same order ID to one consumer for consistent state management
  • Per-user event streams: group user activity events by user ID across consumers
  • Multi-tenant workloads: route tenant data to dedicated consumer instances
  • IoT device routing: route device telemetry by device ID

Subscription types map directly to common messaging patterns.

One message is delivered to exactly one consumer in a group. Use a Shared subscription with the same name across all workers:

  • Topic: /default/orders
  • Subscription: orders-workers (type Shared)
  • Run N consumers with the same subscription name. Messages are load-balanced round-robin across them.

Every downstream service receives every message. Create a separate subscription per service:

  • Topic: /default/events
  • Subscriptions: billing (Exclusive), analytics (Exclusive), monitoring (Failover)
  • Each subscription receives the full event stream independently.

Messages with the same key must be processed by the same consumer for ordering or state consistency. Use a Key-Shared subscription:

  • Topic: /default/orders
  • Subscription: order-processors (type KeyShared)
  • Run N consumers with the same subscription name. Messages are distributed by routing key.
  • Optionally use key filters to assign specific key patterns to specific consumers.

All subscription patterns work with either dispatch mode:

  • Non-Reliable: lowest latency, best-effort delivery, no persistence
  • Reliable: at-least-once delivery with WAL + cloud persistence and replay

See Dispatch Strategies for details.