Visitar URL original
fix(backup): only list and restore backups complete on their tier by gabriele-wolfox · Pull Request #495 · cloudnative-pg/klio · GitHub
Skip to content

fix(backup): only list and restore backups complete on their tier - #495

Open
gabriele-wolfox wants to merge 18 commits into
mainfrom
dev/485
Open

gabriele-wolfox wants to merge 18 commits into
mainfrom
dev/485

Conversation

@gabriele-wolfox

@gabriele-wolfox gabriele-wolfox commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

A backup is listed only when all of its snapshots (pgdata, control data and every tablespace) are present on the tier. A backup still being relayed to tier2, or partially deleted, is no longer listed or restorable, and no longer satisfies the tier2 sync guard that allows deleting the tier1 copy.

Retention applies the policy to complete backups only, and deletes an incomplete backup once it is older than every kept one. WAL retention still counts incomplete backups, so a backup being relayed keeps its WALs.

Adds the IncompleteBackup e2e test. On Kind it passes on this branch and fails on main, where the incomplete backup is still listed.

Assisted-by: Claude

Fixes #485

@gabriele-wolfox
gabriele-wolfox requested a review from a team as a code owner October 23, 2026 14:17
@fcanovai
fcanovai force-pushed the dev/485 branch 6 times, most recently from 0f566fe to bc29960 Compare October 30, 2026 12:10
@fcanovai

Copy link
Copy Markdown
Contributor

I've pushed a set of changes.

The difference between orphan and incomplete backups seemed to complex. Now everything is considered incomplete. For visibility, however, incomplete backups have a status:

  • pending: the latest tier1 attempt, which may still be uploading.
  • recoverable: incomplete on tier2 but complete on tier1, so the next relay migrates it.
  • unrecoverable: incomplete on tier2 and on tier1.

They are logged at each sweep, exported as the klio.server.backup.incomplete_backups gauge, shown on a new Grafana panel, and listed by klio admin list-backups --incomplete.

Additionally, with the existing code we could have tier2 orphans that were still being relayed (e.g.: retrying a failed snapshot migration), and that could have happened at every retry if the backup was large enough. Now the retention sweeper no longer deletes incomplete backups. Unrecoverable ones could be deleted, but we may want them for debugging purposes for now, so the decision to remove them with can be deferred.

@leonardoce
leonardoce force-pushed the dev/485 branch 2 times, most recently from 7aa8a5f to 7bcd980 Compare November 12, 2026 15:42
gabriele-wolfox and others added 18 commits November 12, 2026 16:48
A backup was listed as soon as its metadata snapshot existed, so one
still being relayed to tier2, or partially deleted, could be restored
and satisfied the sweeper's tier2 sync guard.

A backup is now complete on a tier only when its pgdata, control data
and tablespaces all have a snapshot there. Listing, restore, metrics and
the retention policy use complete backups only. An incomplete backup
older than every kept one is deleted. WAL retention still counts
incomplete backups, so their WALs are kept while they are relayed.

Assisted-by: Claude
Signed-off-by: Gabriele Quaresima <gabriele.quaresima@enterprisedb.com>
An incomplete backup was reported as a NoBackupFoundError carrying the
missing parts. Report it as an IncompleteBackupError instead, which
unwraps to NoBackupFoundError so MultiConnection still falls back to
tier2.

Without tier2, MultiConnection now returns the tier1 error rather than a
fresh not-found one, so the missing parts reach the user. It also no
longer shadows the tier1 error, which made a tier1 failure read as
`backup "" for host "" not found`.

Assisted-By: Claude
Signed-off-by: Francesco Canovai <francesco.canovai@enterprisedb.com>
ListBackupsByCompleteness already returns the same backups, split into
complete and incomplete ones. WAL retention now joins the two lists
instead of using a dedicated listing method.

Assisted-By: Claude
Signed-off-by: Francesco Canovai <francesco.canovai@enterprisedb.com>
The listBackups function took a flag to return only the complete backups and, in
that mode, to skip reading the metadata of backups missing their pgdata
or control data. It now always returns both lists, and ListBackups drops
the incomplete one. This costs one extra metadata read per incomplete
backup, which is rare.

