Zum Inhalt springen
Deutsch

Snapshot-Store-Backend

ObjectStorageSnapshotStore ist die Snapshot-Store- Implementierung, die Object Storage als Backing-Schicht verwendet.

import {
ObjectStorageSnapshotStore,
S3ObjectStorageBackend,
PersistenceExtensionId,
} from 'actor-ts';
const snapshotStore = new ObjectStorageSnapshotStore({
backend: new S3ObjectStorageBackend({ region, bucket }),
compression: { algorithm: 'gzip' },
encryption: { keyRing },
});
system.extension(PersistenceExtensionId).configure({
journal: someJournal,
snapshotStore,
});

Snapshots, die von jedem PersistentActor geschrieben werden, gehen in den S3-Bucket; komprimiert + verschlüsselt gemäß der Config.

Drei Muster:

  1. Cluster-weit geteilte Snapshots — Sharded Entities, die zwischen Nodes wandern, brauchen, dass jeder Node den Snapshot jeder Entity laden kann. Object Storage funktioniert; SQLite pro Node nicht.
  2. Verschlüsselte Snapshots — Server-Side- und/oder Client-Side-Verschlüsselung at rest für Compliance erforderlich.
  3. Günstiger Snapshot-Storage — Object Storage ist pro GB viel günstiger als SQL-Stores für selten-gelesene-gelegentlich-überschriebene Daten.

Für Single-Node-Deployments ist SqliteSnapshotStore schneller + einfacher.

interface ObjectStorageSnapshotStoreSettings {
backend: ObjectStorageBackend;
prefix?: string; // Default 'snapshots/'
compression?: CompressionConfig;
encryption?: EncryptionConfig;
}
FeldZweck
backendFilesystem- oder S3-Backend.
prefixObject-Key-Prefix. Nützlich, um Buckets zu teilen.
compressionAt-Rest-Kompression — siehe Kompression.
encryptionAt-Rest-Verschlüsselung — siehe Verschlüsselung.
<prefix>/<persistenceId>/seq-<seqNr>

Beispiele:

snapshots/account-42/seq-100
snapshots/account-42/seq-200
snapshots/account-42/seq-300

Das Framework listet Keys unter <prefix>/<persistenceId>/, um den neuesten Snapshot zu finden.

Für sehr große persistenceId-Räume ist das Listen pro pid in S3 typischerweise schnell (Per-Prefix-Durchsatz). Vermeide es, alle Entity-Typen unter denselben Prefix ohne Per-pid-Unterordner zu packen.

Snapshot-Writes gehen durch das PUT von Object Storage; Reads durch GET. Zahlen für S3 Same-Region:

  • Save — 20-50 ms pro Snapshot.
  • Load latest — 1 LIST + 1 GET = 30-60 ms.

Für Hot-Path-Snapshot-Laden (häufige Actor-Neustarts) wickle es mit CachedSnapshotStore ein:

const cached = new CachedSnapshotStore({
underlying: new ObjectStorageSnapshotStore({ backend, ... }),
maxEntries: 1_000,
});

Reduziert redundante S3-GETs auf Sub-Mikrosekunden-Cache-Hits.

class Account extends PersistentActor<...> {
protected compression() { return { algorithm: 'zstd' as const }; }
protected encryption() { return { keyRing: accountKeyRing }; }
}

Per-Actor-Konfiguration gilt für Snapshots auf dieselbe Weise wie für Durable State. Siehe Per-Actor-Policies.

PersistentActor.persist(event) gelingt
↓ snapshotPolicy() prüfen
↓ wenn true → Snapshot machen
backend.put('snapshots/<pid>/seq-N', serialisierter State)
PersistentActor.preStart
↓ 'snapshots/<pid>/' auflisten, um die neueste seq zu finden
↓ backend.get(latest) → dekodieren → onEvent ab seq+1

Das Framework handhabt Snapshot-Saves + -Loads über dieses Layout; du setzt die Policy.

new ObjectStorageSnapshotStore({
backend,
maxSnapshotsPerPid: 5, // die 5 neuesten behalten; ältere löschen
});

Ohne Cleanup akkumulieren alte Snapshots unbegrenzt. maxSnapshotsPerPid zu konfigurieren triggert einen Cleanup-Pass bei jedem Save — nur die N neuesten werden behalten.

Das Framework löscht beim Lesen nicht automatisch — es gibt ein kleines Fenster, in dem alte Snapshots mit neuen koexistieren.

{
journal: new SqliteJournal({ path: '...' }),
snapshotStore: new ObjectStorageSnapshotStore({ backend }),
}

Das Journal und der Snapshot-Store sind unabhängig. Häufige Muster:

  • SQLite-Journal + ObjectStorage-Snapshots — lokale Event-Rate, geteilte Snapshots für Sharded Entities.
  • Cassandra-Journal + ObjectStorage-Snapshots — beide über den Cluster geteilt.
  • ObjectStorage alles — wenn S3 dein einziger Storage ist. Langsamer pro Op, aber billig.