MariaDB
このコンテンツはまだ日本語訳がありません。
The MariaDB backend is the sibling of the
Postgres backend for teams on
MariaDB or MySQL. It provides the same three components —
MariaDbJournal, MariaDbSnapshotStore, and
MariaDbDurableStateStore — against a single database, via the
official mariadb
connector (which speaks both MariaDB and MySQL).
It’s a separate implementation from Postgres (not a shared dialect layer), so the SQL is idiomatic MariaDB, but the public contract and behaviour are identical.
Install
Section titled “Install”mariadb is an optional peer dependency:
bun add mariadbLazy-imported on first use, like every other adapter.
import { ActorSystem, ActorSystemOptions, PersistenceExtensionId, MariaDbJournalOptions, MariaDbSnapshotStoreOptions, MariaDbDurableStateStoreOptions, RegisterMariaDbPluginsOptions, registerMariaDbPlugins,} from 'actor-ts';
const actorSystemOptions = ActorSystemOptions.create().withConfig({ 'actor-ts': { persistence: { journal: { plugin: 'actor-ts.persistence.journal.mariadb' }, 'snapshot-store': { plugin: 'actor-ts.persistence.snapshot-store.mariadb' }, }, }, });const system = ActorSystem.create('my-app', actorSystemOptions);
const ext = system.extension(PersistenceExtensionId);const mariaDbJournalOptions = MariaDbJournalOptions.create().withPoolConfig(connection);const mariaDbSnapshotStoreOptions = MariaDbSnapshotStoreOptions.create() .withPoolConfig(connection) .withKeepN(3);const mariaDbDurableStateStoreOptions = MariaDbDurableStateStoreOptions.create().withPoolConfig(connection);const registerMariaDbPluginsOptions = RegisterMariaDbPluginsOptions.create() .withJournal(mariaDbJournalOptions) .withSnapshotStore(mariaDbSnapshotStoreOptions) .withDurableStateStore(mariaDbDurableStateStoreOptions);const { durableStateStore } = registerMariaDbPlugins(ext, registerMariaDbPluginsOptions);
// connection = { host, port: 3306, user, password, database } — or pass a// shared `pool` (from mariadb.createPool) at the top level via// `.withPool(pool)` to reuse one pool across all three, or a connection// URL via `.withUrl(...)`.As with Postgres, the journal + snapshot store are selected by the config
plugin IDs, and the durable-state store is returned for you to pass
into your DurableStateActor settings.
Configuration
Section titled “Configuration”type MariaDbConnection = { url?: string; // mariadb://user:pass@host:3306/db poolConfig?: Record<string, unknown>; // { host, port, user, password, database, … } pool?: MariaDbPoolLike; // pre-built / shared pool};interface MariaDbJournalOptions extends MariaDbConnection { eventsTable?: string; tagsTable?: string; autoCreateTables?: boolean;}interface MariaDbSnapshotStoreOptions extends MariaDbConnection { snapshotsTable?: string; keepN?: number; autoCreateTables?: boolean;}interface MariaDbDurableStateStoreOptions extends MariaDbConnection { table?: string; autoCreateTables?: boolean;}Discrete poolConfig (host/user/password/database) is the most portable
way to connect; url and a pre-built pool are also accepted.
Schema
Section titled “Schema”Same shape as Postgres, with
MariaDB types — VARCHAR(255) ids, BIGINT sequence/revision/timestamp,
LONGTEXT payloads — and indexes declared inline in CREATE TABLE
(portable across MariaDB/MySQL versions, unlike CREATE INDEX IF NOT EXISTS):
CREATE TABLE events ( persistence_id VARCHAR(255) NOT NULL, sequence_nr BIGINT NOT NULL, payload LONGTEXT NOT NULL, tags TEXT, timestamp BIGINT NOT NULL, PRIMARY KEY (persistence_id, sequence_nr), INDEX idx_events_pid (persistence_id));-- events_tags, snapshots, durable_state: as Postgres, with the types aboveDialect differences vs. Postgres
Section titled “Dialect differences vs. Postgres”The behaviour is identical; the SQL differs:
| Operation | Postgres | MariaDB |
|---|---|---|
| Placeholders | $1, $2 | ? |
| Tag dedup insert | ON CONFLICT DO NOTHING | INSERT IGNORE |
| Snapshot upsert | ON CONFLICT … DO UPDATE | ON DUPLICATE KEY UPDATE |
keepN prune | NOT IN (SELECT … LIMIT) | derived-table-wrapped subquery¹ |
| Durable create conflict | ON CONFLICT DO NOTHING → rowCount | plain INSERT, catch ER_DUP_ENTRY (1062) |
| Concurrency backstop | SQLSTATE 23505 | ER_DUP_ENTRY / 1062 |
| Contention backstop | SQLSTATE 40001 / 40P01 | ER_CHECKREAD (1020), 1213, 1205² |
¹ MySQL/MariaDB reject LIMIT inside a bare IN (SELECT …) against the
table being deleted, so the prune wraps the subquery in a derived table.
² A losing writer does not always reach the primary key. Under real
contention InnoDB aborts it first — “Record has changed since last read”
(errno 1020) — so the duplicate-key backstop alone would have handed the
caller an opaque JournalError and no way to tell a retryable race from a
storage failure. Those aborts are classified too, but they are only
reported as JournalConcurrencyError when a re-read shows the head actually
moved; otherwise the error stands, because a lock-wait timeout against an
unrelated long transaction is a real failure and retrying it would spin.
The same applies to durable-state CAS.
BIGINT may surface from the connector as a JS bigint; the backend
coerces sequence/revision/timestamp to number at the mapping boundary.
Pitfalls
Section titled “Pitfalls”Where to next
Section titled “Where to next”- PostgreSQL — the sibling backend, with the fuller walkthrough of registration + concurrency.
- Cassandra journal — distributed, for scale-out.
- Durable state — the state-oriented alternative to event sourcing.
