Files
alkstore/docs/architecture/decisions/003-sqlite-driver.md
T
glm-5.3-flash 4391f6e879 docs: open Phase 1 — architecture spec set over the Phase 0 evidence
docs/architecture/ now exists: README index, overview, five component
specs (core-contract, engine-sqlite, engine-postgres, queues,
deployment), ADR-001..007 carrying the Phase 0 resolved decisions
(crate split, feature scope, per-engine drivers, dependency
ownership, wake contract, tx seam), and the centralized
open-questions tracker promotion: OQ-ST-01..08 mirror to OQ-01..08
one-to-one with statuses/resolutions carried; new Phase 1 questions
append (OQ-09 scheduler collapse, OQ-10 contract versioning).
Open Phase 1 work: OQ-04 contract pinning (high), OQ-05 queue
semantics depth (high), OQ-06 honker-core quality read (high;
fork-trigger gate), OQ-08 capability surface, OQ-09, OQ-10.

Erratum fixed in phase-0 OQ-ST-04 (thread-affinity friction is
SQLite-side, previously garbled as pg-side) and a stale scheduler-
boundary pointer corrected in consumer-inventory.md. Two review
passes run (findings: OQ-promotion numbering faithfulness, ADR
back-reference sync) — all critical/warning findings resolved.
2026-10-04 18:13:10 +00:00

4.5 KiB
Raw Blame History

ADR-003: SQLite engine — rusqlite + published honker-core, bridged at the trait seam

Status

Accepted

Context

The SQLite engine's options were never a single "rusqlite vs sqlx" axis (OQ-ST-03). Three distinct postures existed:

  1. honker-core on our own rusqlite connection — we own connection/schema/watcher wiring; honker supplies the SQL-function machinery (attach_notify, attach_honker_functions, bootstrap_honker_schema).
  2. honker-rs as the SQLite substrate (Database::open — honker holds its own connections; sync-only; transactions pin the connection mutex).
  3. raw SQL over sqlx-sqlite with the honker loadable extension (.so built from published source, loaded per pool connection).

POC #1 (docs/research/poc-sqlite-posture-findings.md, ran 2026-10-04; POC #2 is its pg twin) measured postures 1 and 3 end-to-end and settled the choice. Constraint facts that outlive the comparison:

  • rusqlite 0.40.x (what honker-core 0.5.0 pins) needs rustc ≥ 1.99 (cfg_select!) — a deployment note for any binary linking it.
  • sqlx's libsqlite3-sys range collides with rusqlite 0.40's — mixed rusqlite+sqlx binaries need a vendored one-line patch today. The per-engine-crate split ([ADR-001]) keeps engine binaries single-driver, dissolving this.
  • The async-facing-trait + sync-bridge posture is family-standard (alktty REQ-TTY-01; alkblobs store-api.md): bridge-at-the-seam is a supported posture, not a workaround — which removes native-async's main differentiator before any measurement.

Decision

The SQLite engine uses posture 1: published honker-core = 0.5 as a library dependency over the crate's own rusqlite connections (bundled-sqlite for hermetic builds).

  • Driver: rusqlite, riding honker-core's pinned version.
  • Async seam: bridged. The writer slot (Writer) + reader pool (Readers) + every engine call in spawn_blocking. A dedicated std-thread bridge (one thread owning the writer conn, ops over mpsc) is the recorded optimization path if seam throughput ever demands it — not the default.
  • Wake: honker-core's SharedUpdateWatcher inherited (p50 ≈ 1.4 ms at the default 1 ms PRAGMA data_version cadence, with battle-tested failure handling — subscriber senders close on watcher death, never silent-hang). A self-built thin watcher was benchmarked only to confirm honker-core's is tighter (p50 1.40 vs 2.15 ms; max 29 vs 172 ms); no hybrid is warranted.
  • Transactions: the engine opens BEGIN IMMEDIATE on the writer slot and hands back a caller-held tx handle ([ADR-007]). The slot lease models WAL's single-writer honestly: a long transaction parks the writer — that is SQLite's shape, not an emulation artifact.
  • No .so runtime artifact, no vendored patches in the engine crate.

Rejected alternatives, briefly, for the record: posture 3 is viable but pays ~2× p50 seam cost, a runtime dlopen artifact, wider dependency tree, and sqlx 0.9 ergonomic warts (unsafe extension(), SqlSafeStr) for no compensating advantage. Posture 2 (honker-rs) was never measured because posture 1 dominates it on control with comparable reuse.

Consequences

Positive

  • ~0.35 ms p50 transactional seam (measured), ~1.4 ms wake latency, and honker-core's watcher failure-handling inherited for free.
  • Static linkage: "open a path, get a store" needs no runtime artifacts.
  • The engine's job shrinks to wiring + the SQL surfaces honker doesn't cover (queue semantics depth, OQ-ST-05's SQLite side rides honker's machinery directly) + the async bridge.

Negative

  • honker-core pins rusqlite ^0.40.1; version movement in honker-core moves our rusqlite. A honker-core quality read is the recorded fork trigger ([ADR-005], OQ-06).
  • rustc ≥ 1.99 required by rusqlite 0.40.x — binaries linking this engine carry that toolchain floor (deployment-matrix row, deployment.md).
  • The sync bridge means engine ops pay a spawn_blocking hop — mostly invisible next to SQLite write costs, but it shapes the tx-handle design ([ADR-007]).

References

  • docs/research/poc-sqlite-posture-findings.md — the measurements.
  • OQ-ST-03 (docs/research/phase-0.md) — resolution record.
  • ADR-001 — why the engine crate is single-driver.
  • ADR-004 — the Postgres counterpart.
  • ADR-007 — the tx-handle shape both engines implement.
  • engine-sqlite.md — the engine spec this decision defines.