Assisted-By: Claude
Signed-off-by: Francesco Canovai <francesco.canovai@enterprisedb.com>
The backup metrics now walk each cluster's metadata snapshots in end
time order to find the latest and oldest complete backup, so the latest
and oldest IDs tracked per group were no longer read. Group the
snapshots by source, sorted by end time, and count them with len.

Assisted-By: Claude
Signed-off-by: Francesco Canovai <francesco.canovai@enterprisedb.com>
The backups metric checked only the pgdata and control data of each
backup, because the tablespaces are listed in the metadata and only the
latest and oldest metadata snapshots were restored. A backup missing a
tablespace was counted, while ListBackups does not list it.

Restore the metadata of every backup and check it like ListBackups does.
The collector keeps the metadata by snapshot ID, as a snapshot never
changes, so each backup is restored once rather than on every pass. A
backup whose metadata cannot be read is skipped and retried on the next
pass, instead of dropping the series of its whole cluster.

Assisted-By: Claude
Signed-off-by: Francesco Canovai <francesco.canovai@enterprisedb.com>
The upload tags a tablespace snapshot with the base name of the
tablespace, and the completeness check expects the same, but the
restore looked it up with the full name. A tablespace whose name
contains a slash could then be listed as complete but not restored.
Compute the tag value with one helper in all three places.

Assisted-By: Claude
Signed-off-by: Francesco Canovai <francesco.canovai@enterprisedb.com>
Assisted-By: Claude
Signed-off-by: Francesco Canovai <francesco.canovai@enterprisedb.com>
After deleting the pgdata snapshot, check that the metadata snapshot is
still there on both tiers. The backup is then incomplete rather than
gone, and the final check that retention deleted its metadata cannot
pass for a lookup that never matched.

Assisted-By: Claude
Signed-off-by: Francesco Canovai <francesco.canovai@enterprisedb.com>
The metrics collector had its own copy of the metadata reader and of the
interface it needs. Export them from the Kopia backup client and reuse
them.

Assisted-By: Claude
Signed-off-by: Francesco Canovai <francesco.canovai@enterprisedb.com>
Assisted-By: Claude
Signed-off-by: Francesco Canovai <francesco.canovai@enterprisedb.com>
The retention sweeper deleted backup attempts that never produced a
metadata snapshot, once a later backup of the same cluster completed.
It now leaves every incomplete backup in place and reports it, with a
status telling whether it can still complete: PENDING (the latest
tier1 attempt, possibly still uploading), RECOVERABLE (on tier2,
complete on tier1, rebuilt by the next relay) or UNRECOVERABLE (to be
deleted by hand).

The incomplete backups are logged at each sweep, exported as the
klio.server.backup.incomplete_backups gauge, shown on a new Grafana
dashboard panel, and listed by `klio admin list-backups --incomplete`.

Assisted-by: Claude
Signed-off-by: Francesco Canovai <francesco.canovai@enterprisedb.com>
Manifest.StartTime is a UTCTimestamp, but the backup grouping code
still treated it as a string, so the package did not compile. Keep
the start time as a UTCTimestamp while grouping and format it as
RFC3339 for the reported StartedAt.

Assisted-By: Claude
Signed-off-by: Francesco Canovai <francesco.canovai@enterprisedb.com>
The wait for no incomplete backups compared the empty list the server
returns with a nil slice using reflect.DeepEqual, which tells them
apart, so it could never succeed. Treat two empty lists as equal.

Assisted-By: Claude
Signed-off-by: Francesco Canovai <francesco.canovai@enterprisedb.com>
GetMetadata restored Kopia checkpoints of an interrupted metadata upload as
if they were finished, while the backup scan already ignored them. Share
one definition of a finished metadata snapshot between the two.

Assisted-by: Claude
Signed-off-by: Gabriele Quaresima <gabriele.quaresima@enterprisedb.com>
Assisted-by: Claude
Signed-off-by: Gabriele Quaresima <gabriele.quaresima@enterprisedb.com>
Retention no longer deletes incomplete backups. Fix the retention section
of the plugin configuration page and add an upgrade note.

Assisted-by: Claude
Signed-off-by: Gabriele Quaresima <gabriele.quaresima@enterprisedb.com>
Signed-off-by: Leonardo Cecchi <leonardo.cecchi@enterprisedb.com>

This branch has not been deployed

No deployments
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.

Backup listing includes backups not fully present on their tier

3 participants