Aller au contenu
Français

Push-based query

Ce contenu n’est pas encore disponible dans votre langue.

The live PersistenceQuery streams — eventsByPersistenceId and eventsByTag — are poll-based by default: they re-read the journal every pollIntervalMs (default 1 s) for new events. Fine for most projections, but up to a second of latency per event.

When the underlying journal exposes an in-process event bus (journal.events), those same live streams automatically switch to push delivery — they subscribe to the bus and yield each new event within milliseconds, keeping the poll only as a safety net. There is no opt-in flag; it happens whenever the journal has a bus.

events* streamjournal.eventsJournalPersistentActorevents* streamjournal.eventsJournalPersistentActorappend eventconsumer's for-await yields itpersist eventpublish(event)deliver event object

The in-memory and SQLite journals wire up journal.events automatically, so their live streams push. A journal without a bus simply polls. A custom Journal opts in by exposing an events bus and publishing on each successful append.

Push applies to the live streams (eventsByTag / eventsByPersistenceId), which return an AsyncIterable:

import { offsetStart } from 'actor-ts';
// `query` is a PersistenceQuery (SqliteQuery / InMemoryQuery / …) — see Persistence query.
for await (const tagged of query.eventsByTag<AccountEvent>('account', offsetStart())) {
handle(tagged.event); // delivered by the bus in ~ms when available,
// otherwise by the next poll
}

LiveQueryOptions tunes the fallback poll — { pollIntervalMs }. There is no push field: push is automatic when the bus exists, so you can set a higher pollIntervalMs (the poll is only a backstop).

The bus delivers the event, not just a wake-up

Section titled “The bus delivers the event, not just a wake-up”

The push stream subscribes to the bus first, does the catch-up read for history, then drains buffered bus events — so there’s no start-up gap. Each publication carries the event object itself, so the stream yields it directly; it does not re-query the journal on every notification.

At-least-once is preserved: if a subscription misses a publication (start-up race, crash), the fallback poll picks it up.

ProjectionActor polls — it does not use the bus

Section titled “ProjectionActor polls — it does not use the bus”

ProjectionActor drives itself by polling the one-shot currentEventsByTag / currentEventsByPersistenceId on a timer (pollIntervalMs); it does not subscribe to the journal bus. So a projection’s latency is bounded by its poll interval. For true push latency, consume the live events* stream directly (as above) rather than going through ProjectionActor.

journal.events is an in-process bus — push works only when the writer and the stream consumer share an ActorSystem (same node). Across nodes you’re bounded by polling, by the journal’s own replication / CDC, or by publishing on DistributedPubSub yourself.