devilsberg/laravel-mariadb-vector
MariaDB 11.7+ native vector storage and similarity search for Laravel. Adds a VECTOR column type, binary-safe Eloquent casts, and nearestNeighbors() query macros powered by VEC_DISTANCE functions — essentially pgvector, but for MariaDB.
Architecture fit. This is a thin, opinionated adapter over native database capability rather than a search engine. It contributes three narrow things: a vector() column type plus vectorIndex() Blueprint macro, a VectorCast that owns the binary VECTOR ↔ float[] boundary, and four Eloquent query macros (whereVectorSimilarTo, orderByVectorDistance, selectVectorDistance, nearestNeighbors) that compile to VEC_DISTANCE_COSINE / VEC_DISTANCE_EUCLIDEAN over VEC_FromText(?). From a TPM standpoint this is the most desirable shape a dependency can take: the SQL semantics are portable knowledge we keep even if the package goes away, the value-add is ergonomics and casting correctness, and blast radius is confined to query construction and model hydration. No new service, no new datastore, no dual-write pipeline, no second consistency model to reason about.
Integration feasibility. Low engineering effort, gated almost entirely by prerequisites rather than by the package. Three hard gates: PHP 8.4+, Laravel 12+, MariaDB 11.7+. The MariaDB constraint is the real project — 11.7 is a recent release line, not the 11.4 LTS. If we are on 10.x, 11.4 LTS, or MySQL 8, this is a non-starter until a database upgrade ships, and the package is then a roughly one-day follow-on. If we are already on the required versions, adoption is composer require, a config publish, one migration, one cast declaration — a sprint-sized spike.
Technical risk. The dominant risk is project liveness, not code quality: the repository is archived, 9 stars, 0 dependents, single maintainer, no downstream production evidence. We must treat it as a reference implementation we adopt and own, not as a vendor. That is acceptable here specifically because the surface is small and fully inspectable — the macros are string concatenation over documented MariaDB functions, and the cast is a pack/unpack routine. Secondary risks are structural, enforced by MariaDB itself: one VECTOR INDEX per table, index requires a NOT NULL column, no DOT metric, fixed dimension at DDL time, and no tuning knobs exposed for ANN quality (recall/ef parameters), so ANN behaviour is whatever MariaDB's defaults give us. Note also that v1.2.1 fixed a real correctness bug in binary/JSON format detection — pin at or above that version; anything earlier silently misreads roughly 1 in 256 vectors.
Key questions.
Stack fit. Strong fit for a Laravel 12 monolith already on Eloquent with a MySQL-family driver. The "zero external services" property is the headline commercial argument: it removes a vendor, a secret, a network hop, a data-egress review, and a second consistency boundary from the architecture. For teams currently running Pinecone/Qdrant/Weaviate alongside an existing MariaDB, this converts an external dependency into a schema migration.
Migration path. The database upgrade is the critical path and the package is a one-day follower, so plan them as separate workstreams with separate risk. Do not bundle the package adoption into the DB upgrade ticket — the upgrade needs its own soak period and rollback story.
Compatibility. Confirm the mariadb driver is actually configured; the package will not work on MySQL 8, and a driver misconfiguration should surface in a spike, not in staging. Two package-level compatibility notes: the macros register globally on Illuminate\Database\Eloquent\Builder, so check for name collisions with existing macros or other vector/search packages in the dependency tree; and the config-level distance_metric default silently governs any macro call that omits the metric, so set it explicitly rather than inheriting the COSINE default.
Sequencing.
>=1.2.1, review the macros and cast, publish internally.vector() + vectorIndex(), cast, sample write/read round-trip.nearestNeighbors) behind a feature flag against the current search implementation.Maintenance. Effectively we are the maintainer. The archived status means no upstream security or compatibility patches, and future PHP/Laravel upgrades are our problem. The mitigating fact is size: the code surface is small enough that a fork is a bounded, one-time review cost rather than an ongoing drain. Budget it explicitly and put the fork in the normal dependency-review cadence — an archived package that nobody looks at is how a small risk becomes a stale-code incident two years later.
Support. No SLA, no vendor, no commercial backing. Support is internal. This needs a named owner and a documented "what we changed and why" note in the fork, or the next engineer will not know it is a fork.
Scaling. Three concrete ceilings. First, ingestion throughput: writes go through Eloquent attribute assignment and row-by-row saves, and the README documents no bulk-insert path, so embedding backfills at corpus scale will need our own chunked batch writer against raw SQL. Second, index DDL: ALTER TABLE ... ADD VECTOR INDEX on a populated large table is a locking operation and must be scheduled, not run in a routine deploy. Third, ANN quality is unverified — MariaDB's vector index is approximate, the package exposes no recall tuning, and we should assume a recall benchmark is required before we depend on it for a user-facing feature. Under a few tens of thousands of rows, a full scan without the index is likely simpler and entirely adequate; treat the index as an optimisation we adopt on measurement.
Failure modes to plan for.
VECTOR(n) DDL — surfaces as a write error, but the operational failure is a silent semantic split if two models are in flight during a model change. Re-embedding needs a versioned column or a version column, not an in-place overwrite.vectorIndex() — DDL error, caught in migration, low impact.VECTOR INDEX on one table — hard architectural limit; multi-vector-per-entity designs must be flattened into a single indexed column or a side table with its own index budget.DOT product configured — throws InvalidArgumentException; only meaningful if someone assumes pgvector parity.null, so "no embedding yet" and "embedding of nothing" are indistinguishable — decide the convention deliberately.nearestNeighbors returns a normalised score where higher is better, inverting MariaDB's distance convention. Any dashboard, threshold, or API contract built on top must be explicit about which direction it means — this is the most likely source of quiet product bugs.Ramp-up. Low for any Laravel developer: the API is three method calls and a cast, and the README is genuinely good. The concepts that need a short primer are not the code but the model — distance metric choice, dimension lock-in, exact vs. approximate search, and the cost of changing embedding models after data exists. Plan a single team walkthrough plus a written runbook covering re-embedding and index rebuild; that runbook, not the package documentation, is the durable artefact.
How can I help you explore Laravel packages today?