Zum Inhalt springen
Deutsch

MariaDB

Das MariaDB-Backend ist das Geschwister des Postgres-Backends für Teams auf MariaDB oder MySQL. Es liefert dieselben drei Komponenten — MariaDbJournal, MariaDbSnapshotStore und MariaDbDurableStateStore — gegen eine einzige Datenbank, über den offiziellen mariadb-Connector (der sowohl MariaDB als auch MySQL spricht).

Es ist eine eigenständige Implementierung (kein geteilter Dialekt-Layer), also ist das SQL idiomatisches MariaDB — der öffentliche Vertrag und das Verhalten sind aber identisch.

mariadb ist eine optionale Peer-Dependency:

Terminal-Fenster
bun add mariadb

Beim ersten Zugriff lazy-importiert, wie jeder andere 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 } — oder gib oben
// einen geteilten `pool` (aus mariadb.createPool) via `.withPool(pool)`
// mit, um einen Pool über alle drei zu nutzen, oder eine Connection-URL
// via `.withUrl(...)`.

Wie bei Postgres werden Journal + Snapshot-Store über die Plugin-IDs ausgewählt, und der Durable-State-Store wird zurückgegeben, damit du ihn in die Settings deines DurableStateActor reichst.

type MariaDbConnection = {
url?: string; // mariadb://user:pass@host:3306/db
poolConfig?: Record<string, unknown>; // { host, port, user, password, database, … }
pool?: MariaDbPoolLike; // vorgebauter / geteilter 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;
}

Diskretes poolConfig (host/user/password/database) ist der portabelste Verbindungsweg; url und ein vorgebauter pool werden ebenfalls akzeptiert.

Gleiche Form wie bei Postgres, mit MariaDB-Typen — VARCHAR(255)-Ids, BIGINT für Sequence/Revision/Timestamp, LONGTEXT-Payloads — und Indizes inline im CREATE TABLE deklariert (portabel über MariaDB-/MySQL-Versionen, anders als 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: wie bei Postgres, mit den Typen oben

Das Verhalten ist identisch; das SQL unterscheidet sich:

OperationPostgresMariaDB
Platzhalter$1, $2?
Tag-Dedup-InsertON CONFLICT DO NOTHINGINSERT IGNORE
Snapshot-UpsertON CONFLICT … DO UPDATEON DUPLICATE KEY UPDATE
keepN-PruneNOT IN (SELECT … LIMIT)derived-table-gewrappte Subquery¹
Durable-Create-KonfliktON CONFLICT DO NOTHING → rowCountreines INSERT, ER_DUP_ENTRY (1062) fangen
Concurrency-BackstopSQLSTATE 23505ER_DUP_ENTRY / 1062
Contention-BackstopSQLSTATE 40001 / 40P01ER_CHECKREAD (1020), 1213, 1205²

¹ MySQL/MariaDB lehnen LIMIT in einer nackten IN (SELECT …) gegen die zu löschende Tabelle ab, daher wrappt der Prune die Subquery in eine Derived Table.

² Ein unterlegener Writer erreicht den Primary Key nicht immer. Unter echter Contention bricht InnoDB ihn vorher ab — „Record has changed since last read” (errno 1020) — der Duplicate-Key-Backstop allein hätte dem Caller also einen undurchsichtigen JournalError gereicht, ohne Unterscheidungsmöglichkeit zwischen wiederholbarem Race und echtem Speicherfehler. Diese Abbrüche werden ebenfalls klassifiziert, aber nur dann als JournalConcurrencyError gemeldet, wenn ein erneutes Lesen zeigt, dass der Head sich tatsächlich bewegt hat; sonst bleibt der Fehler stehen, denn ein Lock-Wait-Timeout gegen eine unbeteiligte lange Transaktion ist ein echter Fehler, und ein Retry würde sich nur drehen. Für Durable-State- CAS gilt dasselbe.

BIGINT kann der Connector als JS-bigint liefern; das Backend coerct Sequence/Revision/Timestamp an der Mapping-Grenze zu number.

  • PostgreSQL — das Geschwister-Backend, mit dem ausführlicheren Durchgang zu Registrierung + Concurrency.
  • Cassandra-Journal — verteilt, für Scale-out.
  • Durable State — die State-orientierte Alternative zum Event Sourcing.