Aller au contenu
Français

DynamoDB

Ce contenu n’est pas encore disponible dans votre langue.

The DynamoDB backend provides all three persistence components against Amazon DynamoDB, via @aws-sdk/client-dynamodb:

  • DynamoDbJournal — the event journal for PersistentActors.
  • DynamoDbSnapshotStore — snapshots to bound recovery.
  • DynamoDbDurableStateStore — key-value durable state for DurableStateActors.

It has the strongest optimistic concurrency of any backend the framework ships — see Concurrency model — and it is fully serverless: no instance to run, on-demand capacity by default.

@aws-sdk/client-dynamodb is an optional peer dependency:

Terminal window
bun add @aws-sdk/client-dynamodb

It is the same SDK family already used for the S3 object-storage backend, so if you use that, the runtime story is one you already have. The framework lazy-imports it only when a DynamoDB store is first used.

Register the journal + snapshot store against the PersistenceExtension and receive a ready-to-use durable-state store. Set the connection once on the composite and all three components inherit it:

import {
ActorSystem,
ActorSystemOptions,
DynamoDbDurableStateStoreOptions,
DynamoDbJournalOptions,
DynamoDbSnapshotStoreOptions,
PersistenceExtensionId,
RegisterDynamoDbPluginsOptions,
registerDynamoDbPlugins,
} from 'actor-ts';
const systemOptions = ActorSystemOptions.create()
// Select the DynamoDB plugins as the active journal + snapshot store.
.withConfig({
'actor-ts': {
persistence: {
journal: { plugin: 'actor-ts.persistence.journal.dynamodb' },
'snapshot-store': { plugin: 'actor-ts.persistence.snapshot-store.dynamodb' },
},
},
});
const system = ActorSystem.create('my-app', systemOptions);
const ext = system.extension(PersistenceExtensionId);
const dynamoDbSnapshotStoreOptions = DynamoDbSnapshotStoreOptions.create()
.withKeepN(3);
const registerOptions = RegisterDynamoDbPluginsOptions.create()
.withRegion('eu-central-1')
.withJournal(DynamoDbJournalOptions.create() /* .withEventsTable(...) */)
.withSnapshotStore(dynamoDbSnapshotStoreOptions)
.withDurableStateStore(DynamoDbDurableStateStoreOptions.create() /* .withTable(...) */);
const { durableStateStore } = registerDynamoDbPlugins(ext, registerOptions);

Credentials come from the standard AWS provider chain (environment, shared config, IAM role), so nothing about them is actor-ts-specific. Pass withClientConfig({ credentials }) to override.

dynamodb-local needs an endpoint override, and the SDK will not sign a request without credentials even though the emulator ignores them:

const dynamoDbJournalOptions = DynamoDbJournalOptions.create()
.withRegion('eu-central-1')
.withEndpoint('http://localhost:8000')
.withClientConfig({ credentials: { accessKeyId: 'local', secretAccessKey: 'local' } });
  • You are on AWS and want no database to operate. No instance, no connection pool, no patching.
  • You want the strongest append guarantee available. The transactional append is atomic, which not even the relational backends manage for a multi-event batch under contention.
  • Your write pattern is spiky. On-demand billing needs no capacity planning.
type DynamoDbConnection = {
region?: string; // eu-central-1, …
endpoint?: string; // http(s) URL — dynamodb-local, LocalStack
clientConfig?: Record<string, unknown>; // { credentials, maxAttempts, … }
operations?: DynamoDbOperations; // pre-built / shared façade
};
type DynamoDbTableProvisioning = {
autoCreateTables?: boolean; // default true
billingMode?: 'PAY_PER_REQUEST' | 'PROVISIONED'; // default on-demand
provisionedThroughput?: { readCapacityUnits: number; writeCapacityUnits: number };
tableReadyTimeoutMs?: number; // default 30_000
};
interface DynamoDbJournalOptions extends DynamoDbConnection, DynamoDbTableProvisioning {
eventsTable?: string; // default 'actor_ts_events'
}
interface DynamoDbSnapshotStoreOptions extends DynamoDbConnection, DynamoDbTableProvisioning {
snapshotsTable?: string; // default 'actor_ts_snapshots'
keepN?: number; // keep newest N per pid; default 3, <=0 keeps all
}
interface DynamoDbDurableStateStoreOptions extends DynamoDbConnection, DynamoDbTableProvisioning {
table?: string; // default 'actor_ts_durable_state'
}

Options are validated when the store is constructed. Two rules earn their keep: a table name is checked against DynamoDB’s own charset and length, and capacity units are rejected unless billingMode is PROVISIONED — AWS silently ignores them otherwise, which makes the mistake invisible.

TablePartition keySort keyHolds
actor_ts_eventspidseq (N)one item per event; seq = 0 is the compaction mark
actor_ts_snapshotspidseq (N)one item per snapshot
actor_ts_durable_statepidone item per persistence id

With autoCreateTables (the default) each store creates its table on first use and waits for it to become ACTIVECreateTable returns while the table is still CREATING, and every operation against it fails until it flips. The wait happens inside the lazy init, so the first caller blocks and everyone behind it gets a table that works.

  • Journal append is one atomic transaction. Every event goes into a single TransactWriteItems, each Put carrying ConditionExpression: attribute_not_exists(pid) — “only if this (pid, seq) item does not exist”. A racing writer therefore cannot win a partial append: the transaction is all-or-nothing, and its cancellation becomes JournalConcurrencyError. The head read that precedes it is only an optimization, turning the common stale append into one cheap query instead of a rejected transaction.
  • Durable-state CAS is a native conditional writePutItem … attribute_not_exists(pid) to create, UpdateItem … revision = :expected to update — needing neither a transaction nor a read-back.
  • The compaction mark only rises. It is updated with attribute_not_exists(deletedTo) OR deletedTo < :value, which is GREATEST expressed as a condition; a lower value is rejected, and that rejection is the expected outcome rather than an error.
  • MongoDB — the other non-SQL backend, with an indexed tag query.
  • PostgreSQL — if you would rather run a relational database.
  • Durable state — the contract DynamoDB fits best.
  • Snapshots — bound the recovery scan.