Skip to content

Commit 8a95227

Browse files
committed
backup: add generics to ValidateEndTimeAndTruncate
This patch updates ValidateEndTimeAndTruncate to use generics instead of the zipped BackupEntry. This allows us to pass in anything that conforms to the `ManifestLike` interface, i.e. `BackupIndexMetadata` so that we can run the validation and truncation logic on other types. Epic: CRDB-47942 Release note: None
1 parent 4595a31 commit 8a95227

File tree

3 files changed

+119
-81
lines changed

3 files changed

+119
-81
lines changed

pkg/backup/backupdest/backup_destination.go

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -688,12 +688,18 @@ func ResolveBackupManifests(
688688

689689
totalMemSize := ownedMemSize
690690
ownedMemSize = 0
691-
validatedDefaultURIs, validatedMainBackupManifests, validatedLocalityInfo, err := backupinfo.ValidateEndTimeAndTruncate(
692-
defaultURIs, mainBackupManifests, localityInfo, endTime, includeSkipped, includeCompacted,
691+
manifestEntries, err := backupinfo.ZipBackupTreeEntries(
692+
defaultURIs, mainBackupManifests, localityInfo,
693+
)
694+
if err != nil {
695+
return nil, nil, nil, 0, err
696+
}
697+
validatedEntries, err := backupinfo.ValidateEndTimeAndTruncate(
698+
manifestEntries, endTime, includeSkipped, includeCompacted,
693699
)
694-
695700
if err != nil {
696701
return nil, nil, nil, 0, err
697702
}
698-
return validatedDefaultURIs, validatedMainBackupManifests, validatedLocalityInfo, totalMemSize, nil
703+
uris, manifests, localityInfo := backupinfo.UnzipBackupTreeEntries(validatedEntries)
704+
return uris, manifests, localityInfo, totalMemSize, nil
699705
}

pkg/backup/backupinfo/manifest_handling.go

Lines changed: 104 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -875,6 +875,7 @@ func GetLocalityInfo(
875875
return info, nil
876876
}
877877

878+
// TODO (kev-cao): Remove in v26.2
878879
type BackupTreeEntry struct {
879880
// The URI to the backup manifest.
880881
Uri string
@@ -884,11 +885,45 @@ type BackupTreeEntry struct {
884885
LocalityInfo jobspb.RestoreDetails_BackupLocalityInfo
885886
}
886887

887-
// zipBackupTreeEntries zips the default URIs, main backup manifests, and
888+
func (b BackupTreeEntry) Start() hlc.Timestamp {
889+
return b.Manifest.StartTime
890+
}
891+
892+
func (b BackupTreeEntry) End() hlc.Timestamp {
893+
return b.Manifest.EndTime
894+
}
895+
896+
func (b BackupTreeEntry) Compacted() bool {
897+
return b.Manifest.IsCompacted
898+
}
899+
900+
func (b BackupTreeEntry) MVCC() backuppb.MVCCFilter {
901+
return b.Manifest.MVCCFilter
902+
}
903+
904+
func (b BackupTreeEntry) RevisionStart() hlc.Timestamp {
905+
return b.Manifest.RevisionStartTime
906+
}
907+
908+
// This interface is used as a stop-gap during the transitional period where
909+
// some restorable backups will not have backup indexes and some will.
910+
// Afterwards, we can remove this interface and replace instances of
911+
// ManifestLike with BackupIndexMetadata.
912+
// TODO (kev-cao): Remove in v26.2
913+
type ManifestLike interface {
914+
Start() hlc.Timestamp
915+
End() hlc.Timestamp
916+
Compacted() bool
917+
MVCC() backuppb.MVCCFilter
918+
RevisionStart() hlc.Timestamp
919+
}
920+
921+
// ZipBackupTreeEntries zips the default URIs, main backup manifests, and
888922
// locality information into a slice of backupChainEntry structs. It assumes
889923
// that the passed in slices are of the same length and that each entry at the
890924
// same index in each slice corresponds to the same backup.
891-
func zipBackupTreeEntries(
925+
// TODO (kev-cao): Remove in v26.2
926+
func ZipBackupTreeEntries(
892927
defaultURIs []string,
893928
mainBackupManifests []backuppb.BackupManifest,
894929
localityInfo []jobspb.RestoreDetails_BackupLocalityInfo,
@@ -912,10 +947,11 @@ func zipBackupTreeEntries(
912947
return entries, nil
913948
}
914949

915-
// unzipBackupTreeEntries unzips a slice of backupChainEntry structs into
950+
// UnzipBackupTreeEntries unzips a slice of backupChainEntry structs into
916951
// slices of their constituent parts: default URIs, main backup manifests, and
917952
// locality info and returns the individual slices.
918-
func unzipBackupTreeEntries(
953+
// TODO (kev-cao): Remove in v26.2
954+
func UnzipBackupTreeEntries(
919955
entries []BackupTreeEntry,
920956
) ([]string, []backuppb.BackupManifest, []jobspb.RestoreDetails_BackupLocalityInfo) {
921957
defaultURIs := make([]string, len(entries))
@@ -935,85 +971,70 @@ func unzipBackupTreeEntries(
935971
// The method also performs additional sanity checks to ensure the backups cover
936972
// the requested time.
937973
//
938-
// TODO (kev-cao): Refactor this function to accept/return the BackupTreeEntry
939-
// type and update surrounding caller logic to use it. This will allow us to
940-
// refactor out the zipping and unzipping logic.
941-
func ValidateEndTimeAndTruncate(
942-
defaultURIs []string,
943-
mainBackupManifests []backuppb.BackupManifest,
944-
localityInfo []jobspb.RestoreDetails_BackupLocalityInfo,
945-
endTime hlc.Timestamp,
946-
includeSkipped bool,
947-
includeCompacted bool,
948-
) ([]string, []backuppb.BackupManifest, []jobspb.RestoreDetails_BackupLocalityInfo, error) {
949-
backupEntries, err := zipBackupTreeEntries(defaultURIs, mainBackupManifests, localityInfo)
950-
if err != nil {
951-
return nil, nil, nil, err
952-
}
953-
974+
// Note: This function assumes that the manifests in the chain are sorted by end
975+
// time in ascending order and ties are broken by start time in ascending order.
976+
func ValidateEndTimeAndTruncate[E ManifestLike](
977+
manifests []E, endTime hlc.Timestamp, includeSkipped bool, includeCompacted bool,
978+
) ([]E, error) {
954979
if !includeCompacted {
955-
backupEntries = skipCompactedBackups(backupEntries)
980+
manifests = skipCompactedBackups(manifests)
956981
}
957982

958983
if endTime.IsEmpty() {
959984
if includeSkipped {
960-
uris, manifests, locality := unzipBackupTreeEntries(backupEntries)
961-
return uris, manifests, locality, nil
985+
return manifests, nil
962986
}
963-
backupEntries = elideSkippedLayers(backupEntries)
964-
if err := validateContinuity(backupEntries); err != nil {
965-
return nil, nil, nil, err
987+
manifests = elideSkippedLayers(manifests)
988+
if err := validateContinuity(manifests); err != nil {
989+
return nil, err
966990
}
967-
uris, manifests, locality := unzipBackupTreeEntries(backupEntries)
968-
return uris, manifests, locality, nil
991+
return manifests, nil
969992
}
970-
for i, b := range backupEntries {
993+
for i, b := range manifests {
971994
// Find the backup that covers the requested time.
972-
if !(b.Manifest.StartTime.Less(endTime) && endTime.LessEq(b.Manifest.EndTime)) {
995+
if !(b.Start().Less(endTime) && endTime.LessEq(b.End())) {
973996
continue
974997
}
975998

976999
// Ensure that the backup actually has revision history.
977-
if !endTime.Equal(b.Manifest.EndTime) {
978-
if b.Manifest.MVCCFilter != backuppb.MVCCFilter_All {
1000+
if !endTime.Equal(b.End()) {
1001+
if b.MVCC() != backuppb.MVCCFilter_All {
9791002
const errPrefix = "invalid RESTORE timestamp: restoring to arbitrary time requires that BACKUP for requested time be created with 'revision_history' option."
9801003
if i == 0 {
981-
return nil, nil, nil, errors.Errorf(
1004+
return nil, errors.Errorf(
9821005
errPrefix+" nearest backup time is %s",
983-
timeutil.Unix(0, b.Manifest.EndTime.WallTime).UTC(),
1006+
timeutil.Unix(0, b.End().WallTime).UTC(),
9841007
)
9851008
}
986-
return nil, nil, nil, errors.Errorf(
1009+
return nil, errors.Errorf(
9871010
errPrefix+" nearest BACKUP times are %s or %s",
988-
timeutil.Unix(0, mainBackupManifests[i-1].EndTime.WallTime).UTC(),
989-
timeutil.Unix(0, b.Manifest.EndTime.WallTime).UTC(),
1011+
timeutil.Unix(0, manifests[i-1].End().WallTime).UTC(),
1012+
timeutil.Unix(0, b.End().WallTime).UTC(),
9901013
)
9911014
}
9921015
// Ensure that the revision history actually covers the requested time -
9931016
// while the BACKUP's start and end might contain the requested time for
9941017
// example if start time is 0 (full backup), the revision history was
9951018
// only captured since the GC window. Note that the RevisionStartTime is
9961019
// the latest for ranges backed up.
997-
if endTime.LessEq(b.Manifest.RevisionStartTime) {
998-
return nil, nil, nil, errors.Errorf(
1020+
if endTime.LessEq(b.RevisionStart()) {
1021+
return nil, errors.Errorf(
9991022
"invalid RESTORE timestamp: BACKUP for requested time only has revision history"+
1000-
" from %v", timeutil.Unix(0, b.Manifest.RevisionStartTime.WallTime).UTC(),
1023+
" from %v", timeutil.Unix(0, b.RevisionStart().WallTime).UTC(),
10011024
)
10021025
}
10031026
}
10041027
if includeSkipped {
1005-
uris, manifests, locality := unzipBackupTreeEntries(backupEntries[:i+1])
1006-
return uris, manifests, locality, nil
1028+
return manifests[:i+1], nil
10071029
}
1008-
backupEntries = elideSkippedLayers(backupEntries[:i+1])
1009-
if err := validateContinuity(backupEntries); err != nil {
1010-
return nil, nil, nil, err
1030+
manifests = elideSkippedLayers(manifests[:i+1])
1031+
if err := validateContinuity(manifests); err != nil {
1032+
return nil, err
10111033
}
1012-
uris, manifests, locality := unzipBackupTreeEntries(backupEntries)
1013-
return uris, manifests, locality, nil
1034+
return manifests, nil
10141035
}
10151036

1016-
return nil, nil, nil, errors.Errorf(
1037+
return nil, errors.Errorf(
10171038
"invalid RESTORE timestamp: supplied backups do not cover requested time",
10181039
)
10191040
}
@@ -1022,26 +1043,30 @@ func ValidateEndTimeAndTruncate(
10221043
// backups and returns the updated list backup entries.
10231044
//
10241045
// NOTE: This function modifies the underlying memory of the slices passed in.
1025-
func skipCompactedBackups(backupEntries []BackupTreeEntry) []BackupTreeEntry {
1026-
for i := len(backupEntries) - 1; i >= 0; i-- {
1027-
if backupEntries[i].Manifest.IsCompacted {
1028-
backupEntries = slices.Delete(backupEntries, i, i+1)
1046+
func skipCompactedBackups[E ManifestLike](manifests []E) []E {
1047+
for i := len(manifests) - 1; i >= 0; i-- {
1048+
if manifests[i].Compacted() {
1049+
manifests = slices.Delete(manifests, i, i+1)
10291050
}
10301051
}
1031-
return backupEntries
1052+
return manifests
10321053
}
10331054

10341055
// validateContinuity checks that the backups are continuous.
1035-
func validateContinuity(backupEntries []BackupTreeEntry) error {
1036-
if len(backupEntries) == 0 {
1056+
//
1057+
// Note: This fucntion assumes the backups are sorted by end time in ascending
1058+
// order.
1059+
func validateContinuity[E ManifestLike](manifests []E) error {
1060+
if len(manifests) == 0 {
10371061
return errors.AssertionFailedf("an empty chain of backups cannot cover an end time")
10381062
}
1039-
for i := range len(backupEntries) - 1 {
1040-
if !backupEntries[i].Manifest.EndTime.Equal(backupEntries[i+1].Manifest.StartTime) {
1063+
for i := range len(manifests) - 1 {
1064+
end := manifests[i].End()
1065+
if !end.Equal(manifests[i+1].Start()) {
10411066
return errors.AssertionFailedf(
10421067
"backups are not continuous: %dth backup ends at %+v, %dth backup starts at %+v",
1043-
i, backupEntries[i].Manifest.EndTime,
1044-
i+1, backupEntries[i+1].Manifest.StartTime,
1068+
i, manifests[i].End(),
1069+
i+1, manifests[i+1].Start(),
10451070
)
10461071
}
10471072
}
@@ -1051,49 +1076,53 @@ func validateContinuity(backupEntries []BackupTreeEntry) error {
10511076
// elideSkippedLayers removes backups that are skipped in the backup chain and
10521077
// ensures only backups that will be used in the restore are returned.
10531078
//
1054-
// Note: This assumes that the provided backups are sorted in increasing order
1055-
// by end time, and then sorted in increasing order by start time to break ties.
1056-
func elideSkippedLayers(backupEntries []BackupTreeEntry) []BackupTreeEntry {
1057-
backupEntries = elideDuplicateEndTimes(backupEntries)
1058-
i := len(backupEntries) - 1
1079+
// Note: This function assumes that the manifests in the chain are sorted by end
1080+
// time in ascending order and ties are broken by start time in ascending order.
1081+
func elideSkippedLayers[E ManifestLike](manifests []E) []E {
1082+
manifests = elideDuplicateEndTimes(manifests)
1083+
i := len(manifests) - 1
10591084
for i > 0 {
10601085
// Find j such that backups[j] is parent of backups[i].
10611086
j := i - 1
1062-
for j >= 0 && !backupEntries[i].Manifest.StartTime.Equal(backupEntries[j].Manifest.EndTime) {
1087+
start := manifests[i].Start()
1088+
for j >= 0 && !start.Equal(manifests[j].End()) {
10631089
j--
10641090
}
10651091
// If there are backups between i and j, remove them.
10661092
// If j is less than 0, then no parent was found so nothing to skip.
10671093
if j != i-1 && j >= 0 {
1068-
backupEntries = slices.Delete(backupEntries, j+1, i)
1094+
manifests = slices.Delete(manifests, j+1, i)
10691095
}
10701096
// Move up to check the chain from j now.
10711097
i = j
10721098
}
1073-
return backupEntries
1099+
return manifests
10741100
}
10751101

10761102
// elideDuplicateEndTimes ensures that backups in a list of backups are
10771103
// functionally unique by removing any duplicates that have the same end time,
10781104
// choosing backups with earlier start times and eliding the rest.
10791105
//
1080-
// Note: This assumes that the provided backups are sorted in increasing order
1081-
// by end time, and then sorted in increasing order by start time to break ties.
1082-
func elideDuplicateEndTimes(backupEntries []BackupTreeEntry) []BackupTreeEntry {
1083-
for i := range len(backupEntries) - 1 {
1106+
// Note: This function assumes that the manifests in the chain are sorted by end
1107+
// time in ascending order and ties are broken by start time in ascending order.
1108+
func elideDuplicateEndTimes[E ManifestLike](manifests []E) []E {
1109+
for i := range len(manifests) - 1 {
1110+
if i >= len(manifests) {
1111+
break
1112+
}
10841113
j := i + 1
10851114
// Find j such that backups[j] no longer shares the same end time as
10861115
// backups[i].
1087-
for j < len(backupEntries) &&
1088-
backupEntries[i].Manifest.EndTime.Equal(backupEntries[j].Manifest.EndTime) {
1116+
end := manifests[i].End()
1117+
for j < len(manifests) && end.Equal(manifests[j].End()) {
10891118
j++
10901119
}
10911120
// If there exists backups between i and j, remove them.
10921121
if j > i+1 {
1093-
backupEntries = slices.Delete(backupEntries, i+1, j)
1122+
manifests = slices.Delete(manifests, i+1, j)
10941123
}
10951124
}
1096-
return backupEntries
1125+
return manifests
10971126
}
10981127

10991128
// GetBackupIndexAtTime returns the index of the latest backup in

pkg/backup/backupinfo/manifest_handling_test.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -576,8 +576,10 @@ func TestValidateEndTimeAndTruncate(t *testing.T) {
576576
inputLocs[i].URIsByOriginalLocalityKV[index] = index
577577
}
578578

579-
uris, res, locs, err := backupinfo.ValidateEndTimeAndTruncate(
580-
inputURIs, tc.manifests, inputLocs,
579+
manifestEntries, err := backupinfo.ZipBackupTreeEntries(inputURIs, tc.manifests, inputLocs)
580+
require.NoError(t, err)
581+
manifestEntries, err = backupinfo.ValidateEndTimeAndTruncate(
582+
manifestEntries,
581583
hlc.Timestamp{WallTime: int64(tc.endTime)},
582584
false, /* includeSkipped */
583585
tc.includeCompacted,
@@ -586,6 +588,7 @@ func TestValidateEndTimeAndTruncate(t *testing.T) {
586588
require.ErrorContains(t, err, tc.err)
587589
return
588590
}
591+
uris, res, locs := backupinfo.UnzipBackupTreeEntries(manifestEntries)
589592
require.Equal(t, len(tc.expected), len(res))
590593
require.Equal(t, expectedOrder, uris)
591594
require.Len(t, locs, len(tc.expected))

0 commit comments

Comments
 (0)