Zum Inhalt springen
Deutsch

Push-basierte Query

Die Live-PersistenceQuery-Streams — eventsByPersistenceId und eventsByTag — sind standardmäßig poll-basiert: sie lesen das Journal alle pollIntervalMs (Default 1 s) auf neue Events erneut. Für die meisten Projektionen okay, aber bis zu eine Sekunde Latenz pro Event.

Wenn das zugrunde liegende Journal einen In-Process-Event-Bus (journal.events) bereitstellt, wechseln dieselben Live-Streams automatisch zu Push-Delivery — sie abonnieren den Bus und liefern jedes neue Event innerhalb von Millisekunden, wobei das Polling nur als Sicherheitsnetz bleibt. Es gibt kein Opt-in-Flag; es passiert, sobald das Journal einen Bus hat.

events*-Streamjournal.eventsJournalPersistentActorevents*-Streamjournal.eventsJournalPersistentActorappend eventfor-await des Consumers yieldet espersist eventpublish(event)Event-Objekt liefern

Das In-Memory- und das SQLite-Journal verdrahten journal.events automatisch, ihre Live-Streams pushen also. Ein Journal ohne Bus pollt einfach. Ein eigenes Journal macht mit, indem es einen events-Bus bereitstellt und bei jedem erfolgreichen Append publiziert.

Push gilt für die Live-Streams (eventsByTag / eventsByPersistenceId), die ein AsyncIterable zurückgeben:

import { offsetStart } from 'actor-ts';
// `query` ist eine PersistenceQuery (SqliteQuery / InMemoryQuery / …) — siehe Persistence Query.
for await (const tagged of query.eventsByTag<AccountEvent>('account', offsetStart())) {
handle(tagged.event); // via Bus in ~ms geliefert, wenn verfügbar,
// sonst beim nächsten Poll
}

LiveQueryOptions justiert das Fallback-Polling — { pollIntervalMs }. Es gibt kein push-Feld: Push ist automatisch, wenn der Bus existiert, du kannst also ein höheres pollIntervalMs setzen (das Polling ist nur ein Backstop).

Der Bus liefert das Event, nicht nur einen Weckruf

Abschnitt betitelt „Der Bus liefert das Event, nicht nur einen Weckruf“

Der Push-Stream abonniert den Bus zuerst, macht dann den Catch-up-Read für die Historie und drainiert danach gepufferte Bus-Events — es gibt also keine Startup-Lücke. Jede Publikation trägt das Event-Objekt selbst, der Stream yieldet es also direkt; er fragt das Journal nicht bei jeder Benachrichtigung neu ab.

At-least-once bleibt erhalten: verpasst ein Abonnement eine Publikation (Startup-Race, Crash), holt das Fallback-Polling sie nach.

ProjectionActor treibt sich selbst durch Polling der einmaligen currentEventsByTag / currentEventsByPersistenceId auf einem Timer (pollIntervalMs); er abonniert den Journal-Bus nicht. Die Latenz einer Projektion ist also durch ihr Poll-Intervall begrenzt. Für echte Push-Latenz konsumiere den Live-events*-Stream direkt (wie oben) statt über den ProjectionActor zu gehen.

journal.events ist ein In-Process-Bus — Push funktioniert nur, wenn Writer und Stream-Consumer ein ActorSystem teilen (selber Knoten). Über Knoten hinweg bist du durch Polling begrenzt, durch die Replikation / CDC des Journals, oder indem du selbst auf DistributedPubSub publizierst.