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.
Installation
Abschnitt betitelt „Installation“mariadb ist eine optionale Peer-Dependency:
bun add mariadbBeim ersten Zugriff lazy-importiert, wie jeder andere Adapter.
Einrichtung
Abschnitt betitelt „Einrichtung“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.
Konfiguration
Abschnitt betitelt „Konfiguration“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 obenDialekt-Unterschiede ggü. Postgres
Abschnitt betitelt „Dialekt-Unterschiede ggü. Postgres“Das Verhalten ist identisch; das SQL unterscheidet sich:
| Operation | Postgres | MariaDB |
|---|---|---|
| Platzhalter | $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-gewrappte Subquery¹ |
| Durable-Create-Konflikt | ON CONFLICT DO NOTHING → rowCount | reines INSERT, ER_DUP_ENTRY (1062) fangen |
| Concurrency-Backstop | SQLSTATE 23505 | ER_DUP_ENTRY / 1062 |
| Contention-Backstop | SQLSTATE 40001 / 40P01 | ER_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.
Fallstricke
Abschnitt betitelt „Fallstricke“Wie geht’s weiter
Abschnitt betitelt „Wie geht’s weiter“- 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.
