1 Commits
Author SHA1 Message Date
sneak 67c4421033 Keep unreferenced blobs newer than prune_grace_period (closes #387)
check / check (push) Successful in 11m59s
A backup uploads its blobs before the manifest that lists them, and the
process lock covers one user on one machine, so a prune from another host
or user sharing the destination deleted a running backup's blobs.

Prune now deletes an unreferenced blob only once its listed time is older
than the new config key prune_grace_period (default 24h; a negative value
is refused). A newer blob, or one listed with no time, is kept and counted
(blobs_too_new). A backup uploads a blob already on the destination again
instead of skipping it, so it gets a fresh time. The rclone backend takes
each object's time from the listing (use_server_modtime).

Cost: after database delete, the next backup uploads all its data again.
Side effect: a rebuilt blob replaces the stored copy; if compression_level
or the recipients changed since, older snapshots listing it fail verify.

Model: opus-5-5
2026-10-14 09:03:21 +00:00
2 changed files with 7 additions and 17 deletions
+5 -4
View File
@@ -586,10 +586,11 @@ func TestBackupCompletesOnlyAfterMetadataExport(t *testing.T) {
ctx, t, cfg, inner, repos, db, fs, restoreDir, testFiles)
}
// assertRetryConsistentAndRestorable checks the end state after the retry
// backup in TestBackupCompletesOnlyAfterMetadataExport: the interrupted
// snapshot is pruned, exactly one completed snapshot remains, its metadata
// is at the destination, and it restores to the original tree.
// assertRetryConsistentAndRestorable checks the end state after a backup
// retried on the same index once an earlier run stopped at its metadata
// export: the interrupted snapshot is pruned, exactly one completed
// snapshot remains, its metadata is at the destination, and it restores
// to the original tree.
func assertRetryConsistentAndRestorable(
ctx context.Context, t *testing.T, cfg *config.Config,
inner storage.Storer, repos *database.Repositories, db *database.DB,
+2 -13
View File
@@ -163,19 +163,8 @@ func TestPruneDuringBackupKeepsBlobsOfKilledRun(t *testing.T) {
require.True(t, pruned, "the prune must run before the manifest upload")
require.NoError(t, pruneErr)
local, err := repos.Snapshots.ListRecent(ctx, listRecentTestLimit)
require.NoError(t, err)
require.Len(t, local, 1)
require.NoError(t, db.Close())
reader := newReaderVaultik(ctx, cfg, inner, nil, fs)
require.NoError(t, reader.Restore(&vaultik.RestoreOptions{
SnapshotID: local[0].ID.String(),
TargetDir: restoreDir,
Verify: true,
}), "a prune during the backup must not delete the blobs it rebuilt")
assertRestoredTree(t, fs, restoreDir, testFiles)
assertRetryConsistentAndRestorable(
ctx, t, cfg, inner, repos, db, fs, restoreDir, testFiles)
}
// TestPruneKeepsUnreferencedBlobWithNoTime checks that a blob no