Files
alkstore/docs/architecture/decisions/003-sqlite-driver.md
T
glm-5.3-flash befbe2e714 OQ-06 resolved: honker-core quality read fires the fork trigger (ADR-011)
Quality read of honker-core's watcher/transactional core cross-checked
against the published crates.io artifact: the core itself is clean
(Writer/Readers, polling-watcher failure handling, WatcherDeathGuard
all verified), but published 0.5.0 predates upstream's unreleased fix
train carrying the issue-#133 savepoint hardening (silent job loss in
the dead-letter paths) and five .ok() error swallows — and ADR-010's
queue depth requires engine-owned queue SQL in any posture. Resolution:
fork honker-core at the reference revision, inherit the clean machinery
and test suites, re-derive queue ops on contract v1, rename tables to
__alkstore_*.

- docs/research/quality-read-honker-core.md — full evidence
- docs/architecture/decisions/011-sqlite-substrate-fork.md — decision
- OQ-06 resolved in open-questions.md; ADR-003/005/008, engine-sqlite,
  queues, README annotated for consistency
2026-10-05 03:39:46 +00:00

106 lines
4.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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) — *(resolved 2026-10-05: the trigger
fired, [ADR-011](011-sqlite-substrate-fork.md) forks the substrate;
its rusqlite pin is then ours to move deliberately.)*
- rustc ≥ 1.99 required by rusqlite 0.40.x — binaries linking this
engine carry that toolchain floor (deployment-matrix row,
[deployment.md](../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](001-crate-split.md) — why the engine crate is
single-driver.
- [ADR-004](004-postgres-driver.md) — the Postgres counterpart.
- [ADR-007](007-transactional-seam.md) — the tx-handle shape both
engines implement.
- [engine-sqlite.md](../engine-sqlite.md) — the engine spec this
decision defines.