Zum Inhalt springen
Deutsch

Push-basierte Query

Die Default-PersistenceQuery ist Poll-basiert: Live-Streams prüfen das Journal alle pollIntervalMs (Default 1 s) auf neue Events. In Ordnung für die meisten Projektionen, führt aber bis zu 1 Sekunde Latenz pro Event ein.

Für niedrigere Latenz fügt das Framework Push-Delivery über einen In-Process-JournalEventBus hinzu:

ProjectionActorJournalEventBusJournalPersistentActorProjectionActorJournalEventBusJournalPersistentActorEvent speichernder nächste Poll würdees irgendwann auch fangenpersist Eventpublishsofort aufweckenEvent behandelnOffset speichern

Der Bus liefert in einstelligen Millisekunden statt im Poll-Intervall. At-least-once-Semantik ist erhalten — wenn der Subscriber eine Publikation verpasst (Startup-Race, Crash), holt der nächste Poll sie nach.

Sub-Poll-Intervall-Delivery ist nur in einem spezifischen Fall wertvoll:

  • Der PersistentActor und der ProjectionActor sind auf demselben Node.
  • Die Latenz von persist() bis zum Projection-Handler ist für den User sichtbar.

Über Nodes hinweg (Writer auf Node-A, Projektion auf Node-B) hilft der Bus nicht — er ist ein In-Process-Bus. Über Nodes hinweg bist du entweder durch die Poll-Kadenz begrenzt oder durch die journal-eigene Replication-/Pub/Sub-Schicht (Cassandras CDC, Postgres Logical Replication, etc.).

Für die meisten Apps ist das Default-Polling in Ordnung — 1 Sekunde Projection-Lag ist selten ein Problem. Greife zu Push-basiertem, wenn:

  • Eine Echtzeit-UI eine Projektion abonniert.
  • Ein Cluster-Singleton-Koordinator in Echtzeit auf seine eigenen Persistence-Events reagieren muss.
  • Die Projektion kompensierende Aktionen in einer Saga antreibt, wo sich Poll-Lag über Schritte hinweg aufsummiert.
const projection = ProjectionActor.byTag<E>({
name: 'realtime-balance',
tag: 'account',
query: new SqliteQuery({ path: '...' }),
liveOptions: {
push: true, // Bus-Subscription aktivieren
pollIntervalMs: 5_000, // Fallback-Poll-Kadenz — kann hoch sein
},
async handle(event) { /* ... */ },
});

Mit push: true:

  • Die Projektion abonniert zusätzlich den In-Process-Bus des Journals.
  • Bei Bus-Delivery wacht die Projektion sofort auf, fragt das Journal nach dem/den neuen Event(s) ab und verarbeitet sie.
  • Der Poll läuft trotzdem als Sicherheitsnetz mit pollIntervalMs — setze ihn auf 5-30 Sekunden, da Push den Hot-Path übernimmt.

Der Fallback-Poll fängt:

  • Events, die der Bus verpasst hat (selten — Subscription-Races beim Start).
  • Events von Cross-Process-Writern (werden nicht über den In-Process-Bus geliefert).
  • Events, bei denen dein Handler abgestürzt ist und die er beim Neustart erneut versuchen muss.
// Innerhalb des Journals, bei jedem erfolgreichen Append:
this.events.publish(persistentEvent);
// Innerhalb der Projektion, bei jeder Subscription:
this.events.subscribe((event) => {
if (matchesFilter(event)) wakeUpAndProcess();
});

Der Bus ist ein einfaches In-Process-Pub/Sub — kein Netzwerk, keine Serialisierung. Publish ist synchron; Subscriber feuern im selben Tick.

Für die SQLite- und In-Memory-Journals ist der Bus automatisch verdrahtet. Für benutzerdefinierte Journals sollte deine Journal-Implementierung bei jedem erfolgreichen Append this.events.publish(...) aufrufen, um Push-Delivery an In-Process-Konsumenten zu ermöglichen.

Push-Delivery ist strikt In-Process. Für Cross-Node-Push-Delivery:

  • Das DistributedPubSub des Frameworks bedient general-purpose Cluster-Pub/Sub. Du würdest manuell auf Events publizieren, die du cluster-weit streamen willst.
  • Das Cassandra-Journal hat die Option, CDC für Cross-Cluster-Delivery zu verwenden, aber das ist nicht standardmäßig verdrahtet.

Für “Echtzeit-Projektionen über Nodes” ist das einfachste Muster:

  • Der PersistentActor (auf welchem Node er auch immer gehostet wird) publiziert beim Persistieren auch via DistributedPubSub (benutzerdefinierter Code obenauf des Per-Persist-Callbacks).
  • Subscriber auf jedem Node erhalten die Benachrichtigung und triggern ihre Projektionen.