Files
alkstore/docs/architecture/deployment.md
T
glm-5.3-flash 49face898d docs alignment: v1 TLS posture owned by deployment.md, QueueOpts numeric consumer-obligation notes (task pg-fix-docs-alignment, review 002 Finding 6 remainder)
- forwarder.rs's ListenerConnection doc corrected: NoTls is hardwired on
  every connection path (pooled, listener, reconnect) — the pooled path
  never rode the consumer's Config sslmode (a sslmode=require DSN fails
  at connect); grep-audited no other in-crate doc repeats the claim
- PgOpts doc carries the corrected one-line TLS pointer (engine-crate-
  docs posture, ADR-016 §2)
- deployment.md: new 'TLS posture (v1)' subsection (NoTls everywhere,
  sslmode=require DSN fails at connect, topology-level confidentiality
  is the v1 substitute, TLS a post-v1 deployment concern) and a new
  'Consumer-obligation notes on engine options' section carrying the
  QueueOpts trusted-as-given note with code-verified per-field symptoms
  (max_attempts <= 0: never claimed, dead-lettered at the next claim
  call's pre-claim sweep; negative visibility: instantly-reclaimable
  claims; negative retention: every dead row at the next sweep_expired)
  plus the PgOpts::max_size 0-guard counter-case; frontmatter advanced
- alkstore/src/opts.rs: QueueOpts struct doc mirrors the
  consumer-obligation note (ADR-023 §2 scoping: the domain table covers
  trait-surface arguments, not consumer-constructed constants)
- cross-file doc sweep over the fix batch's touched files (forwarder,
  tx, scheduler, store) found no further doc-behavior mismatch
- gates: cargo build / clippy --all-targets -D warnings / fmt --check
  all green (doc-only, no test touched)
2026-10-10 05:08:27 +00:00

11 KiB
Raw Blame History

status, last_updated
status last_updated
draft 2026-10-10 (docs alignment — v1 TLS posture stated; QueueOpts numeric consumer-obligation notes)

Deployment

What a deployer must know to size, run, and reason about alkstore engines: host semantics, connection budgets, durability knobs, and where engine differences may honestly surface in the contract. The capability-surface decision is resolved (ADR-016, 2026-10-06): no runtime capability surface — this document's matrix is (with the engine crates' own docs) where the honest boundary lives; this document holds the facts.

Host semantics

