跳转到内容
简体中文

SQLite snapshot store

此内容尚不支持你的语言。

SqliteSnapshotStore persists snapshots to a SQLite file — durable, zero-dependency on Bun, peer-dep on Node. Pair with SqliteJournal for the standard single-node production setup.

import {
SqliteJournal,
SqliteJournalOptions,
SqliteSnapshotStore,
SqliteSnapshotStoreOptions,
ActorSystem,
ActorSystemOptions,
} from 'actor-ts';
const sqliteJournalOptions = SqliteJournalOptions.create()
.withPath('/var/lib/my-app/events.db')
.withWal(true);
const sqliteSnapshotStoreOptions = SqliteSnapshotStoreOptions.create().withPath('/var/lib/my-app/snapshots.db');
const actorSystemOptions = ActorSystemOptions.create().withPersistence({
journal: new SqliteJournal(sqliteJournalOptions),
snapshotStore: new SqliteSnapshotStore(sqliteSnapshotStoreOptions),
});
const system = ActorSystem.create('my-app', actorSystemOptions);
type SqliteSnapshotStoreOptions = {
path?: string; // file path, or ':memory:'
snapshotsTable?: string; // default 'snapshots'
keepN?: number; // max snapshots kept per persistenceId, pruned on save (default 3)
busyTimeoutMs?: number; // lock-wait budget, default 1000; 0 = fail fast
driver?: SqliteDriver;
};

path, busyTimeoutMs and driver behave exactly as they do on the SQLite journal — including the reason busyTimeoutMs has a framework-set default at all: the drivers ship different ones (0 ms on bun:sqlite and node:sqlite, 5000 ms on better-sqlite3), and the wait is synchronous, so it blocks the event loop. There is no wal option here; instead the snapshot store adds keepN — the maximum number of snapshots retained per persistenceId, with older ones pruned on each save (default 3).

const sqliteSnapshotStoreOptions = SqliteSnapshotStoreOptions.create()
.withPath('/var/lib/my-app/snapshots.db')
.withBusyTimeoutMs(1000);
new SqliteSnapshotStore(sqliteSnapshotStoreOptions)
CREATE TABLE snapshots (
persistence_id TEXT NOT NULL,
sequence_nr INTEGER NOT NULL,
payload TEXT NOT NULL,
timestamp INTEGER NOT NULL,
PRIMARY KEY (persistence_id, sequence_nr)
);

Same pattern as the journal: keyed by (persistence_id, sequence_nr). The framework reads the highest sequence_nr for a given persistence_id on loadLatest.

// Production:
{
const sqliteJournalOptions = SqliteJournalOptions.create().withPath('/var/lib/events.db');
const sqliteSnapshotStoreOptions = SqliteSnapshotStoreOptions.create().withPath('/var/lib/snapshots.db');
journal: new SqliteJournal(sqliteJournalOptions),
snapshotStore: new SqliteSnapshotStore(sqliteSnapshotStoreOptions),
}

The journal and snapshot store are independent files. You can also put them in the same file with different table names — but two files keep operations simpler (back up independently, vacuum independently).

For multi-node deployments where the journal is Cassandra, you can still use SQLite for snapshots if each entity always recovers on the same node. But for sharded entities (which move between nodes), the snapshot store also needs to be shared — see Object storage snapshot store.

  • Snapshot write — single INSERT, ~100 μs. Dominated by serialization, not SQLite I/O.
  • Snapshot read — single SELECT, sub-millisecond.

For very-large snapshots (multi-MB state), serialization is the bottleneck. Consider compression (Object storage compression) or a different snapshot strategy.

The SqliteSnapshotStore API reference covers the full options.