Files
alkblobs/docs/research/poc-pglo-spec.md
T
glm-5.3-flash 281c37876e docs(architecture): ADR-008 — trait size probe, fleet GC, fs-tier engines; requirements anchor
- requirements.md (new): the three use cases pinned as REQ-1..4 with
  the node/pool/fleet/engine vocabulary defined once — ends per-session
  re-derivation of consumer facts. REQ-2 (replicator fleet over one
  shared pg pool, incl. large-blob serving) is recorded as a planning
  fact predating all POCs, on the operator's authority.
- ADR-008: Backend trait gains size(key) length probe (before any
  backend ships data — ADR-002 one-way-door discipline); fleet GC
  mechanism (DB-backed pin rows committed atomically with entries,
  TTL+renewal semantics, liveness = embedder-owned table, protect
  callback is single-node-only, advisory-locked single sweeper,
  staged re-arbitrated delete window on both engines); fs tier becomes
  engine-selectable (local default; pg-lo named candidate) with the
  fleet locality contract (shared media or re-routing; mixed tiers are
  a documented deployment invariant, not a constructor-provable one).
- poc-pglo-spec.md (new): POC #7 spec — pg Large Objects as the fs
  tier's pg-lo engine; instruments, decision gate, registered in
  phase-0.md OQ-BL-06.
- ADR-003/005 status amendments point to ADR-008's extensions; specs
  ripple (backends/store-api/gc/ops/overview/README).
- open-questions.md: deferral-policy header gains the decisions-vs-
  sequenced-work distinction; pg-lo's why-not-parked audit trail
  recorded.
- research fixes: postgres POC renumbered #4->#5 to the canonical
  register (phase-0 OQ-BL-06), redb cross-refs fixed, thinking-
  artifact sentence in B1 replaced with the honest reading.

Verification: docs-only change; reference-integrity sweep across the
tree (ADR/REQ/POC refs resolve); architecture-reviewer pass on the
delta — original 3 criticals addressed, its follow-up (fleet liveness
form, pin TTL, staged-delete semantics, enforceability, shared-media
caveats) fixed in this commit.
2026-10-03 03:41:49 +00:00

4.7 KiB
Raw Blame History

status, title, last_updated
status title last_updated
specified POC #7 — postgres Large Objects as the fs tier's pg-lo engine 2026-10-03

POC: postgres Large Objects as the fs tier's pg-lo engine — spec

POC register #7, added post-Phase-0 per the ADR-003/007 substitution door (the third use of it, after postgres-on-kv and redb-on-kv). Code: standalone crate /workspace/alkblobs-pglo-poc (clone the POC #5 crate's harness conventions; add a PgLoBackend implementing the ADR-008 trait contract including size). Findings land here regardless, per the established convention. Requested by REQ-2 (requirements.md — the fleet-with-large-blobs consolidation option; ADR-008 names pg-lo as the candidate fs-tier engine). Status: specified, not run.

What this POC must decide

The ADR-008 question: can postgres Large Objects hold the fs tier's contract for the fleet topology — serving the ≥128 KiB regime (packfiles, large artifacts) from the same engine the kv tier already rides — at acceptable performance and with an honest ops posture? The fs tier's contract (ADR-003/008): get yields a pread-able handle, stage-then-commit semantics for unknown-length puts, complete list(), GC-participating delete, size without content fetch.

Large Objects' native shape differs from files in one structural way the ADR names: LO descriptors are transaction-scoped (lo_open → loread/lowrite must run inside a tx). The POC must measure what that does to the handle pattern under a connection pool, not assume it away.

Instruments

  1. Inherited bench_small-shaped harness, LO arm — the POC #3/#5 methodology at fs-tier-relevant sizes: 128 KiB, 256 KiB, 1 MiB, 16 MiB, 128 MiB, sequential and random-replay, p50/p99. Compare against: local fs (the local engine baseline, same harness) and kv-tier pg (bytea) at the lower sizes, as cross-checks of methodology parity. (loread/lowrite via SQL functions through tokio-postgres — the postgres_large_object crate targets postgres 0.15 and is dead; do not use it.)
  2. Handle-cost probe (pgdiag7): the tx-scoped descriptor under deadpool — cost of open-tx → lo_open → lseek/loread per range read, held vs. re-opened per read; pool pressure under N concurrent readers (the ops fetch handler's real shape).
  3. Write path probe (pgdiag8): lo_create + chunked lowrite (the LOBLKSIZE ~2 KiB page layout matters here — measure write amplification as value size grows); unknown-length put shape (stage into one LO, companion row on commit); verify size-without-fetch (SELECT size from the companion table).
  4. Churn/vacuum probe: repeated put/delete cycles at 1–16 MiB; measure pg_largeobject catalog growth, TOAST/vacuum behavior, autovacuum posture (the fs engine inherits ADR-008/007's maintenance-surface honesty requirement).
  5. Crash/orphan probe: kill client mid-write-tx; verify no orphan LO and no half-commit; kill after lo_create before commit; write the orphan-recovery sweep story the engine ADR will need (compare lo_get-visible OIDs against the companion table — never trust the catalogs).

Decision gate (mirrors ADR-007's)

pg-lo is admitted as a shipped fs-tier engine iff:

  • Performance: LO put/get at 128 KiB–16 MiB is within an order-of-magnitude of local fs on the same box (the tier is chosen for fleet correctness, not solo speed — but a 10× gap would put the fleet's packfile serving materially behind a single-node fs deployment, and the ADR must say so if measured);
  • Contract: complete list() (companion table), GC-participating delete (lo_unlink under the delete window), virgin-store no-op reads, size probe — all hold, proven via the exact-count sweep-outcome test shape under --all-features;
  • Ops posture: named deltas (tx-scoped handles, autovacuum/vacuum on pg_largeobject, orphan recovery sweep) with measured or documented-bounded costs.

Findings feed an engine ADR in the ADR-007 shape (measured curves, posture deltas as requirements). If the gate fails, ADR-008's recorded statement stands: shared-media or re-routing are REQ-2's fleet answers, and pg-lo's named cost was paid as evidence, not shipped blind.

Register note (phase-0.md OQ-BL-06 convention)

The canonical register is phase-0.md OQ-BL-06: #1 trait dispatch, #2 absorbed, #3 large-blob, #4 pooled-CAS (covered by #1), #5 postgres, #6 redb. The post-convergence POC files briefly carried inconsistent self-numberings (the postgres file titled itself "#4"); they are renumbered to the register. This POC is #7 — specified 2026-10-03, requested by REQ-2 (requirements.md; ADR-008 names pg-lo as the candidate fs-tier engine).