Engine Host posture Notes
SQLite single-machine, file-backed NFS two-writers unsupported (honker's honesty posture, inherited, ADR-003). Cross-process on one host is verified POC ground (data_version is cross-process by nature).
Postgres multi-host native Nothing assumes a shared host; POC #2 ran all-through-network (docker bridge) with the same properties (ADR-004).

The unified trait must not pretend SQLite is multi-host — and it does not: ADR-016 resolves that honesty to compile-time engine identity (the engine crate a binary depends on is the deployment statement) plus this documented matrix. There is no Store::capabilities() — the trait's shape constrains where capability differences can surface (ADR-006), and the resolution is: nowhere at runtime.

Options for OQ-08, with their outcome (ADR-016):

  1. Compile-time only — a consumer chooses an engine crate at dependency time; the engine's docs carry its deployment facts. Smallest contract; nothing runtime to match on. Adopted — together with (3); the two compose (engine docs serve the consumer choosing the dependency, the matrix serves the operator choosing the topology).
  2. Store::capabilities() — a runtime description (payload limits, wake cadence knobs, host semantics). Lets a consumer adapt (e.g., chunk large notify payloads) but adds a contract surface all engines must keep honest. Rejected — field-by-field under ADR-008 §5's act-differently rule, and no consumer-inventory row names a runtime-adapt need (ADR-016 §2).
  3. Deployment matrix only (this document) — no API surface. The honest-middle choice; matches the ecosystem's doc-first posture but provides no programmatic guard. Adopted (with (1)) — the "programmatic guard" gap is closed where it can honestly be: the engine-crate dependency edge cannot drift out of sync with the truth it states; the misconfiguration case (SQLite as shared network storage) follows the family's deployment-asserts-truth posture — documented detection symptom, no fabricated runtime machinery (ADR-016 §3).

Connection budgets

SQLite engine

  • Connections are in-process (writer + reader pool + watcher thread owning a connection). No external budget lines; the file lock is the OS-level resource.

Postgres engine

Connection class Count Notes
Pool max_size per process claims/queries via deadpool
Listener +1 per LISTEN-ing process non-pooled, dedicated; pooled connections cannot carry LISTEN (deadpool#360, test-pinned — ADR-004)
— — Sizing rule: max_size + 1 per process; verify end-to-end accounting (POC #2's pgdiag-st-4: pool 6 + 1 listener + probe conns = exact server-side count)

Shared-server co-tenancy (the alkblobs ADR-008 precedent — /workspace/@alkdev/alkblobs/docs/architecture/decisions/ — consumer tables co-tenant the pg instance) is supported and expected — the naming / reserved namespace contract protects reserved names; queue/stream/lock/schedule tables live in one engine-owned PostgreSQL schema (default alkstore, per-engine option) — layout per ADR-010 §8 (resolved from queues.md's namespace bullet).

TLS posture (v1)

The pg engine hardwires NoTls on every connection path in v1 — the pooled connections, the dedicated listener connection, and every reconnect attempt alike; the engine does not ride the consumer's Config sslmode setting, and a DSN carrying sslmode=require (or any TLS demand) fails at connect. The listener's NoTls posture is test-pinned in the type it carries (ADR-004); v1 TLS should therefore be treated as effectively unavailable — network confidentiality between the process and the Postgres server must come from the deployment topology itself (private network, egress rules, a local unix socket or sidecar), not from driver TLS. Encrypting traffic between engine and server is a post-v1 deployment concern (wire a TLS connector through the pool and listener construction); until then the boundary above is the honest statement, per ADR-016's spirit — the matrix states the limitation rather than having code pretend otherwise.

Consumer-obligation notes on engine options

Consumer-constructed numeric option values are trusted as given — the engine does not validate them against domain extents. The engine would be a second normative home for semantics the contract deliberately leaves to the caller's constants; the ADR-023 §2 domain table covers the trait-surface arguments, not these. This applies to the queue stamps (QueueOpts: visibility_timeout_s, dead_letter_retention_s, max_attempts) on both engines:

  • Negative or zero visibility_timeout_s stamps claims whose deadline is already past — every claim is instantly reclaimable (the dual-execution window is the consumer's documented budget ADR-010 §2).
  • Zero or negative max_attempts stamps rows the claim statement never hands out — each is dead-lettered with reason "max attempts exceeded" at the next claim call on that queue (the pre-claim sweep's attempts >= max_attempts arm), i.e. the queue silently discards its work.
  • Negative dead_letter_retention_s deletes every dead row at the next sweep_expired call (retention is driver-free — nothing runs without a caller).
  • The stamps ride future enqueues only (QueueOpts's documented shape) — repair the opts before enqueueing rather than repairing rows afterwards.

The same trusted-as-given shape applies to PgOpts::max_size's positive-integer contract — 0 is typed-failed at open, the one knob with an engine-side guard; everything above has none. The consumer-obligation shape here is the visibility-budgeting precedent (ADR-010 §2): the constant is the consumer's, the behavior of a mis-set one is documented, nothing runtime is fabricated.

Durability knobs

Engine Knob Shape
SQLite synchronous WAL + NORMAL shipped (ADR-003); FULL is available consumer-side for stricter durability; commit fsyncs land at WAL checkpoints (the ~1000-commit spike cadence, POC #1)
SQLite poll_interval the watcher's data_version poll cadence — 1 ms shipping default (ADR-023 §4, the measured-wake-latency posture), carried on SqliteOpts; the idle cost is ~1000 poll reads/sec/instance; raising the interval trades wake latency (interval-bound) for idle CPU — the tuning recipe for latency-tolerant deployments
Postgres synchronous_commit per-session knob; on is ship config (p50 2.40 ms seam); off trades max-tail (40.9 ms) for slightly better p50 — measured, honest trade (ADR-004); session-level SET mechanics POC-verified

These are engine-configuration concerns, not trait surface. What part of engine config is contract-level shape vs engine-crate docs is decided: constructors and option structs live in the engine crates; the contract is the trait the constructor returns (ADR-008 §6).

Toolchain / platform notes

Note Engine Affects
rusqlite 0.40.x needs rustc ≥ 1.99 SQLite any binary linking alkstore-sqlite (ADR-003)
bundled-sqlite adds a C build (~10 s dev, cacheable) SQLite build/CI time
libsqlite3-sys collision with sqlx today any mixed-driver binary structurally avoided (ADR-001 single-driver rule)
Listener application_name set for diagnosability (kill-targetable) Postgres ops runbooks (ADR-004)

Consumer-facing latency profile (indicative, POC-measured)

From both POCs (single-box, relative shapes are the deliverable — ADR-003 and ADR-004 carry the full tables):

  • Tx seam: SQLite ~0.35 ms p50; Postgres ~2.4 ms p50 (ship config).
  • Wake: ~1.1–2.2 ms p50 both engines at default cadence.
  • Queue claim: LISTEN-driven 3–6 ms p50 (pg); poll-only interval-bound (32–50 ms at a 50 ms poll).
  • Absolute numbers will differ per hardware/network; they set expectations of order, not SLAs — contract docs must not bake them in (ADR-007's pg-POC note).

Design Decisions

ADR Decision Summary
001 Crate split single-driver binaries shape the matrix
003 SQLite driver bundling, toolchain floor
004 Postgres driver listener budget line, forwarder posture
006 Wake contract where capability differences may surface
008 Contract v1 constructor/options in engine crates; no capability surface in v1 (OQ-08; resolved by ADR-016)
016 Deployment honesty no runtime capability surface — compile-time identity + this matrix; PayloadTooLarge is the one runtime asymmetry carriage
021 Third review round drop = rollback keeps the connection budgets exact under error paths (SQLite lease releases; pg client re-pools)

Open Questions

Open questions are tracked in open-questions.md. Key questions affecting this document:

  • OQ-08: capability-surface shape — resolved (2026-10-06, ADR-016): no runtime capability surface; compile-time engine identity + this matrix; re-entry via a consumer-inventory row naming a runtime-adapt need.