Snapshot store backend
此内容尚不支持你的语言。
ObjectStorageSnapshotStore is the snapshot-store
implementation that uses
object storage
as its backing layer.
import { ObjectStorageSnapshotStore, ObjectStorageSnapshotStoreOptions, S3ObjectStorageBackend, S3ObjectStorageOptions, PersistenceExtensionId,} from 'actor-ts/persistence';
const s3ObjectStorageOptions = S3ObjectStorageOptions.create() .withRegion(region) .withBucket(bucket);const objectStorageSnapshotStoreOptions = ObjectStorageSnapshotStoreOptions.create() .withBackend(new S3ObjectStorageBackend(s3ObjectStorageOptions)) .withCompression({ algorithm: 'gzip' }) .withEncryption(encryption);const snapshotStore = new ObjectStorageSnapshotStore(objectStorageSnapshotStoreOptions);
system.extension(PersistenceExtensionId).setJournal(someJournal);system.extension(PersistenceExtensionId).setSnapshotStore(snapshotStore);Snapshots written by every PersistentActor go to the S3
bucket; compressed + encrypted per the config.
When to use it
Section titled “When to use it”Three patterns:
- Cluster-wide shared snapshots — sharded entities that move between nodes need any node to be able to load any entity’s snapshot. Object storage works; SQLite per-node doesn’t.
- Encrypted snapshots — server-side and/or client-side encryption at rest required for compliance.
- Cheap snapshot storage — object storage is far cheaper per GB than SQL stores for read-rarely-rewrite-occasionally data.
For single-node deployments, SqliteSnapshotStore is faster + simpler.
Configuration
Section titled “Configuration”type ObjectStorageSnapshotStoreOptionsType = { backend: ObjectStorageBackend; prefix?: string; // default '' keepN?: number; // default 3 compression?: CompressionConfig | CompressionResolver; encryption?: EncryptionConfig | EncryptionResolver; integrity?: IntegrityConfig | IntegrityResolver; allowUntaggedBodies?: boolean; // default false requireContextBinding?: boolean; // default false maxDecompressedBytes?: number; // default 512 MiB};| Field | Purpose |
|---|---|
backend | Filesystem or S3 backend. |
prefix | Object-key prefix. Useful for sharing buckets. |
keepN | Snapshots retained per persistenceId; older ones are pruned on save. Default 3. |
compression | At-rest compression — see Compression. |
encryption | At-rest encryption — see Encryption. |
integrity | HMAC-SHA256 tag over each snapshot body — signs writes and requires a tag on reads. See Body integrity. |
allowUntaggedBodies | Accept untagged snapshots while integrity is set — the migration window for a bucket written before integrity. Default false. |
requireContextBinding | Refuse snapshots not bound to the key they were read from — closes replay onto another persistenceId or sequence number. Turn on once the bucket is rewritten. See Binding a body to its key. Default false. |
maxDecompressedBytes | Decompression-bomb guard — cap on a decoded snapshot’s size. Default 512 MiB. |
Key layout
Section titled “Key layout”<prefix>/<persistenceId>/seq-<seqNr>Examples:
snapshots/account-42/seq-100snapshots/account-42/seq-200snapshots/account-42/seq-300The framework lists keys under <prefix>/<persistenceId>/ to
find the latest snapshot. The listing is ascending by key and
carries no limit: the zero-padded sequence number makes the
last entry the newest one, so a limit: 1 would hand back the
oldest instead.
For very-large persistenceId spaces, listing per-pid is
typically fast in S3 (per-prefix throughput). Avoid putting
all entity types under the same prefix without per-pid
subdirectories — that directory segment is the only thing
bounding what a load has to enumerate.
Performance
Section titled “Performance”Snapshot writes go through object-storage’s PUT; reads through GET. Numbers for S3 same-region:
- Save — 20-50 ms per snapshot.
- Load latest — 1 LIST + 1 GET = 30-60 ms.
That LIST is what the per-persistenceId directory in the key
buys. Both shipped backends scope it to
<prefix>/<persistenceId>/ — S3 through Prefix / MaxKeys,
the filesystem backend by reading only that directory — so a
load costs one entity’s own snapshots and stays flat as the
number of entities grows. keepN pruning issues the same LIST
after every save, so a backend that did not scope it would put
that growth on the write path too.
For hot-path snapshot loading (frequent actor restarts), wrap with CachedSnapshotStore:
const objectStorageSnapshotStoreOptions = ObjectStorageSnapshotStoreOptions.create().withBackend(backend);const cachedSnapshotStoreOptions = CachedSnapshotStoreOptions.create().withCache(cache);const cached = new CachedSnapshotStore( new ObjectStorageSnapshotStore(objectStorageSnapshotStoreOptions), cachedSnapshotStoreOptions,);Reduces redundant S3 GETs to sub-microsecond cache hits.
Per-actor overrides
Section titled “Per-actor overrides”class Account extends PersistentActor<...> { protected compression() { return { algorithm: 'zstd' as const }; } protected encryption() { return { keyRing: accountKeyRing }; }}Per-actor configuration applies to snapshots the same way as durable state. See Per-actor policies.
Snapshot lifecycle
Section titled “Snapshot lifecycle”PersistentActor.persist(event) succeeds ↓ check snapshotPolicy() ↓ if true → take snapshotbackend.put('snapshots/<pid>/seq-N', serialized state)
PersistentActor.preStart ↓ list 'snapshots/<pid>/' — that directory only, ascending ↓ last key = latest seq ↓ backend.get(latest) → decode → onEvent from seq+1 onwardsThe framework handles snapshot saves + loads via this layout; you set the policy.
Cleanup of old snapshots
Section titled “Cleanup of old snapshots”const objectStorageSnapshotStoreOptions = ObjectStorageSnapshotStoreOptions.create() .withBackend(backend) .withKeepN(5);new ObjectStorageSnapshotStore(objectStorageSnapshotStoreOptions); // keep 5 most recent; delete olderPruning is on by default: keepN defaults to 3, so each save
retains only the most-recent snapshots per persistenceId and
deletes the older ones. Raise or lower the bound with
withKeepN; set keepN: 0 to disable pruning and keep every
snapshot.
The framework doesn’t auto-delete during reads — there’s a small window where old snapshots co-exist with new ones.
Mixing with the journal
Section titled “Mixing with the journal”{ const sqliteJournalOptions = SqliteJournalOptions.create().withPath('...'); const objectStorageSnapshotStoreOptions = ObjectStorageSnapshotStoreOptions.create().withBackend(backend); journal: new SqliteJournal(sqliteJournalOptions), snapshotStore: new ObjectStorageSnapshotStore(objectStorageSnapshotStoreOptions),}The journal and snapshot store are independent. Common patterns:
- SQLite journal + ObjectStorage snapshots — local event-rate, shared snapshots for sharded entities.
- Cassandra journal + ObjectStorage snapshots — both shared across the cluster.
- ObjectStorage everything — when S3 is your only storage. Slower per-op but cheap.
Where to next
Section titled “Where to next”- Object storage overview — the bigger picture.
- Snapshot stores overview — general snapshot policy.
- CachedSnapshotStore — read-through cache.
- Per-actor policies — per-actor compression / encryption.
