fix(core): identify Kopia snapshots by a stable identity in verify and delete - #74
Open
gabriele-wolfox wants to merge 5 commits into
Open
fix(core): identify Kopia snapshots by a stable identity in verify and delete#74gabriele-wolfox wants to merge 5 commits into
gabriele-wolfox wants to merge 5 commits into
Conversation
gabriele-wolfox
force-pushed
the
dev/cnp-9006
branch
2 times, most recently
from
August 11, 2026 13:06
de5fdfb to
e9b63b2
Compare
Tier1 backup verification resolved a backup to its Kopia snapshot manifest IDs and passed them to "kopia snapshot verify". Post-relay tier2 maintenance unpins those same snapshots, and "kopia snapshot pin --remove" rewrites a snapshot manifest under a new ID and deletes the old one, so an ID resolved a moment earlier could already be gone by the time verification ran. Kopia then reported "found 0 of the N requested snapshot IDs to verify", and klio turned that into a corruption failure with exit code 65, failing an intact backup. This is the intermittent WALRetentionQueueAwareness e2e failure, and the same misreport is reachable outside tests. Resolve backups to the root object ID of each snapshot instead. Root object IDs are a stable identity that the pin rewrite leaves untouched, and the server side already identifies the snapshots it unpins the same way. Directory and file roots need different flags, and a backup mixes both: pgdata and metadata are directory snapshots, while the control data file is snapshotted on its own. Passing a file root to --directory-id makes Kopia parse file content as a directory listing and report healthy data as corrupt, so the root entry type now decides the flag and an unknown type is an error rather than a guess. Also stop classifying an unresolved snapshot set as corruption. Such a result means nothing was verified, so it carries no evidence about repository integrity, and it is now returned as a retryable error. Errors about a missing object or blob keep failing as corruption. Assisted-by: Claude Signed-off-by: Gabriele Quaresima <gabriele.quaresima@enterprisedb.com>
Backup deletion listed a backup's snapshots and then deleted them by manifest ID, which a concurrent "kopia snapshot pin" can rewrite in between. Deleting a replaced ID matches nothing and fails with "no snapshots matched", so the deletion reports an error and the backup survives. Resolve the snapshots again and retry, up to three attempts. Each attempt lists the current IDs, and snapshots removed by an earlier attempt are no longer listed, so the loop converges without deleting anything twice. Deleting by root object ID, the identity used for verification, would be wrong here: unchanged content dedupes to the same root across backups, and Kopia deletes every snapshot matching the ID it is given. Two backups of an idle tablespace share a root object, so deleting one backup by root ID would take the other backup's snapshot with it. The delete loop moved behind a narrow snapshotStore interface so the retry behaviour is covered by unit tests without reworking how Connection holds its Kopia client. Assisted-by: Claude Signed-off-by: Gabriele Quaresima <gabriele.quaresima@enterprisedb.com>
The WAL retention feature already drives three backups through a full maintenance pass, which unpins their snapshots and so rewrites every snapshot manifest under a new ID. Verifying the newest backup at the end of the feature asserts that verification still resolves it afterwards, and exercises that resolution against a real backup: each backup mixes a file root (the control data file, snapshotted on its own) with directory roots (pgdata and metadata), which need different Kopia flags. Only the newest backup is verified, because "klio backup list" spans both tiers and so also reports the backup the feature deletes, which no longer has tier1 snapshots. Also drop the tracker references from the test comments added with the fix, and describe the failure mode instead. Assisted-by: Claude Signed-off-by: Gabriele Quaresima <gabriele.quaresima@enterprisedb.com>
Add a section on when to identify a snapshot by manifest ID and when by root object ID. A manifest ID is not stable, since "kopia snapshot pin" rewrites it, so reads that must survive concurrent maintenance use the root object ID, while deletions must not: unchanged content dedupes to the same root across backups and Kopia deletes every snapshot matching the ID it is given. Also record that directory and file roots take different verify flags, that a verify result reporting only unresolved snapshot IDs is not corruption, and correct the existing note about a stale manifest ID: a delete of a rewritten ID matches nothing and fails rather than deleting the wrong snapshot. Assisted-by: Claude Signed-off-by: Gabriele Quaresima <gabriele.quaresima@enterprisedb.com>
The unresolved-snapshot verify classification can never trigger: once buildVerifyArgs only emits --directory-id/--file-id flags, Kopia never produces the "requested snapshot IDs to verify" error it matched. Removed it; classifyVerifyError again treats any errorCount > 0 as corruption. Also: - verifySpecificBackups no longer aborts every backup when one fails to resolve; it joins the error and still verifies the rest. - DeleteBackup now waits between retry attempts instead of retrying immediately, matching the backoff already used for the same pin-rewrite race elsewhere. - AGENTS.md no longer calls the tier1 unpin a safe root-ID read: unpinning by root ID mutates every snapshot sharing that root, same as delete. It stays only because the step is best-effort. - Reworded the e2e verification step to say it exercises root-object-ID resolution, not the rewrite race itself. Assisted-by: Claude Signed-off-by: Gabriele Quaresima <gabriele.quaresima@enterprisedb.com>
gabriele-wolfox
force-pushed
the
dev/cnp-9006
branch
from
August 11, 2026 14:49
e9b63b2 to
2410b28
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
WALRetentionQueueAwarenessfails intermittently withbackup verification detected corruption: exit status 65on a backup that is intact.Post-relay tier2 maintenance unpins the snapshots of the backup just taken, and
kopia snapshot pin --removerewrites each manifest under a new ID. Verification resolves the manifest IDs and uses them a moment later, so a rewrite in between makes Kopia reportfound 0 of the N requested snapshot IDs to verify, which klio classified as corruption. The observed gap between resolution and rewrite was around 150 ms, hence the flake. The same misreport is reachable outside tests: any backup whose tier1 verification overlaps the unpin of its own snapshots can be failed as corrupt, and exit 65 is non-retryable.Changes
(the control data file is snapshotted on its own), so the root entry type picks the flag.
or blob still fail as corruption.
Testing
golangci-lintclean on both modules; 15 new unit subtests.WALRetentionQueueAwarenesspasses locally on kind.Assisted-by: Claude
Closes #73