Zum Inhalt springen
Deutsch

Remember Entities

Sharded Entities sind standardmäßig lazy: ein Entity-Actor existiert erst, nachdem er seine erste Nachricht empfangen hat. Ohne rememberEntities bedeutet ein Cluster-Kaltstart oder Koordinator-Failover, dass die aktive Entity-Menge leer ist — Entities werden bei Bedarf neu gespawnt, sobald Nachrichten ankommen.

Für viele Workloads ist das in Ordnung. Manchmal aber willst du, dass Entities eager neu erstellt werden:

  • Sie haben geplante Hintergrundarbeit, die unabhängig von eingehenden Nachrichten laufen sollte.
  • Sie haben etwas Externes abonniert und müssen am Leben sein, um es zu verarbeiten.
  • Failover sollte das volle Arbeitsset sofort wiederherstellen, nicht durchsickern, sobald Nutzer auf jede Entity treffen.

rememberEntities: true ist der Schalter.

sharding.start({
typeName: 'session',
entityProps: Props.create(() => new SessionActor()),
extractEntityId: (msg) => msg.userId,
rememberEntities: true,
});

Was es tut:

  • Die Existenz jeder Entity (ihre ID) wird in einem Remember-Entities-Store persistiert.
  • Beim Cluster-Kaltstart oder nach einem Koordinator-Failover liest der Koordinator die Menge bekannter Entity-IDs und bittet jede Shard-Region, sie eager neu zu spawnen.

Der Default-Store nutzt das Journal des Systems, sodass du das umsonst bekommst, sobald Persistenz konfiguriert ist. Überschreibe mit rememberEntitiesStore, wenn du einen anderen Backing-Store brauchst.

Nur die Identitäten aktiver Entities — nicht ihr Zustand. Der Entity-Zustand ist die Verantwortung der Entity selbst (PersistentActor journalisiert Zustandsänderungen; Remember-Entities verfolgt, welche IDs zurückgebracht werden sollen).

Zwei-Speicher-Aufteilung:

Journal (deine Events) RememberEntitiesStore (nur IDs)
pid=cart-user-42 entityIds = ['cart-user-42', 'cart-user-43', ...]
Event 1
Event 2
Event 3

Das ist absichtlich — der Remember-Store hat andere Zugriffsmuster (kleine, häufige Writes, wenn Entities starten/stoppen) und profitiert davon, separierbar zu sein.

  • Der Shard, zu dem eine gegebene Entity gehört hat. Bei Respawn läuft die Allokation erneut (per Allokationsstrategie).
  • Der Zustand der Entity. Wenn du willst, dass Zustand überlebt, muss die Entity ihn selbst persistieren — über PersistentActor oder externen Storage.

Wenn du rememberEntities: true verwendest, aber kein Journal konfiguriert hast:

sharding.start({
typeName: '...',
entityProps: ...,
extractEntityId: ...,
rememberEntities: true,
rememberEntitiesStore: null, // explizites Opt-Out aus Persistenz
});

null zu übergeben fällt zurück auf das v1-In-Memory-Only-Verhalten — die Entity-Menge wird im Prozessspeicher verfolgt und bei Koordinator-Failover verloren. Nützlich für Dev / Tests; sinnlos in Produktion.

Ohne rememberEntitiesStore: null und ohne konfiguriertes Journal instanziiert das Framework automatisch einen JournalRememberEntitiesStore mit dem (standardmäßig In-Memory) Journal — was zum Testen in Ordnung ist, aber einen Neustart nicht überlebt. Stelle sicher, dass dein Journal in Produktion dauerhaft ist.

import type { RememberEntitiesStore } from 'actor-ts/cluster/sharding';
class RedisRememberEntitiesStore implements RememberEntitiesStore {
async addEntity(typeName: string, entityId: string): Promise<void> { /* SADD */ }
async removeEntity(typeName: string, entityId: string): Promise<void> { /* SREM */ }
async listEntities(typeName: string): Promise<ReadonlyArray<string>> { /* SMEMBERS */ }
}
sharding.start({
// ...
rememberEntities: true,
rememberEntitiesStore: new RedisRememberEntitiesStore(),
});

Die Schnittstelle ist klein. Nützlich, wenn du die Entity-Registry auf einem anderen Backing-Store als den Rest deiner Persistenz willst (z. B. Redis für latenzarme Entity-Set-Lookups, während das Journal auf Cassandra liegt).

rememberEntities: true fügt zwei Write-Operationen pro Entity-Lebenszyklus hinzu:

  1. Ein Write, wenn die Entity spawnt (addEntity).
  2. Ein Write, wenn die Entity passiviert / stoppt (removeEntity).

Für High-Churn-Entities (Millionen kurzlebiger Sessions pro Stunde) ist das echter Overhead. Erwäge:

  • rememberEntities für kurzlebige Workloads überspringen.
  • Einen schnellen Store (In-Memory-Redis) statt des Default-Journals nutzen.
  • Writes batchen, wenn du einen eigenen Store implementierst.

Für Low-Churn-Workloads (10K stabile Entities) sind die Kosten vernachlässigbar.

SzenariorememberEntities nutzen?
Per-User-Sessions, kurzlebig, beim nächsten Request neu spawnenNein
Lange laufende Koordinatoren, müssen leben, um geplante Arbeit zu tunJa
IoT-Device-Handler, müssen beim Start MQTT-Topics abonnierenJa
Per-Tenant-Cache, teuer zu rebauenJa
Per-Order-Saga, muss nach Failover dort weiter, wo sie aufgehört hatJa
Per-Request-temporärer-Actor, stirbt in SekundenNein

Die Regel: wenn “brauche ich diese Entity am Leben auch ohne kürzliche Nachricht?” ein Ja ist, aktivieren.

Passivierung stoppt idle Entities. Mit rememberEntities:

Entity aktiv → passiviere (idle) → Entity stoppt → Store: removeEntity?

Verhalten des Frameworks:

  • Idle-Timeout-Passivierung (passivationIdleMs) — entfernt die Entity aus dem Store. Nächste Nachricht spawnt sie neu; der Store empfängt erneut ein addEntity.
  • maxEntities-LRU-Passivierung — dasselbe.
  • Passivate von der Entity selbst — dasselbe.

Für Workloads, in denen du eager Re-Spawn auch von kürzlich inaktiven Entities willst, setze passivationIdleMs: 0, damit sie bleiben.