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 dendurableStateStoredurch 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.
Konfiguration
Abschnitt betitelt „Konfiguration“interface DurableDistributedDataSettings { durableStateStore: DurableStateStore; keys: string[]; // welche Keys durable sind encryption?: EncryptionConfig; compression?: CompressionConfig;}| Feld | Zweck |
|---|---|
durableStateStore | Eine beliebige DurableStateStore-Implementierung — in-memory, SQLite, Object Storage. |
keys | Die Whitelist der Key-Namen, die persistiert werden sollen. Andere Keys bleiben nur in-memory. |
encryption | Optionale AES-GCM-Verschlüsselung at rest. |
compression | Optionale 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.
Store-Auswahl
Abschnitt betitelt „Store-Auswahl“| Store | Verwendung |
|---|---|
InMemoryDurableStateStore | Tests (pro Prozess durable, aber beim Prozess-Exit weg — komisch, aber für Unit Tests nützlich). |
SqliteDurableStateStore | Single-Node-Deployment oder Per-Node-Durable-State in einem Cluster. |
ObjectStorageDurableStateStore | Dateisystem- 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.
Per-Node vs. shared Store
Abschnitt betitelt „Per-Node vs. shared Store“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 aufObjectStorageDurableStateStore zeigt auf gemeinsamen S3-Bucket: gemeinsamer Bucket ← persistiert den zuletzt gemergten Wert des Clusters Cold Start → jeder Node lädt denselben Zustand — sofortige KonvergenzPer-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.
Was persistiert wird
Abschnitt betitelt „Was persistiert wird“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.
Verschlüsselung
Abschnitt betitelt „Verschlüsselung“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.
Kompression
Abschnitt betitelt „Kompression“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.
Startup-Ablauf
Abschnitt betitelt „Startup-Ablauf“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 ZustandDas 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.
Wann du Durable Storage NICHT einsetzt
Abschnitt betitelt „Wann du Durable Storage NICHT einsetzt“Wohin als Nächstes
Abschnitt betitelt „Wohin als Nächstes“- Distributed Data im Überblick — das Gesamtbild.
- Replikation — wie State zwischen In-Memory-Replikas propagiert.
- Object Storage — der Dateisystem- / S3-Store, den du für durable DD nutzen kannst.
- PersistentActor — die Event-sourced Alternative für State mit häufigen Updates.