Plexus streams
A Plexus stream is a channel type beside the work queue. Where a queue
delivers each message once (consumed means gone), a stream delivers every
record to every consumer. Pick the type when you declare the channel:
declare_queue(...) for a work queue, declare_plexus(...) for a stream.
Use a stream for fan-out: event broadcast, change feeds, notifications, an audit tail, or anything where many independent readers each need the full sequence.
The dashboard shows retained records and each durable cursor’s distance from the tail. This read-only example has one cursor at the tail and another catching up.
Cursors: where a consumer reads from
Section titled “Cursors: where a consumer reads from”A stream subscription tracks position with a cursor. There are two modes:
- Durable (recommended). Give the subscription a name. The broker remembers the cursor for that name, advances it as you ack, and resumes it after a restart or reconnect, anywhere in the cluster. No offset bookkeeping on your side. A fresh name starts at the earliest retained record so it cannot silently miss data. Different names are independent fan-out consumers, each tracking its own position. The same name is single-active (last commit wins).
- Ephemeral. No name. You choose a start position and the broker keeps no state for you, so you are at the mercy of retention. Good for a live tail or an ad-hoc replay.
Start positions for an ephemeral subscription: latest (only new records), earliest (oldest retained), N records back from the tail, or the first record at or after a wall-clock time.
Note that a record’s offset is an internal storage detail, not a stable identity to build application logic on. Resume with a durable cursor name rather than by remembering offsets. The client deliberately does not let you start a subscription at a raw offset.
Filtering
Section titled “Filtering”A subscription can carry a header filter: an AND of header == pattern
clauses, where the value pattern may contain * wildcards (eu-*). It is
deliberately tiny — no regex, no OR, no nesting. A record is delivered only when
it carries every named header and each matches. Filtering is stream-only.
Stream filters describes the planned
richer filters and the shared pattern rules.
Partitioning: fan-in, not work-sharing
Section titled “Partitioning: fan-in, not work-sharing”Partitioning a stream buys write throughput, storage spread, and per-key ordering (records with the same partition key stay ordered). It does not divide work among consumers. A stream subscription reads all partitions and fans them in client-side, so post-filter you see every matching record. The same durable name keeps an independent cursor per partition.
This is the opposite of a queue’s exclusive consumer cohort, which splits partitions across members. If you want within-group work distribution, use the work queue — that is its job.
Retention
Section titled “Retention”A stream keeps records until a retention bound is crossed, then drops whole sealed log segments. Bound it by age, total bytes, or record count (any combination). Retention wins over a slow cursor: a consumer that lags past the retained window is clamped forward and flagged rather than holding storage forever.
Slow consumers
Section titled “Slow consumers”Delivery is contiguous per subscriber on every tier. A consumer that cannot keep up with the live feed falls behind instead of losing records: the broker detaches it from live fan-out, replays the missed suffix in order from the in-memory ring (or the log when older), and rejoins it to the live tail once caught up. Records are never skipped mid-stream, so an auto-ack cursor can only advance along what was actually delivered. The one way a lagging consumer loses records is retention overtaking its position, as above. The admin streams page shows per-partition live subscription counts and how often a subscriber went through lag recovery.
An idle stream channel (no live subscriptions, no recent publish) can be
evicted from memory like an idle queue, controlled by the stream runtime
settings (disabled by default). Durable data is untouched and the channel
reopens transparently on next use.
Durability tiers
Section titled “Durability tiers”Each stream picks a durability tier at declare time. They trade latency for durability:
- durable (default) — persist and fsync (and replicate when configured) before delivering and confirming the producer. Survives power loss. Highest latency.
- speculative — deliver the instant the offset is assigned, persist (fsync)
in the background, and defer the producer confirm until the record is durable.
Readers see it sooner, the producer still gets a real durability confirmation.
Speculative deliveries carry a
fibril.speculativeheader so a consumer knows the record may not be durable yet. - ephemeral: deliver at staging and confirm the producer as soon as the record has its offset, without waiting for the write. The record is written without a per-record fsync, and the broker syncs the partition about every 5 ms while it has new records. Lowest latency, weakest guarantee. A confirmed record can still be in the broker’s own write buffers, so a process crash can drop the newest confirmed records, and a power loss or OS crash can drop records written since the last sync. See failure modes.
All three are log-backed, so durable cursors, replay, and retention work on any tier.
Speculative saves only the fsync wait between publish and delivery. That wait grows with write load, from a few milliseconds on a quiet disk to tens of milliseconds under heavy writes (see benchmarks). The producer confirm still waits for the fsync, so producer latency and throughput match the durable tier. The gain matters when consumer work is short compared to that wait, or when a consumer can start work early and hold back anything it cannot undo until the record is durable. When each record takes much longer to process than an fsync, the difference mostly disappears. Benchmarks with instant consumers show the largest gap, so treat them as the upper bound.
On the speculative and ephemeral tiers, live subscribers get a record when it is staged, before the log can serve it. Log reads return only durable records. A subscriber that starts behind the live feed, or catches up after falling behind, reads the log first and joins the live feed only once it has every record up to it. If the log is still behind the live feed, the subscriber waits for those records to become durable. Catch-up on these tiers can pause briefly, but it never skips a record. A start by wall-clock time at the live tail includes records that are staged but not written yet. A stream’s reported tail also counts staged records, so on these tiers it can run ahead of what the log can serve.
Durability vs high availability
Section titled “Durability vs high availability”A durability tier and replication are different guarantees, and it is worth being precise about which you have:
- Durable, not replicated (a single owner) survives a process restart or power loss — the records are on the owner’s disk — but it does not survive losing that node. While the owner is down the partition is unavailable, and if its disk is lost the records are gone.
- Durable and replicated (the durable tier with a replication factor of one or
more) survives node loss: the owner replicates its record and cursor logs to that
many followers, and a durable publish confirms once the cluster assignment
policy is met. With the default
majority_durablethat is both copies for a replication factor of one and any two of three for a factor of two. When the owner is lost, the controller seals enough surviving copies to be sure one holds every confirmed record, promotes the furthest of them and keeps serving without an operator. See stream failover. A caught-up follower picks up new durable records and cursor commits as soon as the owner has them, so these confirms do not wait out a poll interval. This is the high-availability configuration.
Replication is the durable tier only. The express tiers (speculative, ephemeral)
are always owner-only — replication would contradict their latency-first intent.
Set the cluster default replica count with the controller’s
stream_replication_factor (see Configuration); the
default of one keeps a durable stream available across a single node loss. A
single stream can override it at declare time with the replication-factor builder
(replication_factor in Rust and Python, replicationFactor in TypeScript), so
a critical stream can ask for more replicas while the rest use the default. Do
not treat a clustered durable stream as HA unless it has a replication factor of
at least one.
Acks advance the cursor
Section titled “Acks advance the cursor”For a durable subscription, settling a record (manual ack, or server-side auto-ack) advances the cursor past it — at-least-once delivery with no offset math. Cursor commits are microbatched per partition off the delivery path: a small window (default 100µs) coalesces a burst of acks — across every reader on the partition — into one write, keeping the high-water mark per consumer. This keeps high-fan-out auto-ack from committing once per record without changing the guarantee: the cursor only moves forward, so a crash before the next flush simply re-delivers a bounded window.
A cursor commit is written to the operating system at once and synced to disk by the cursor log’s background sync, which runs about every 100 ms, instead of waiting for its own fsync. A process crash loses nothing. A power cut on the owner can lose the cursor advances of roughly the last sync interval, and readers resuming from those cursors receive those records again. Records are never lost this way, only re-delivered, which at-least-once delivery allows. The owner replicates a cursor commit only once it is synced, so followers never hold a commit the owner could lose. Followers write replicated commits the same way and pull again any they lose in a power cut, so a durable publish confirm never waits on a cursor sync.
Publishing to a stream uses the ordinary publish path; the broker routes by channel kind, so the same producer works for queues and streams.
See Client usage for the stream API in Rust, TypeScript, and Python.