Pular para o conteúdo
Português (BR)

MariaDB

Este conteúdo não está disponível em sua língua ainda.

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.

mariadb is an optional peer dependency:

Terminal window
bun add mariadb

Lazy-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.

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.

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 above

The behaviour is identical; the SQL differs:

OperationPostgresMariaDB
Placeholders$1, $2?
Tag dedup insertON CONFLICT DO NOTHINGINSERT IGNORE
Snapshot upsertON CONFLICT … DO UPDATEON DUPLICATE KEY UPDATE
keepN pruneNOT IN (SELECT … LIMIT)derived-table-wrapped subquery¹
Durable create conflictON CONFLICT DO NOTHING → rowCountplain INSERT, catch ER_DUP_ENTRY (1062)
Concurrency backstopSQLSTATE 23505ER_DUP_ENTRY / 1062
Contention backstopSQLSTATE 40001 / 40P01ER_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.

  • 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.