Skip to content

Persistent stream pulling architecture

A persistent stream provider connects Orleans streams to a durable queue technology. Producers enqueue through an adapter. Silo-local pulling agents own queue partitions, read batches, cache them, discover subscriptions, and deliver events through ordinary Orleans calls.

This page describes the runtime mechanism. For stream APIs and provider selection, see the streaming documentation.

Rendering diagram.

PersistentStreamProvider is the common implementation. A provider-specific IQueueAdapterFactory creates:

During lifecycle initialization the provider resolves its named adapter factory and creates the adapter. At the active stage it initializes the pulling manager and starts agents. Shutdown stops agents before the provider closes.

By default, pulling agents start automatically. Explicit grain-based and implicit subscriptions are both enabled.

API: PersistentStreamProvider. Implementation: provider lifecycle and provider options.

The queue mapper deterministically assigns a stream identity to a queue. All producers and consumers for a provider must use compatible mapping or events can be written to queues which no intended agent reads.

The queue balancer assigns queues to silos and publishes sequenced ownership changes. PersistentStreamPullingManager is a silo-local system target which serializes those notifications, ignores stale sequences, and starts or stops one pulling agent per owned queue. When membership changes, queues move among managers; agents themselves are not virtual and do not migrate.

Source: PersistentStreamPullingManager.

Each PersistentStreamPullingAgent is a system target with single-threaded Orleans scheduling. Its loop:

  1. asks the adapter receiver for a batch;
  2. adds batch containers to its queue cache;
  3. groups cached items by stream;
  4. resolves and caches pub-sub registrations;
  5. advances each subscription’s cursor independently;
  6. sends events through Orleans messaging;
  7. records delivery progress and failure; and
  8. purges only data which the cache says is safe to remove.

The default maximum adapter batch-container batch size is 1 and the empty-poll period is 100 ms. These defaults are runtime behavior, not a universal throughput recommendation.

An IQueueCache decouples queue reads from consumer delivery. Each subscription has an IQueueCacheCursor, so a slow consumer does not directly block a fast consumer at a later cursor.

The cache tracks the earliest delivery progress across active subscriptions. Purging must not remove an item still needed by any cursor. SimpleQueueCache uses pressure buckets to stop or slow reads as lag grows instead of discarding undelivered events. Its default capacity is 4,096 batch containers.

Rendering diagram.

Cache capacity is not durability. The queue remains the durable boundary, subject to the adapter’s acknowledgement contract.

The agent registers as a producer for each stream and obtains subscription records from stream pub-sub. It holds a pin cursor while subscription handshakes complete so cache cleanup cannot pass the requested start token. New subscription notifications update the agent’s local pub-sub cache.

Sequence tokens allow a rewindable adapter to start from a supported historical position. An adapter whose IQueueAdapter.IsRewindable property is false must reject unsupported tokens rather than pretending to honor them.

The agent normally awaits delivery before advancing a subscription cursor, creating per-subscription backpressure. When delivery fails, it invokes the configured IStreamFailureHandler. Depending on provider policy, an explicit subscription can be faulted and removed.

Persistent streams are not universally exactly once. Semantics depend on:

  • when the external queue considers a message acknowledged;
  • whether the adapter can redeliver after receiver or silo failure;
  • cache checkpoint behavior;
  • consumer idempotency; and
  • provider-specific sequence tokens.

A queue message can be delivered again after ownership change or failure. Consumers which perform durable side effects should be idempotent.

Provider authors should keep these responsibilities separate:

See provider authoring for hosting and validation patterns and Azure Queue streams for a concrete adapter.