Operator review of ADR-012's fork design re-litigated §1's crate identity. alkstore-substrate misdescribed what the code is (unpublished, path-dep-only, one consumer, SQLite-only — not a family-wide substrate); the mechanical-diff hope was gone at fork time regardless (port deltas, renames, re-derived half); and the alksocks F-1 lesson applies — a vendored region under a second, weaker instruction set is a defect seam. The fork folds into alkstore-sqlite as a bounded module subtree (src/substrate/); ADR-012 §3–§6 retained verbatim, §2 retained with its enforcement re-sited from the crate graph to diff fence + review + contract-suite equivalence pins. OQ-11 item (1) dissolved.
120 lines
5.4 KiB
Markdown
120 lines
5.4 KiB
Markdown
# 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). *(Ownership superseded
|
||
2026-10-05 by [ADR-011](011-sqlite-substrate-fork.md): the substrate
|
||
is the forked honker-core lineage, carried in-tree as the engine
|
||
crate's substrate module subtree per
|
||
[ADR-013](013-fold-substrate-into-sqlite.md), not the published
|
||
dependency — the driver, seam, and watcher architecture below are
|
||
unchanged.)*
|
||
|
||
- **Driver**: rusqlite, riding honker-core's pinned version. *(The
|
||
pin is ours to move deliberately since the fork — [ADR-011], per
|
||
[ADR-005]'s annotated rusqlite row.)*
|
||
- **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. *(Ownership since resolved:
|
||
the substrate is forked — [ADR-011](011-sqlite-substrate-fork.md) —
|
||
and the queue ops re-derived on contract v1 within it.)*
|
||
|
||
**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.
|
||
[ADR-001]: 001-crate-split.md
|
||
[ADR-005]: 005-dependency-ownership.md
|
||
[ADR-007]: 007-transactional-seam.md
|