- 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.
4.7 KiB
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 aPgLoBackendimplementing the ADR-008 trait contract includingsize). 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
- 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 (thelocalengine baseline, same harness) and kv-tier pg (bytea) at the lower sizes, as cross-checks of methodology parity. (loread/lowritevia SQL functions through tokio-postgres — thepostgres_large_objectcrate targets postgres 0.15 and is dead; do not use it.) - 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). - 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); verifysize-without-fetch (SELECT sizefrom the companion table). - Churn/vacuum probe: repeated put/delete cycles at 1–16 MiB;
measure
pg_largeobjectcatalog growth, TOAST/vacuum behavior, autovacuum posture (the fs engine inherits ADR-008/007's maintenance-surface honesty requirement). - 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_unlinkunder the delete window), virgin-store no-op reads,sizeprobe — 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).