Module 11 · Dive 11.2
The archive_command is the fence.
Postgres asks one hard thing of WAL archiving: the archive_command must be idempotent, and it must never overwrite a same-named segment with different bytes — break that and point-in-time recovery corrupts. State it plainly and it is already familiar: that is create-if-not-exists, the exact conditional put the engine commits with in Module 9. So the PROPOSED WAL archive needs no new fence — a retried or raced archive resolves by the same put failing, and the WAL-archive manifest is the authoritative durable frontier.
/wal keyspace and the WAL-archive manifest Volume are PROPOSED in graft.pg-wal-archive.design.md (the chosen path is Arm A→B; the Graft-native archive is Arm C, deferred). What is real is the mechanism the design rests on — the create-if-not-exists fence the engine already commits with (Module 9, graft.design.md).archive_command, which PUTs the bytes to /wal under create-if-not-exists. A fresh segment is created and the manifest frontier advances; a duplicate name is refused (ConditionNotMatch) — no clobber, frontier unchanged./wal shelf is empty. Tap fresh segment: the conditional PUT finds nothing at that key, creates the object, and the manifest frontier advances. Then tap retry / duplicate — the same name PUT again — and watch the fence refuse it.What Postgres demands of an archive
PostgreSQL · Continuous ArchivingContinuous archiving is how a Postgres cluster earns arbitrary-point recovery: as each write-ahead log segment fills, the server runs the operator's archive_command to copy that segment somewhere durable; a restore then replays the base backup forward over the archived segments to a chosen point or timeline. The documentation is blunt about the contract the command must honour — it must report success only when the segment is safely stored, and it must not overwrite a pre-existing file of the same name with different content, because a retry, a crash between copy and acknowledgement, or a promoted standby re-archiving can all present the same segment name twice. If the second write clobbers the first with different bytes, replay reads a segment that does not match the history that produced it, and recovery is silently corrupt.
So the archive's hardest requirement is not throughput — it is a concurrency-and-idempotency rule: same name, same bytes, exactly once; a duplicate must be refused, not merged. Get that one rule right and the rest of archiving is bookkeeping; get it wrong and the failure surfaces only during a disaster, when the restore is already running.
That rule is the engine's fence
graft.pg-wal-archive.design.mdRead that requirement next to Module 9 and it is the same sentence. The engine's durable commit is a create-if-not-exists on the next object in the bucket — graft.design.md names it directly: OpenDAL's conditional put, surfaced as ConditionNotMatch, is the commit fence; the matching If-None-Match create-only PUT is the same rule the other way. “Create only if absent; reject if present” is precisely “archive only if this segment name is unwritten; refuse if it already exists.” A retried or raced archive_command therefore resolves itself: the first PUT creates the segment object, and every duplicate PUT comes back ConditionNotMatch and is treated as the harmless success the contract wants.
There is no clobber to guard against because the store will not admit the second write at all — the same property that keeps the engine's commit chain from forking (Module 9) keeps two copies of one WAL segment from ever disagreeing. The design records this as the fence coincidence: the archive's hardest concurrency requirement is satisfied by a mechanism the engine ships. No new fence is invented for the money's WAL — the same conditional put arbitrates both the engine's commits and the ledger's segments.
Where the bytes land, and where the truth lives
graft.pg-wal-archive.design.md · the-loop-closesThe fence settles whether a segment is admitted; two more PROPOSED pieces settle where the bytes go and what counts as recovered. WAL segments are opaque and do not deduplicate, so storing them as engine pages would waste the page model; instead they land as blobs in the same Tigris bucket under a new /wal/{cluster}/{tli}/… keyspace, sitting beside the engine's own /segments and /logs. The authority is not the pile of blobs but a small transactional WAL-archive manifest — one entry per segment: timeline, segment name, start and end WAL LSN, object key, checksum — held in a per-cluster Graft Volume and committed under the very same fence.
That manifest is the authoritative durable frontier: a restore reads it to verify a gap-free, single-timeline chain before it replays a byte, so the recovery cut can never run past what is actually committed. This is the same discipline the archive fold already uses on the available-first tier, where the watermark W is derived from the engine's committed frontier rather than guessed (the loop closes) — the cut follows the commit, never the other way round. The bytes are blobs; the truth is the committed manifest; the arbiter for both is the fence the engine was already built with.
References & sources
- Echo records
- graft.pg-wal-archive.design.md“Common mechanism” — the fence coincidence, bytes-as-blobs, the manifest, the /wal keyspace; Arm C
- graft.design.mdthe create-if-not-exists conditional put (ConditionNotMatch) as the commit fence; /segments + /logs
- the create-if-not-exists fence (Module 9)the fence the engine commits with, reused here for archive_command
- the loop closesthe watermark W derived from the engine's committed frontier
- External
- PostgreSQL · Continuous Archiving & PITRarchive_command idempotency / no-overwrite, WAL segments, timelines
- Tigris conditional writescreate-if-not-exists semantics
- If-None-Matchthe conditional header behind a create-only PUT