The MySQL/MariaDB backend for busbar's durable governance store — a kind: store plugin.
Targets the common SQL subset supported by MySQL 8.0.16+, MariaDB, and Aurora MySQL: one plugin, protocol-compatible with all three, same reasoning as busbar's "Valkey (Redis-protocol compatible)" naming — broad coverage via standard SQL, not three separate builds.
Point a fleet of busbar nodes at one MySQL/MariaDB server and they share virtual keys, budgets, and
usage across the cluster, same as the sibling store-postgres plugin.
store:
module: mysql
settings:
url: "mysql://user:pass@host:3306/busbar"Drop the built .so/.dll/.dylib into busbar's plugins_dir, or install it live via
POST /plugins — see busbar's admin API docs.
- MySQL >= 8.0.16 (older 8.x releases and Aurora MySQL 2.x parse
CHECKconstraints but do not enforce them — this plugin boot-probes for real enforcement and refuses to start if it's missing, rather than silently accepting unvalidated data), or MariaDB, or Aurora MySQL 3.x. sql_modemust includeSTRICT_ALL_TABLESorSTRICT_TRANS_TABLES— also boot-probed. Set it server-wide (SET GLOBAL sql_mode='STRICT_ALL_TABLES';) before pointing busbar at the server.
api_keys, notkeys:KEYSis a MySQL/MariaDB reserved word. Every other backend (Postgres, SQLite, Valkey) keepskeys.- A single-row
store_sequencerevision counter is bumped FIRST in every control-plane transaction, beforeapi_keys/credentials/denylist— a fixed lock order that makes deadlock across the admin plane structurally impossible. ascii_bincollation on every opaque identifier column (credential lookup handles, key ids, group names): MySQL's default collation is case-insensitive, which would be a real security-relevant footgun for a credential handle. Byte-exact comparison, matching Postgres/SQLite's default.- Every conditional mutation does an explicit
SELECT ... FOR UPDATEexistence/state check before acting — MySQL'srows_affected()reports rows changed, not rows matched, so an idempotent no-op can't be distinguished from "not found" by row count alone. DELETE /keys/{id}tombstones, it never removes the row: every credential is destroyed,enabled=false/deleted_atare set, butid/name/key_group/labelssurvive so billing attribution (usage_metering.key_id) keeps resolving forever. The tombstone and credential destruction land in the SAME transaction with ONE revision stamp — a hydrator can never observe the tombstone without the credentials already being gone.
docker run -d -p 3306:3306 \
-e MYSQL_ROOT_PASSWORD=busbar -e MYSQL_USER=busbar -e MYSQL_PASSWORD=busbar \
-e MYSQL_DATABASE=busbar_test mysql:8
BUSBAR_TEST_MYSQL_URL=mysql://busbar:busbar@127.0.0.1:3306/busbar_test cargo test
Tests skip cleanly (not fail) when BUSBAR_TEST_MYSQL_URL is unset locally; CI always sets it via
the mysql:8 service container in the shared plugin-ci.yml workflow.
Built against busbar 1.5.0's generic-credentials Store trait redesign. MariaDB compatibility is
validated by schema/query design (standard SQL, no MySQL-8-only syntax used) but not yet exercised
against a live MariaDB container in this repo's test suite — flagged as a follow-up, not a
guarantee. MySQL 8 is the fully tested target.