Skip to content

feat: create pgbackrest stanza during WAL archiving when missing - #121

Merged
Agalin merged 3 commits into
operasoftware:mainfrom
melancholictheory:feat/wal-stanza-bootstrap
Aug 12, 2026
Merged

feat: create pgbackrest stanza during WAL archiving when missing#121
Agalin merged 3 commits into
operasoftware:mainfrom
melancholictheory:feat/wal-stanza-bootstrap

Conversation

@melancholictheory

Copy link
Copy Markdown
Contributor

What

Create the pgBackRest stanza from the WAL archive path when it does not exist yet, so continuous WAL archiving can start without waiting for the first backup.

Why

Today pgbackrest stanza-create runs only inside the Backup RPC (internal/cnpgi/instance/backup.go). The WAL archive path only checks whether the stanza exists (pgbackrest info) and never creates it. So on a fresh cluster, or after a major upgrade changes the repository path, archive-push keeps failing with HINT: has a stanza-create been performed? until a backup happens to run. Until then continuous archiving stays down, and a rejoining replica can hang on archive recovery.

This is the known limitation discussed in #60, and it is also what #18 runs into when an immediate backup races the plugin becoming ready on the primary.

How

  • CheckWalArchiveDestination now returns the parsed pgbackrest info catalog instead of only an error, so callers can inspect the stanza status. The single info call still doubles as the repository reachability check.
  • The catalog now parses the per-stanza status.code, with a StanzaMissing() helper (code 1 = "missing stanza path").
  • In the WAL Archive handler, when the stanza is missing, it runs the existing idempotent CreatePgbackrestStanza before archive-push.

Why this placement:

  • It runs on the primary as soon as that instance's sidecar is up, independent of any ScheduledBackup timing.
  • WAL archiving already retries. PostgreSQL re-runs archive_command until it succeeds, so no extra retry loop is added.
  • Stanza creation is best-effort and only triggers when the stanza is genuinely missing (no backup has created it yet), so it does not contend with a running backup for the stanza lock. This addresses the lock concern in the old code comment and in Error appends using ScheduleBackup : ERROR: [050]: unable to acquire lock on file #21.
  • It covers the major-upgrade case (Support Major Version Upgrade of Postgres #42): after the repository path changes, the next WAL push sees no stanza and recreates it.

Testing

  • go build ./... and go vet ./... are clean.
  • New unit tests for StanzaMissing() (status codes 0 and 1); existing catalog and archiver tests pass.
  • Not yet validated end-to-end on a live cluster. Opening as a draft for feedback on the approach, in particular the best-effort lock handling.

Refs #60, #18, #42.

Stanza creation previously ran only inside the Backup RPC, so on a fresh
cluster (or after a major upgrade changes the repository path) WAL archiving
stayed broken until the first backup happened to run: archive-push kept
failing with "has a stanza-create been performed?", and a rejoining replica
could hang on archive recovery.

The WAL archive path now inspects the "pgbackrest info" status and, when the
stanza is missing (status code 1), runs the existing idempotent
CreatePgbackrestStanza before archive-push. This executes on the primary as
soon as its sidecar is up and relies on PostgreSQL's own archive_command
retry, so archiving comes up without waiting for a backup. Creation is
best-effort and only triggers when the stanza is genuinely absent, so it does
not contend with a running backup for the stanza lock.

The stanza check lives entirely in the WAL archive path and does not touch
CheckWalArchiveDestination, which the restore path uses for a read-only check.

Refs operasoftware#60, operasoftware#18, operasoftware#42.

Signed-off-by: Vasiliy Fakunin <61789920+melancholictheory@users.noreply.github.com>
@melancholictheory
melancholictheory force-pushed the feat/wal-stanza-bootstrap branch from fb5437a to daac990 Compare July 30, 2026 06:26
melancholictheory pushed a commit to melancholictheory/cnpg-plugin-pgbackrest that referenced this pull request Aug 3, 2026
Covers lazy stanza creation on first WAL archive, successful archiving
before any backup, then backup and restore. Log assertion matches operasoftware#121.

Co-authored-by: Cursor <cursoragent@cursor.com>
Covers lazy stanza creation on first WAL archive, successful archiving
before any backup, then backup and restore. Log assertion matches operasoftware#121.

Co-authored-by: Cursor <cursoragent@cursor.com>
Signed-off-by: Vasiliy Fakunin <61789920+melancholictheory@users.noreply.github.com>
@melancholictheory
melancholictheory force-pushed the feat/wal-stanza-bootstrap branch 2 times, most recently from da130a7 to 1e9ede0 Compare August 3, 2026 21:13

@Agalin Agalin left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm fine with the default change although we'll need to mark it as a breaking change.

Comment thread internal/cnpgi/common/wal.go Outdated
Comment thread test/e2e/internal/tests/walarchive/wal_archive_no_backup.go Outdated
Comment thread internal/pgbackrest/api/config.go
Comment thread test/e2e/internal/tests/walarchive/wal_archive_stanza.go
Add a createStanza policy to the Archive configuration that controls when
the pgBackRest stanza is created:

- OnFirstArchive (default): create it on the first WAL archive if missing,
  so archiving works without a prior backup.
- OnBackup: create it only when a backup runs (the previous behavior).
- Disabled: never create it automatically; the stanza is managed out of band.

The WAL archive path creates the stanza only under OnFirstArchive; the backup
path creates it unless Disabled. This keeps archiving working out of the box
while giving a backward-compatible opt-out, as discussed in operasoftware#60.

The WAL archive handler relies on CheckWalArchiveDestination returning a typed
ErrStanzaMissing rather than inspecting the info catalog itself, which keeps the
restore path's intent clear. The e2e coverage exercises both the default and the
OnBackup policy.

BREAKING CHANGE: WAL archiving now creates the pgBackRest stanza on the first WAL
archive by default (createStanza=OnFirstArchive) instead of only during the first
backup. Set createStanza=OnBackup to keep the previous behavior.

Refs operasoftware#60.

Signed-off-by: Vasiliy Fakunin <61789920+melancholictheory@users.noreply.github.com>
@melancholictheory
melancholictheory force-pushed the feat/wal-stanza-bootstrap branch from 1e9ede0 to 090af96 Compare August 5, 2026 17:35
@melancholictheory

Copy link
Copy Markdown
Contributor Author

Addressed all four in 090af96:

  • CheckWalArchiveDestination now returns a typed ErrStanzaMissing; the WAL handler reacts to it, and restore just tolerates it, so restore behavior is unchanged.
  • Dropped the restore step from the WAL test and renamed the file to wal_archive_stanza.go.
  • Added an OnBackup e2e that asserts archiving stays failing until a backup creates the stanza.
  • Marked the default change as breaking (feat! + BREAKING CHANGE).

@Agalin Agalin left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me. If CI passes the only thing left wikk be marking as ready. 🙂

@melancholictheory
melancholictheory marked this pull request as ready for review August 5, 2026 19:37
@Agalin
Agalin merged commit 9962568 into operasoftware:main Aug 12, 2026
1 check passed
Agalin pushed a commit that referenced this pull request Aug 12, 2026
Covers lazy stanza creation on first WAL archive, successful archiving
before any backup, then backup and restore. Log assertion matches #121.

Co-authored-by: Cursor <cursoragent@cursor.com>
Signed-off-by: Vasiliy Fakunin <61789920+melancholictheory@users.noreply.github.com>
@melancholictheory
melancholictheory deleted the feat/wal-stanza-bootstrap branch August 12, 2026 18:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants