MariaDB
Ce contenu n’est pas encore disponible dans votre langue.
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.
