Skip to content

Commit 12a500e

Browse files
committed
backup: ValidateEndTimeAndTruncate now supports compacted backups with rev history
Previously, the logic in `ValidateEndTimeAndTruncate` assumed that for a revision history backup tree, there would be no compacted backups. This patch teaches the function to now work with trees that contain both revision history backups and compacted backups. The logic works as such: 1. Compacted backups that cover the requested AOST (but do not have a matching end time) are elided. 2. Any preceding compacted backups will still be included and its component backups elided. This patch is part of a larger effort to fully support compactions in revision history trees. Informs: #158696 Release note: None
1 parent ebfcb56 commit 12a500e

File tree

2 files changed

+134
-21
lines changed

2 files changed

+134
-21
lines changed

pkg/backup/backupinfo/manifest_handling.go

Lines changed: 77 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -967,34 +967,54 @@ func ValidateEndTimeAndTruncate[E ManifestLike](
967967
if includeSkipped {
968968
return manifests, nil
969969
}
970-
manifests = elideSkippedLayers(manifests)
970+
manifests = elideSkippedLayers(manifests, endTime)
971971
if err := validateContinuity(manifests); err != nil {
972972
return nil, err
973973
}
974974
return manifests, nil
975975
}
976+
977+
var closestEndTime hlc.Timestamp
978+
var closestTimeDiff time.Duration
979+
arbitraryTimeErr := func() error {
980+
return errors.Errorf(
981+
"invalid RESTORE timestamp: restoring to arbitrary time requires that "+
982+
"BACKUP for requested time be created with `revision_history` option."+
983+
" nearest backup time is %s",
984+
closestEndTime.GoTime().UTC(),
985+
)
986+
}
976987
for i, b := range manifests {
977-
// Find the backup that covers the requested time.
978-
if !(b.Start().Less(endTime) && endTime.LessEq(b.End())) {
988+
timeDiff := b.End().GoTime().Sub(endTime.GoTime()).Abs()
989+
if closestEndTime.IsEmpty() || timeDiff < closestTimeDiff {
990+
closestEndTime = b.End()
991+
closestTimeDiff = timeDiff
992+
}
993+
994+
if b.End().Less(endTime) {
995+
// This backup precedes the requested time, so continue.
979996
continue
997+
} else if endTime.LessEq(b.Start()) {
998+
// We've overshot the requested time, so no subsequent backups will cover
999+
// it. This implies the user has requested an arbitrary time without a
1000+
// covering revision history backup.
1001+
return nil, arbitraryTimeErr()
9801002
}
9811003

982-
// Ensure that the backup actually has revision history.
1004+
// At this point, we know that b.Start() < endTime <= b.End(). If endTime !=
1005+
// b.End(), then this backup only covers the time if it is a revision
1006+
// history backup.
9831007
if !endTime.Equal(b.End()) {
9841008
if b.MVCC() != backuppb.MVCCFilter_All {
985-
const errPrefix = "invalid RESTORE timestamp: restoring to arbitrary time requires that BACKUP for requested time be created with 'revision_history' option."
986-
if i == 0 {
987-
return nil, errors.Errorf(
988-
errPrefix+" nearest backup time is %s",
989-
timeutil.Unix(0, b.End().WallTime).UTC(),
990-
)
1009+
if i < len(manifests)-1 {
1010+
// This backup is not a revision history backup, but subsequent
1011+
// backups might be (e.g. revision history backup chain that contains
1012+
// compacted backups). We continue to search.
1013+
continue
9911014
}
992-
return nil, errors.Errorf(
993-
errPrefix+" nearest BACKUP times are %s or %s",
994-
timeutil.Unix(0, manifests[i-1].End().WallTime).UTC(),
995-
timeutil.Unix(0, b.End().WallTime).UTC(),
996-
)
1015+
return nil, arbitraryTimeErr()
9971016
}
1017+
9981018
// Ensure that the revision history actually covers the requested time -
9991019
// while the BACKUP's start and end might contain the requested time for
10001020
// example if start time is 0 (full backup), the revision history was
@@ -1007,16 +1027,18 @@ func ValidateEndTimeAndTruncate[E ManifestLike](
10071027
)
10081028
}
10091029
}
1030+
10101031
if includeSkipped {
10111032
return manifests[:i+1], nil
10121033
}
1013-
manifests = elideSkippedLayers(manifests[:i+1])
1034+
manifests = elideSkippedLayers(manifests[:i+1], endTime)
10141035
if err := validateContinuity(manifests); err != nil {
10151036
return nil, err
10161037
}
10171038
return manifests, nil
10181039
}
10191040

1041+
// If we reach here, the requested time is beyond the end time of all backups.
10201042
return nil, errors.Errorf(
10211043
"invalid RESTORE timestamp: supplied backups do not cover requested time",
10221044
)
@@ -1037,7 +1059,7 @@ func skipCompactedBackups[E ManifestLike](manifests []E) []E {
10371059

10381060
// validateContinuity checks that the backups are continuous.
10391061
//
1040-
// Note: This fucntion assumes the backups are sorted by end time in ascending
1062+
// Note: This function assumes the backups are sorted by end time in ascending
10411063
// order.
10421064
func validateContinuity[E ManifestLike](manifests []E) error {
10431065
if len(manifests) == 0 {
@@ -1056,12 +1078,48 @@ func validateContinuity[E ManifestLike](manifests []E) error {
10561078
return nil
10571079
}
10581080

1081+
// elideCompactedBackupsForRevisionHistory removes compacted backups that will
1082+
// not be used in a revision history restore. Since compacted backups do not
1083+
// contain revision histories, any compacted backup that contains the requested
1084+
// time without matching its end time must be elided. However, all prior
1085+
// compacted backups can be retained. The requestedEnd may be empty to indicate
1086+
// no specified end time.
1087+
//
1088+
// Note: This function assumes that the backups are sorted by end time in
1089+
// increasing order.
1090+
func elideCompactedBackupsForRevisionHistory[E ManifestLike](
1091+
manifests []E, requestedEnd hlc.Timestamp,
1092+
) []E {
1093+
if requestedEnd.IsEmpty() {
1094+
return manifests
1095+
}
1096+
for i := len(manifests) - 1; i >= 0; i-- {
1097+
// Since manifests are sorted by end time ascending, once we reach a backup
1098+
// whose end time is less than the requested end time, we can stop. No
1099+
// subsequent backups will cover.
1100+
if manifests[i].End().Less(requestedEnd) {
1101+
break
1102+
}
1103+
if !manifests[i].Compacted() {
1104+
continue
1105+
}
1106+
// If the compacted backup covers the end time but does not end at the end
1107+
// time specifically, it cannot be used in the restore.
1108+
if manifests[i].Start().Less(requestedEnd) && !requestedEnd.Equal(manifests[i].End()) {
1109+
manifests = slices.Delete(manifests, i, i+1)
1110+
}
1111+
}
1112+
return manifests
1113+
}
1114+
10591115
// elideSkippedLayers removes backups that are skipped in the backup chain and
1060-
// ensures only backups that will be used in the restore are returned.
1116+
// ensures only backups that will be used in the restore are returned. The
1117+
// provided requestedEnd may be empty to indicate no specified end time.
10611118
//
10621119
// Note: This function assumes that the manifests in the chain are sorted by end
10631120
// time in ascending order and ties are broken by start time in ascending order.
1064-
func elideSkippedLayers[E ManifestLike](manifests []E) []E {
1121+
func elideSkippedLayers[E ManifestLike](manifests []E, requestedEnd hlc.Timestamp) []E {
1122+
manifests = elideCompactedBackupsForRevisionHistory(manifests, requestedEnd)
10651123
manifests = elideDuplicateEndTimes(manifests)
10661124
i := len(manifests) - 1
10671125
for i > 0 {

pkg/backup/backupinfo/manifest_handling_test.go

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -441,21 +441,76 @@ func TestValidateEndTimeAndTruncate(t *testing.T) {
441441
err: "supplied backups do not cover requested time",
442442
},
443443
{
444-
name: "revision history restore should fail on non-revision history backups",
444+
name: "rev history restore should fail on non-rev history backups",
445445
manifests: []backuppb.BackupManifest{
446446
mNorm(0, 2), mNorm(2, 4),
447447
},
448448
endTime: 3,
449449
err: "restoring to arbitrary time",
450450
},
451451
{
452-
name: "revision history restore should succeed on revision history backups",
452+
name: "rev history restore should succeed on rev history backups",
453453
manifests: []backuppb.BackupManifest{
454454
mRev(0, 2), mRev(2, 4),
455455
},
456456
endTime: 3,
457457
expected: [][]int{{0, 2}, {2, 4}},
458458
},
459+
{
460+
name: "rev history restore to aost after compacted backup should succeed",
461+
manifests: []backuppb.BackupManifest{
462+
mRev(0, 2), mRev(2, 4), mRev(4, 6), mComp(2, 8), mRev(6, 8), mRev(8, 10),
463+
},
464+
endTime: 9,
465+
includeCompacted: true,
466+
expected: [][]int{
467+
{0, 2}, {2, 8}, {8, 10},
468+
},
469+
},
470+
{
471+
name: "rev history restore to aost of compacted backup should include compacted backup",
472+
manifests: []backuppb.BackupManifest{
473+
mRev(0, 2), mRev(2, 4), mRev(4, 6), mComp(2, 8), mRev(6, 8), mRev(8, 10),
474+
},
475+
endTime: 8,
476+
includeCompacted: true,
477+
expected: [][]int{
478+
{0, 2}, {2, 8},
479+
},
480+
},
481+
{
482+
name: "rev history restore to aost in middle of compacted backup should succeed",
483+
manifests: []backuppb.BackupManifest{
484+
mRev(0, 2), mRev(2, 4), mRev(4, 6), mComp(2, 8), mRev(6, 8), mRev(8, 10),
485+
},
486+
endTime: 7,
487+
includeCompacted: true,
488+
expected: [][]int{
489+
{0, 2}, {2, 4}, {4, 6}, {6, 8},
490+
},
491+
},
492+
{
493+
name: "rev history restore to aost in middle of compacted backup with two compactions",
494+
manifests: []backuppb.BackupManifest{
495+
mRev(0, 2), mRev(2, 4), mRev(4, 6), mComp(2, 8), mComp(4, 8), mRev(6, 8), mRev(8, 10),
496+
},
497+
endTime: 7,
498+
includeCompacted: true,
499+
expected: [][]int{
500+
{0, 2}, {2, 4}, {4, 6}, {6, 8},
501+
},
502+
},
503+
{
504+
name: "rev history restore should still use preceding compacted backups",
505+
manifests: []backuppb.BackupManifest{
506+
mRev(0, 2), mRev(2, 4), mComp(2, 6), mRev(4, 6), mRev(6, 8), mComp(6, 10), mRev(8, 10),
507+
},
508+
endTime: 9,
509+
includeCompacted: true,
510+
expected: [][]int{
511+
{0, 2}, {2, 6}, {6, 8}, {8, 10},
512+
},
513+
},
459514
{
460515
name: "end time in middle of chain should truncate",
461516
manifests: []backuppb.BackupManifest{

0 commit comments

Comments
 (0)