Skip to content

Commit 4731465

Browse files
committed
backup: fix elide skipped layers to account for new incremental path suffix
Due to the changes introduced in #143226, the order of the backup manifests passed to `ElideSkippedLayers` is now changed. Whereas previously a compacted backup would always appear *after* any backup with the same end time, compacted backups now appear *before*. This meant that during restore, compacted backups ended up always being skipped and never used. To fix this, prior to running the `ElideSkippedLayers` algorithm, we now remove duplicate backups (i.e. same end time) and keep the backup with the earliest start time (aka the most compacted backup). Epic: None Release note: None
1 parent 1b96d8c commit 4731465

File tree

2 files changed

+118
-13
lines changed

2 files changed

+118
-13
lines changed

pkg/backup/backupinfo/manifest_handling.go

Lines changed: 80 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -887,11 +887,19 @@ func ValidateEndTimeAndTruncate(
887887
endTime hlc.Timestamp,
888888
includeSkipped bool,
889889
) ([]string, []backuppb.BackupManifest, []jobspb.RestoreDetails_BackupLocalityInfo, error) {
890+
890891
if endTime.IsEmpty() {
891892
if includeSkipped {
892893
return defaultURIs, mainBackupManifests, localityInfo, nil
893894
}
894-
return ElideSkippedLayers(defaultURIs, mainBackupManifests, localityInfo)
895+
uris, manifests, locality, err := ElideSkippedLayers(defaultURIs, mainBackupManifests, localityInfo)
896+
if err != nil {
897+
return nil, nil, nil, err
898+
}
899+
if err := validateContinuity(manifests, endTime); err != nil {
900+
return nil, nil, nil, err
901+
}
902+
return uris, manifests, locality, nil
895903
}
896904
for i, b := range mainBackupManifests {
897905
// Find the backup that covers the requested time.
@@ -930,7 +938,16 @@ func ValidateEndTimeAndTruncate(
930938
if includeSkipped {
931939
return defaultURIs[:i+1], mainBackupManifests[:i+1], localityInfo[:i+1], nil
932940
}
933-
return ElideSkippedLayers(defaultURIs[:i+1], mainBackupManifests[:i+1], localityInfo[:i+1])
941+
uris, manifests, locality, err := ElideSkippedLayers(
942+
defaultURIs[:i+1], mainBackupManifests[:i+1], localityInfo[:i+1],
943+
)
944+
if err != nil {
945+
return nil, nil, nil, err
946+
}
947+
if err := validateContinuity(manifests, endTime); err != nil {
948+
return nil, nil, nil, err
949+
}
950+
return uris, manifests, locality, nil
934951

935952
}
936953

@@ -939,10 +956,42 @@ func ValidateEndTimeAndTruncate(
939956
)
940957
}
941958

942-
// ElideSkippedLayers removes backups that are skipped in the backup chain.
959+
// ValidateContinuity checks that the backups are continuous and cover the
960+
// requested end time.
961+
func validateContinuity(manifests []backuppb.BackupManifest, endTime hlc.Timestamp) error {
962+
if len(manifests) == 0 {
963+
return errors.AssertionFailedf("an empty chain of backups cannot cover an end time")
964+
}
965+
for i := range len(manifests) - 1 {
966+
if !manifests[i].EndTime.Equal(manifests[i+1].StartTime) {
967+
return errors.AssertionFailedf(
968+
"backups are not continuous: %dth backup ends at %+v, %dth backup starts at %+v",
969+
i, manifests[i].EndTime,
970+
i+1, manifests[i+1].StartTime,
971+
)
972+
}
973+
}
974+
if !endTime.IsEmpty() {
975+
lastManifest := manifests[len(manifests)-1]
976+
if !lastManifest.StartTime.Less(endTime) || !endTime.LessEq(lastManifest.EndTime) {
977+
return errors.AssertionFailedf(
978+
"requested time %s is not covered by the last backup",
979+
endTime,
980+
)
981+
}
982+
}
983+
return nil
984+
}
985+
986+
// ElideSkippedLayers removes backups that are skipped in the backup chain and
987+
// ensures only backups that will be used in the restore are returned.
988+
//
989+
// Note: This assumes that the provided backups are sorted in increasing order
990+
// by end time, and then sorted in increasing order by start time to break ties.
943991
func ElideSkippedLayers(
944992
uris []string, backups []backuppb.BackupManifest, loc []jobspb.RestoreDetails_BackupLocalityInfo,
945993
) ([]string, []backuppb.BackupManifest, []jobspb.RestoreDetails_BackupLocalityInfo, error) {
994+
uris, backups, loc = elideDuplicateEndTimes(uris, backups, loc)
946995
i := len(backups) - 1
947996
for i > 0 {
948997
// Find j such that backups[j] is parent of backups[i].
@@ -959,10 +1008,37 @@ func ElideSkippedLayers(
9591008
// Move up to check the chain from j now.
9601009
i = j
9611010
}
962-
9631011
return uris, backups, loc, nil
9641012
}
9651013

1014+
// elideDuplicateEndTimes ensures that backups in a list of backups are
1015+
// functionally unique by removing any duplicates that have the same end time,
1016+
// choosing backups with earlier start times and eliding the rest.
1017+
//
1018+
// Note: This assumes that the provided backups are sorted in increasing order
1019+
// by end time, and then sorted in increasing order by start time to break ties.
1020+
// This is the case for backups being returned by storage clients due to us
1021+
// encoding backup paths in a way that ensures this order.
1022+
func elideDuplicateEndTimes(
1023+
uris []string, backups []backuppb.BackupManifest, loc []jobspb.RestoreDetails_BackupLocalityInfo,
1024+
) ([]string, []backuppb.BackupManifest, []jobspb.RestoreDetails_BackupLocalityInfo) {
1025+
for i := range len(backups) - 1 {
1026+
j := i + 1
1027+
// Find j such that backups[j] no longer shares the same end time as
1028+
// backups[i].
1029+
for j < len(backups) && backups[i].EndTime.Equal(backups[j].EndTime) {
1030+
j++
1031+
}
1032+
// If there exists backups between i and j, remove them.
1033+
if j > i+1 {
1034+
uris = slices.Delete(uris, i+1, j)
1035+
backups = slices.Delete(backups, i+1, j)
1036+
loc = slices.Delete(loc, i+1, j)
1037+
}
1038+
}
1039+
return uris, backups, loc
1040+
}
1041+
9661042
// GetBackupIndexAtTime returns the index of the latest backup in
9671043
// `backupManifests` with a StartTime >= asOf.
9681044
func GetBackupIndexAtTime(

pkg/backup/backupinfo/manifest_handling_test.go

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -379,18 +379,46 @@ func TestMakeBackupCodec(t *testing.T) {
379379
func TestElideSkippedLayers(t *testing.T) {
380380
defer leaktest.AfterTest(t)()
381381

382+
// Note: The tests here work under the assumption that the input lists are
383+
// always sorted in ascending order by end time, and then sorted in ascending
384+
// order by start time.
382385
for _, tc := range []struct {
383386
name string
384387
times [][]int // len 2 slices of start and end time.
385-
expected []int // expected end times.
388+
expected [][]int // expected start and end times
386389
}{
387-
{"single", [][]int{{0, 1}}, []int{1}},
388-
{"double", [][]int{{0, 1}, {1, 2}}, []int{1, 2}},
389-
{"simple chain", [][]int{{0, 1}, {1, 2}, {2, 3}, {3, 5}, {5, 8}}, []int{1, 2, 3, 5, 8}},
390-
{"skip one", [][]int{{0, 1}, {1, 2}, {1, 3}, {3, 5}, {5, 8}}, []int{1, 3, 5, 8}},
391-
{"skip all", [][]int{{0, 1}, {1, 2}, {1, 3}, {3, 5}, {1, 8}}, []int{1, 8}},
392-
{"skip twice to first", [][]int{{0, 1}, {1, 2}, {1, 3}, {3, 5}, {3, 8}}, []int{1, 3, 8}},
393-
{"skip twice to second", [][]int{{0, 1}, {1, 2}, {1, 3}, {3, 5}, {2, 8}}, []int{1, 2, 8}},
390+
{"single", [][]int{{0, 1}}, [][]int{{0, 1}}},
391+
{"double", [][]int{{0, 1}, {1, 2}}, [][]int{{0, 1}, {1, 2}}},
392+
{
393+
"simple chain, no skips",
394+
[][]int{{0, 1}, {1, 2}, {2, 3}, {3, 5}, {5, 8}},
395+
[][]int{{0, 1}, {1, 2}, {2, 3}, {3, 5}, {5, 8}},
396+
},
397+
{
398+
"compaction of two backups",
399+
[][]int{{0, 1}, {1, 2}, {1, 3}, {2, 3}, {3, 5}, {5, 8}},
400+
[][]int{{0, 1}, {1, 3}, {3, 5}, {5, 8}},
401+
},
402+
{
403+
"compaction of entire chain",
404+
[][]int{{0, 1}, {1, 2}, {2, 3}, {3, 5}, {0, 8}, {5, 8}},
405+
[][]int{{0, 8}},
406+
},
407+
{
408+
"two compactions of two backups",
409+
[][]int{{0, 1}, {1, 2}, {1, 3}, {2, 3}, {3, 5}, {3, 8}, {5, 8}},
410+
[][]int{{0, 1}, {1, 3}, {3, 8}},
411+
},
412+
{
413+
"compaction includes a compacted backup in the middle",
414+
[][]int{{0, 1}, {1, 2}, {1, 3}, {2, 3}, {3, 5}, {1, 8}, {5, 8}},
415+
[][]int{{0, 1}, {1, 8}},
416+
},
417+
{
418+
"two compactions with the same end time",
419+
[][]int{{0, 1}, {1, 2}, {2, 3}, {3, 5}, {1, 8}, {3, 8}, {5, 8}},
420+
[][]int{{0, 1}, {1, 8}},
421+
},
394422
} {
395423
t.Run(tc.name, func(t *testing.T) {
396424
chain := make([]backuppb.BackupManifest, len(tc.times))
@@ -408,7 +436,8 @@ func TestElideSkippedLayers(t *testing.T) {
408436
require.Equal(t, len(tc.expected), len(locs))
409437
require.Equal(t, len(tc.expected), len(res))
410438
for i := range tc.expected {
411-
require.Equal(t, tc.expected[i], int(res[i].EndTime.WallTime), "expected %q\ngot: %q")
439+
actual := []int{int(res[i].StartTime.WallTime), int(res[i].EndTime.WallTime)}
440+
require.Equal(t, tc.expected[i], actual)
412441
}
413442
})
414443
}

0 commit comments

Comments
 (0)