Zum Inhalt springen
Deutsch

Durable Storage

Standardmäßig leben DistributedData-Replikas nur im Speicher. Ein Full-Cluster-Restart (jeder Node gleichzeitig down) verliert den gesamten State — jeder Key startet wieder bei empty.

Für State, der einen Cold Start überleben muss, umwickle den Replicator mit DurableDistributedDataStore:

import {
DistributedDataId,
DurableDistributedDataStore,
SqliteDurableStateStore,
} from 'actor-ts';
const stateStore = new SqliteDurableStateStore({ path: '/var/lib/dd.db' });
const dd = system.extension(DistributedDataId).start(cluster, {
durable: new DurableDistributedDataStore({
durableStateStore: stateStore,
keys: ['hits', 'config', 'sessions'],
}),
});

Was du damit bekommst:

  • Bei update — die lokale Replika schreibt direkt in den durableStateStore durch und speichert den gemergten Wert durable.
  • Beim Start — bevor sie Gossip beitritt, lädt die lokale Replika ihren persistierten Zustand aus dem Store.

Nach einem Cold Start liefert dd.get(key) den zuletzt persistierten Wert — nicht undefined.

interface DurableDistributedDataSettings {
durableStateStore: DurableStateStore;
keys: string[]; // welche Keys durable sind
encryption?: EncryptionConfig;
compression?: CompressionConfig;
}
FeldZweck
durableStateStoreEine beliebige DurableStateStore-Implementierung — in-memory, SQLite, Object Storage.
keysDie Whitelist der Key-Namen, die persistiert werden sollen. Andere Keys bleiben nur in-memory.
encryptionOptionale AES-GCM-Verschlüsselung at rest.
compressionOptionale gzip- / zstd-Kompression.

Die keys-Whitelist ist Pflicht — ohne sie würde jeder Key persistiert, was das “kleiner, heißer State”-Modell aushebelt, für das DistributedData entworfen ist.

StoreVerwendung
InMemoryDurableStateStoreTests (pro Prozess durable, aber beim Prozess-Exit weg — komisch, aber für Unit Tests nützlich).
SqliteDurableStateStoreSingle-Node-Deployment oder Per-Node-Durable-State in einem Cluster.
ObjectStorageDurableStateStoreDateisystem- oder S3-backed — Multi-Node-shared, wenn du alle Nodes auf denselben Pfad / Bucket zeigen lässt.

Für die meisten Produktions-Setups ist SqliteDurableStateStore pro Node die richtige Wahl — jeder Node persistiert seine eigene Replika, die beim Restart wiederhergestellt wird. Gossip übernimmt die Konvergenz nach dem Restart.

SqliteDurableStateStore pro Node:
Store von node-A ← persistiert die Replika von node-A
Store von node-B ← persistiert die Replika von node-B
Cold Start → jeder Node lädt seinen eigenen Zustand, Gossip holt auf
ObjectStorageDurableStateStore zeigt auf gemeinsamen S3-Bucket:
gemeinsamer Bucket ← persistiert den zuletzt gemergten Wert des Clusters
Cold Start → jeder Node lädt denselben Zustand — sofortige Konvergenz

Per-Node:

  • Pro: Jeder Node hat vollständige lokale Recovery; partitions-tolerant.
  • Contra: State auf einem zerstörten Node ist verloren (kein anderes Backup).

Shared:

  • Pro: Cluster-weit eine einzige Wahrheit; verlierst du einen Node, haben die anderen weiterhin alles.
  • Contra: zusätzlicher Roundtrip zum Laden des Zustands beim Start; Single Point of Failure, wenn der Store down geht.

Für die meisten Apps ist Per-Node die bessere Wahl — der lokale Zustand eines Nodes zu verlieren ist selten ein Problem, weil Gossip + die überlebenden Nodes ihn wiederherstellen.

Nur die Keys in der Whitelist. Alles andere ist nur in-memory.

Pro persistiertem Key:

  • Der vollständige CRDT-State (keine Deltas) wird bei jedem Update neu geschrieben.
  • Der State wird über das toJSON() des CRDT serialisiert.
  • Optionale Verschlüsselung + Kompression werden auf das serialisierte Payload angewendet.

Für kleine CRDTs (Counter, Flags, kleine Sets) ist das billig. Für große (ORMaps mit 10k Einträgen voller verschachtelter CRDTs) schreibt jedes Update das ganze Blob neu — Bandbreite und Write-Amplification zählen. Gleicher Trade-off wie bei Durable-State-Actors.

new DurableDistributedDataStore({
durableStateStore,
keys: [...],
encryption: {
algorithm: 'aes-gcm',
keyId: 'k1',
keyRing: myKeyRing,
},
});

State wird at rest mit AES-GCM verschlüsselt. Siehe Object Storage Encryption für die Details des Key-Managements — dieselben Muster gelten.

new DurableDistributedDataStore({
durableStateStore,
keys: [...],
compression: { algorithm: 'gzip' },
});

Für große CRDTs, die sich gut komprimieren lassen (textlastige Sets, strukturierte Maps), reduziert gzip den Disk-Verbrauch deutlich. Bei kleinen Countern lohnt sich der Overhead nicht.

const dd = system.extension(DistributedDataId).start(cluster, {
durable: new DurableDistributedDataStore({ ... }),
});
// Bevor dieser Aufruf zurückkehrt:
// 1. Den Durable Store öffnen.
// 2. Für jeden Whitelist-Key das persistierte CRDT laden + decodieren.
// 3. Auf die lokale Replika anwenden.
// 4. Dann der Gossip-Schicht beitreten.
const value = dd.get('hits'); // spiegelt bereits den persistierten Zustand

Das heißt, der Start ist durch die Lesegeschwindigkeit des Durable Store begrenzt. Für SQLite-pro-Node mit einer Handvoll Keys sub-millisekündlich. Für S3-backed Shared Storage einstellige Sekunden.