#!/usr/bin/env python3 """Throwaway prototype: SQLite-authoritative Records, Markdown as a projection. NOT product code and not wired into anything. It answers one question with numbers: if a collection's rows lived in a per-vault SQLite table (same natural key, an O(1) guard, a per-row audit event) and the Markdown item files were only a rendered read-only projection, what would a single guarded append and a guard refresh cost at 1,000 and 10,000 items? Two projection modes are timed: * ``sync`` -- the item file is rendered and atomically written inside the append, before the transaction commits (a failed write rolls the row back). * ``async`` -- the transaction commits the row and an outbox entry; a worker thread renders and writes the file afterwards. Foreground latency excludes the render; the worker's lag is reported separately. What is deliberately NOT here, so the numbers are storage-layer only: request validation, the mutation-boundary lease, the idempotency ledger, governance and withheld-is-absent filtering, index fan-out and the response. The findings doc adds the flat, size-independent cost of those (measured on the real path) back on. uv run python benchmarks/prototype-sqlite-records.py --sizes 1000,10000 --appends 30 """ from __future__ import annotations import argparse import datetime as dt import hashlib import json import os import queue import shutil import sqlite3 import sys import tempfile import threading import time import uuid from pathlib import Path REPO = Path(__file__).resolve().parents[1] sys.path.insert(0, str(REPO / "src")) SCHEMA = """ CREATE TABLE collections ( collection_id TEXT PRIMARY KEY, generation INTEGER NOT NULL, manifest_hash TEXT NOT NULL); CREATE TABLE records ( collection_id TEXT NOT NULL, item_key TEXT NOT NULL, natural_key TEXT NOT NULL, values_json TEXT NOT NULL, body TEXT NOT NULL, item_hash TEXT NOT NULL, version INTEGER NOT NULL, PRIMARY KEY (collection_id, item_key), UNIQUE (collection_id, natural_key)); CREATE TABLE audit ( seq INTEGER PRIMARY KEY AUTOINCREMENT, collection_id TEXT NOT NULL, transition_id TEXT NOT NULL, parent_id TEXT, operation TEXT NOT NULL, item_key TEXT NOT NULL, before_hash TEXT, after_hash TEXT NOT NULL, payload_hash TEXT NOT NULL, why TEXT NOT NULL, at TEXT NOT NULL); CREATE TABLE outbox ( seq INTEGER PRIMARY KEY AUTOINCREMENT, collection_id TEXT NOT NULL, item_key TEXT NOT NULL, enqueued_ns INTEGER NOT NULL, done_ns INTEGER); """ def _load_harness(): """The latency harness owns the synthetic manifest and item generator; reuse both.""" import importlib.util spec = importlib.util.spec_from_file_location( "records_append_latency_harness", REPO / "scripts" / "measure-records-append-latency.py" ) assert spec is not None and spec.loader is not None module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) return module def _pct(values: list[float], q: float) -> float: ordered = sorted(values) rank = (len(ordered) - 1) * q lo, hi = int(rank), min(int(rank) + 1, len(ordered) - 1) return ordered[lo] + (ordered[hi] - ordered[lo]) * (rank - lo) def _summary(values: list[float]) -> dict[str, float]: return { "p50_ms": round(1000 * _pct(values, 0.5), 2), "p95_ms": round(1000 * _pct(values, 0.95), 2), } class Store: """The prototype: rows are authoritative, files are rendered from them.""" def __init__(self, root: Path, manifest, sync_mode: str, synchronous: str) -> None: from exomem import record_formats from exomem import structured_collections as collections self.record_formats = record_formats self.collections = collections self.root = root self.manifest = manifest self.cid = manifest.collection_id self.mode = sync_mode self.db = sqlite3.connect(root / "records.sqlite", isolation_level=None) self.db.execute("PRAGMA journal_mode=WAL") self.db.execute(f"PRAGMA synchronous={synchronous}") self.db.executescript(SCHEMA) self.db.execute( "INSERT INTO collections VALUES (?,?,?)", (self.cid, 0, manifest.manifest_version.hash), ) self.items_dir = root / manifest.storage.source self.items_dir.mkdir(parents=True, exist_ok=True) self.lag_seconds: list[float] = [] self._queue: queue.Queue[tuple[str, dict, str] | None] = queue.Queue() self._worker: threading.Thread | None = None if sync_mode == "async": self._worker = threading.Thread(target=self._drain, daemon=True) self._worker.start() # -- guard ------------------------------------------------------------------ def refresh_guard(self) -> int: """The client's container-version read: one indexed row.""" row = self.db.execute( "SELECT generation FROM collections WHERE collection_id=?", (self.cid,) ).fetchone() return int(row[0]) # -- projection --------------------------------------------------------------- def _render(self, key: str, values: dict, correlation: str) -> str: return self.record_formats.render_markdown_item( self.manifest, values, key, "", correlation, resolve_relationship=None ) def _write_projection(self, key: str, text: str) -> None: target = self.items_dir / f"{key}.md" temp = target.with_suffix(".tmp") with open(temp, "w", encoding="utf-8") as handle: handle.write(text) handle.flush() os.fsync(handle.fileno()) os.replace(temp, target) def _drain(self) -> None: while (job := self._queue.get()) is not None: key, values, correlation, enqueued = job # type: ignore[misc] self._write_projection(key, self._render(key, values, correlation)) self.lag_seconds.append((time.perf_counter_ns() - enqueued) / 1e9) self._queue.task_done() # -- append ------------------------------------------------------------------- def append(self, values: dict, why: str, expected_generation: int | None) -> None: natural = json.dumps( [values["observed_on"], values["subject"]], separators=(",", ":") ) key = str(uuid.uuid5(uuid.NAMESPACE_URL, f"{self.cid}/{natural}")) correlation = uuid.uuid4().hex[:24] payload = json.dumps(values, sort_keys=True, separators=(",", ":")) payload_hash = hashlib.sha256(payload.encode()).hexdigest() db = self.db db.execute("BEGIN IMMEDIATE") try: generation, manifest_hash = db.execute( "SELECT generation, manifest_hash FROM collections WHERE collection_id=?", (self.cid,), ).fetchone() if expected_generation is not None and expected_generation != generation: raise RuntimeError("STALE_RECORD") twin = db.execute( "SELECT item_key FROM records WHERE collection_id=? AND natural_key=?", (self.cid, natural), ).fetchone() if twin is not None: raise RuntimeError("RECORD_ID_CONFLICT") item_hash = payload_hash db.execute( "INSERT INTO records VALUES (?,?,?,?,?,?,?)", (self.cid, key, natural, payload, "", item_hash, 1), ) parent = db.execute( "SELECT transition_id FROM audit WHERE collection_id=? ORDER BY seq DESC LIMIT 1", (self.cid,), ).fetchone() db.execute( "INSERT INTO audit (collection_id, transition_id, parent_id, operation," " item_key, before_hash, after_hash, payload_hash, why, at)" " VALUES (?,?,?,?,?,?,?,?,?,?)", ( self.cid, correlation, parent[0] if parent else None, "append", key, None, item_hash, payload_hash, why, dt.datetime.now(dt.timezone.utc).isoformat(), ), ) db.execute( "UPDATE collections SET generation=generation+1 WHERE collection_id=?", (self.cid,), ) if self.mode == "sync": self._write_projection(key, self._render(key, values, correlation)) else: db.execute( "INSERT INTO outbox (collection_id, item_key, enqueued_ns) VALUES (?,?,?)", (self.cid, key, time.perf_counter_ns()), ) db.execute("COMMIT") except BaseException: db.execute("ROLLBACK") raise if self.mode == "async": self._queue.put((key, values, correlation, time.perf_counter_ns())) # type: ignore[arg-type] def flush(self) -> None: if self._worker is not None: self._queue.join() self._queue.put(None) self._worker.join() def seed(self, count: int, item, project: bool) -> float: started = time.perf_counter() db = self.db db.execute("BEGIN") for index in range(count): values = item(index) natural = json.dumps( [values["observed_on"], values["subject"]], separators=(",", ":") ) key = str(uuid.uuid5(uuid.NAMESPACE_URL, f"{self.cid}/{natural}")) payload = json.dumps(values, sort_keys=True, separators=(",", ":")) digest = hashlib.sha256(payload.encode()).hexdigest() db.execute( "INSERT INTO records VALUES (?,?,?,?,?,?,?)", (self.cid, key, natural, payload, "", digest, 1), ) if project: self._write_projection_fast(key, values) db.execute("UPDATE collections SET generation=?", (count,)) db.execute("COMMIT") return time.perf_counter() - started def _write_projection_fast(self, key: str, values: dict) -> None: text = self._render(key, values, "seed") (self.items_dir / f"{key}.md").write_text(text, encoding="utf-8") def run(size: int, appends: int, mode: str, synchronous: str) -> dict[str, object]: shared = _load_harness() tmp = Path(tempfile.mkdtemp(prefix="exomem-sqlite-proto-")) try: os.environ.setdefault("EXOMEM_STATE_ROOT", str(tmp / "state")) manifest_file = tmp / shared.COLLECTION manifest_file.parent.mkdir(parents=True) manifest_file.write_text(shared.MANIFEST, encoding="utf-8") from exomem import structured_collections as collections manifest = collections.load_manifest(tmp, manifest_file) store = Store(manifest_file.parent, manifest, mode, synchronous) seeded = store.seed(size, shared._item, project=True) refresh: list[float] = [] wall: list[float] = [] for index in range(appends): start = time.perf_counter() generation = store.refresh_guard() refresh.append(time.perf_counter() - start) start = time.perf_counter() store.append(shared._item(size + index), "prototype", generation) wall.append(time.perf_counter() - start) store.flush() return { "size": size, "mode": mode, "synchronous": synchronous, "seed_seconds": round(seeded, 1), "append": _summary(wall), "guard_refresh": _summary(refresh), "projection_lag_ms": _summary(store.lag_seconds) if store.lag_seconds else None, } finally: shutil.rmtree(tmp, ignore_errors=True) def main() -> int: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--sizes", default="1000,10000") parser.add_argument("--appends", type=int, default=30) parser.add_argument("--synchronous", default="FULL", choices=["FULL", "NORMAL"]) parser.add_argument("--out", default="sqlite-prototype-results.json") args = parser.parse_args() results = [] for size in [int(part) for part in args.sizes.split(",")]: for mode in ("sync", "async"): print(f"== size {size} mode {mode} ==", file=sys.stderr, flush=True) results.append(run(size, args.appends, mode, args.synchronous)) Path(args.out).write_text(json.dumps(results, indent=2) + "\n", encoding="utf-8") print(json.dumps(results, indent=2)) return 0 if __name__ == "__main__": raise SystemExit(main())