feat(docker): add an s3 stack profile for a real bucket as the cold tier - #38
Conversation
Pointing Penca's cold tier at a real S3 bucket required hand-writing a
compose override file: the OBJECT_STORAGE_* keys were literals in
compose.yml, duplicated across query/write/lifecycle, so there was no
env knob to turn. The mechanism was documented only in a comment at the
top of compose.yml.
Collapse the three copies into one x-object-storage anchor merged into
each servicer, interpolated with the existing SeaweedFS values as
defaults, so dev.env and test.env stay silent about storage and resolve
byte-identically to before. Three hand-synced copies of a block whose
disagreement surfaces as an empty table rather than an error is the part
worth deleting.
Endpoint and both credential keys use the no-colon ${VAR-default} form.
A real-S3 deployment has to blank all three -- an empty endpoint is what
makes ObjectStorageConfig skip with_endpoint so the endpoint derives
from the region, and empty keys are what fall through to the standard
AWS credential chain under an instance role. ${VAR:-default} substitutes
on empty as well as unset, so it cannot express that; verified against
docker compose rather than assumed.
Split SeaweedFS and its bucket-init job out of the infra profile into
their own. Postgres and the object store are separately external, and
"real bucket, local Postgres" was not expressible while both lived in
infra. The servicers' dependency on seaweedfs-init becomes
required: false, which still waits when the job is present (measured:
9s vs 1s) and does not fail when the profile is off.
docker/s3.env requires PENCA_S3_BUCKET with no default. A silent
fall-through to the dev bucket name would point a deployment at the
wrong store, and reads against the wrong bucket surface as an empty
table rather than an error. penca-down and penca-logs pass a
placeholder, so a missing variable can never strand a running stack --
neither reads the bucket.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The intro said catalog and branch metadata "is served from Postgres and stays that way," which reads as Postgres being a permanent home for metadata. The intended end state is the opposite: object storage is the only durable tier for anything Penca stores, and the metadata Postgres serves today gets checkpointed out and reconstituted into Postgres tables at startup, so a Postgres page is always rebuildable and never a system of record. Say that in both places the claim appears -- the intro paragraph and the roadmap entry, which already described checkpoint-and-reload but stopped at "recoverable from the object store alone" without naming the consequence. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Integration gate: passedFull suite on a fresh
Zero Checked for the two false-signal modes before trusting the green: no second |
"the roadmap checkpoints it to the object store" reads as the roadmap performing the work. Checkpointing is what sits ON the roadmap. Recast so the work is the subject and the roadmap is where it lives. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Post-merge review on |
Adds a third stack profile,
s3, that points the cold tier at a real S3 bucket while Postgres stays local.Why
The
OBJECT_STORAGE_*keys were literals incompose.yml, duplicated acrossquery/write/lifecycle, so there was no env knob — pointing at a real bucket meant hand-writing a compose override file. That mechanism was documented only in a comment at the top ofcompose.yml, where nobody finds it.What changed
x-object-storageanchor merged into the three servicers, interpolated with the existing SeaweedFS values as defaults.dev.env/test.envsay nothing about storage and resolve byte-identically to before. Three hand-synced copies of a block whose disagreement surfaces as an empty table rather than an error is the part worth deleting.infrainto its own profile. Postgres and the object store are separately external; "real bucket, local Postgres" was not expressible while both lived ininfra. Thes3profile starts no gateway.docker/s3.envrequiresPENCA_S3_BUCKETwith no default — a silent fall-through to the dev bucket name would point a deployment at the wrong store, and reads against the wrong bucket look like an empty table, not a failure.docs/development.mdgains a "Backing the cold tier with a real S3 bucket" section and ans3row in the profile table.Compose semantics — measured, not assumed
Four behaviours this depends on were verified against
docker composedirectly. One would have shipped broken on reasoning alone:${VAR:-default}blanked from an env file${VAR-default}(no colon) blanked from an env file${VAR:?msg}inside an env-file valueenvironment:required: falsewith the dependency presentEndpoint and both credential keys therefore use the no-colon form: a real-S3 deployment has to blank all three. An empty endpoint makes
ObjectStorageConfigskipwith_endpointso the endpoint derives from the region; empty keys fall through to the standard AWS credential chain under an instance role. With${VAR:-default}every S3 deployment would have stayed silently pinned tohttp://seaweedfs:8333.Two fixes that only surfaced by running it
penca-down --profile s3hit the required-var check, so you could bring a stack up in one shell and be unable to tear it down from another.penca-downandpenca-logsnow pass a placeholder — neither reads the bucket.justechoes recipe body lines including comments, so thepenca-logsnote moved above the recipe.Testing
just check— green.us-west-1:CREATE TABLE+ inserts over Flight SQL, forcedPersistBranch/SnapshotBranch, re-read through the cold path,UPDATE/DELETE, flushed again. Objects landed underpersist/,snapshot/,tx_log/; the SeaweedFS bucket stayedsize:0 chunk:0, confirming nothing fell back to it.devandtestprofile service lists and resolved storage env compared before/after — identical.Also in this PR
One README change, folded in at the author's request rather than split out: the intro said
catalog and branch metadata "is served from Postgres and stays that way," which reads as
Postgres being a permanent home for metadata. The intended end state is the opposite —
object storage is the only durable tier for anything Penca stores, and the metadata Postgres
serves today gets checkpointed out and reconstituted into Postgres tables at startup, so a
Postgres page is always rebuildable and never a system of record. Updated in both places the
claim appeared (intro + roadmap entry).
🤖 Generated with Claude Code