Skip to content

Commit e3770b8

Browse files
craig[bot]kev-caomsbutler
committed
151151: feat: update FindPriorBackups to use backup index r=msbutler a=kev-cao This patch teaches `FindPriorBackups` (now more aptly renamed to `FindAllIncrementals`) to use the backup index introduced in #150384. Additional changes will still be required to fully fix the related tickets, namely updating the logic to no longer need to load ALL manifests of a chain into memory, only the manifests needed. Epic: CRDB-47942 Informs: #150327, #150329, #150330, #150331 152737: crosscluster/logical: skip udf test r=jeffswenson a=msbutler Informs #138667 Release note: none Co-authored-by: Kevin Cao <[email protected]> Co-authored-by: Michael Butler <[email protected]>
3 parents de1bfff + 89f5391 + d8bf17e commit e3770b8

File tree

13 files changed

+580
-247
lines changed

13 files changed

+580
-247
lines changed

pkg/backup/backup_test.go

Lines changed: 0 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -9965,108 +9965,6 @@ func TestUserfileNormalizationIncrementalShowBackup(t *testing.T) {
99659965
sqlDB.Exec(t, query)
99669966
}
99679967

9968-
// TestBackupRestoreSeparateExplicitIsDefault tests that a backup/restore round
9969-
// trip using the 'incremental_location' parameter restores the same db as a BR
9970-
// round trip without the parameter, even when that location is in fact the default.
9971-
func TestBackupRestoreSeparateExplicitIsDefault(t *testing.T) {
9972-
defer leaktest.AfterTest(t)()
9973-
defer log.Scope(t).Close(t)
9974-
9975-
skip.UnderRace(t, "multinode cluster setup times out under race, likely due to resource starvation.")
9976-
9977-
const numAccounts = 1
9978-
_, sqlDB, _, cleanupFn := backupRestoreTestSetup(t, multiNode, numAccounts, InitManualReplication)
9979-
defer cleanupFn()
9980-
9981-
const c1, c2, c3 = `nodelocal://1/full/`, `nodelocal://2/full/`, `nodelocal://3/full/`
9982-
const i1, i2, i3 = `nodelocal://1/full/incrementals`, `nodelocal://2/full/incrementals`, `nodelocal://3/full/incrementals`
9983-
9984-
collections := []string{
9985-
fmt.Sprintf("'%s?COCKROACH_LOCALITY=%s'", c1, url.QueryEscape("default")),
9986-
fmt.Sprintf("'%s?COCKROACH_LOCALITY=%s'", c2, url.QueryEscape("dc=dc1")),
9987-
fmt.Sprintf("'%s?COCKROACH_LOCALITY=%s'", c3, url.QueryEscape("dc=dc2")),
9988-
}
9989-
9990-
incrementals := []string{
9991-
fmt.Sprintf("'%s?COCKROACH_LOCALITY=%s'", i1, url.QueryEscape("default")),
9992-
fmt.Sprintf("'%s?COCKROACH_LOCALITY=%s'", i2, url.QueryEscape("dc=dc1")),
9993-
fmt.Sprintf("'%s?COCKROACH_LOCALITY=%s'", i3, url.QueryEscape("dc=dc2")),
9994-
}
9995-
tests := []struct {
9996-
dest []string
9997-
inc []string
9998-
}{
9999-
{dest: []string{collections[0]}, inc: []string{incrementals[0]}},
10000-
{dest: collections, inc: incrementals},
10001-
}
10002-
10003-
for _, br := range tests {
10004-
10005-
dest := strings.Join(br.dest, ", ")
10006-
inc := strings.Join(br.inc, ", ")
10007-
10008-
if len(br.dest) > 1 {
10009-
dest = "(" + dest + ")"
10010-
inc = "(" + inc + ")"
10011-
}
10012-
// create db
10013-
sqlDB.Exec(t, `CREATE DATABASE fkdb`)
10014-
sqlDB.Exec(t, `CREATE TABLE fkdb.fk (ind INT)`)
10015-
10016-
for i := 0; i < 10; i++ {
10017-
sqlDB.Exec(t, `INSERT INTO fkdb.fk (ind) VALUES ($1)`, i)
10018-
}
10019-
fb := fmt.Sprintf("BACKUP DATABASE fkdb INTO %s", dest)
10020-
sqlDB.Exec(t, fb)
10021-
10022-
sqlDB.Exec(t, `INSERT INTO fkdb.fk (ind) VALUES ($1)`, 200)
10023-
10024-
sib := fmt.Sprintf("BACKUP DATABASE fkdb INTO LATEST IN %s WITH incremental_location=%s", dest, inc)
10025-
sqlDB.Exec(t, sib)
10026-
{
10027-
// Locality Aware Show Backup validation
10028-
// TODO (msbutler): move to data driven test after 22.1 backport
10029-
10030-
// Assert the localities field populates correctly (not null if backup is locality aware).
10031-
localities := sqlDB.QueryStr(t,
10032-
fmt.Sprintf("SELECT locality FROM [SHOW BACKUP FILES FROM LATEST IN %s]", dest))
10033-
expectedLocalities := map[string]bool{"default": true, "dc=dc1": true, "dc=dc2": true}
10034-
for _, locality := range localities {
10035-
if len(br.dest) > 1 {
10036-
_, ok := expectedLocalities[locality[0]]
10037-
require.Equal(t, true, ok)
10038-
} else {
10039-
require.Equal(t, "NULL", locality[0])
10040-
}
10041-
}
10042-
// Assert show backup still works.
10043-
sqlDB.Exec(t, fmt.Sprintf("SHOW BACKUPS IN %s", dest))
10044-
sqlDB.Exec(t, fmt.Sprintf("SHOW BACKUP FROM LATEST IN %s", dest))
10045-
10046-
// Locality aware show backups will eventually fail if not all localities are provided,
10047-
// but for now, they're ok.
10048-
if len(br.dest) > 1 {
10049-
sqlDB.Exec(t, fmt.Sprintf("SHOW BACKUP FROM LATEST IN %s", br.dest[1]))
10050-
}
10051-
10052-
sqlDB.Exec(t, fmt.Sprintf("SHOW BACKUP FROM LATEST IN %s WITH incremental_location= %s", dest, inc))
10053-
}
10054-
sir := fmt.Sprintf("RESTORE DATABASE fkdb FROM LATEST IN %s WITH new_db_name = 'inc_fkdb', incremental_location=%s", dest, inc)
10055-
sqlDB.Exec(t, sir)
10056-
10057-
ib := fmt.Sprintf("BACKUP DATABASE fkdb INTO LATEST IN %s", dest)
10058-
sqlDB.Exec(t, ib)
10059-
ir := fmt.Sprintf("RESTORE DATABASE fkdb FROM LATEST IN %s WITH new_db_name = 'trad_fkdb'", dest)
10060-
sqlDB.Exec(t, ir)
10061-
10062-
sqlDB.CheckQueryResults(t, `SELECT * FROM trad_fkdb.fk`, sqlDB.QueryStr(t, `SELECT * FROM inc_fkdb.fk`))
10063-
10064-
sqlDB.Exec(t, "DROP DATABASE fkdb")
10065-
sqlDB.Exec(t, "DROP DATABASE trad_fkdb;")
10066-
sqlDB.Exec(t, "DROP DATABASE inc_fkdb;")
10067-
}
10068-
}
10069-
100709968
// TestRestoreOnFailOrCancelAfterPause is a regression test that ensures that we
100719969
// can cancel a paused restore job. Previously, this test would result in a nil
100729970
// pointer exception when accessing the `r.execCfg` since the resumer was not

pkg/backup/backupdest/BUILD.bazel

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,12 @@ go_library(
2626
"//pkg/sql",
2727
"//pkg/sql/pgwire/pgcode",
2828
"//pkg/sql/pgwire/pgerror",
29+
"//pkg/util",
2930
"//pkg/util/ctxgroup",
3031
"//pkg/util/encoding",
3132
"//pkg/util/hlc",
3233
"//pkg/util/ioctx",
34+
"//pkg/util/metamorphic",
3335
"//pkg/util/mon",
3436
"//pkg/util/protoutil",
3537
"//pkg/util/timeutil",

pkg/backup/backupdest/backup_destination.go

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,15 @@ func ResolveDest(
219219
}
220220
defer incrementalStore.Close()
221221

222-
priors, err := FindPriorBackups(ctx, incrementalStore, OmitManifest)
222+
rootStore, err := makeCloudStorage(ctx, collectionURI, user)
223+
if err != nil {
224+
return ResolvedDestination{}, err
225+
}
226+
defer rootStore.Close()
227+
priors, err := FindAllIncrementalPaths(
228+
ctx, execCfg, incrementalStore, rootStore,
229+
chosenSuffix, OmitManifest, len(dest.IncrementalStorage) > 0,
230+
)
223231
if err != nil {
224232
return ResolvedDestination{}, errors.Wrap(err, "adjusting backup destination to append new layer to existing backup")
225233
}
@@ -537,10 +545,13 @@ func ListFullBackupsInCollection(
537545
// included in the result, otherwise they are filtered out.
538546
func ResolveBackupManifests(
539547
ctx context.Context,
548+
execCfg *sql.ExecutorConfig,
540549
mem *mon.BoundAccount,
550+
defaultCollectionURI string,
541551
baseStores []cloud.ExternalStorage,
542552
incStores []cloud.ExternalStorage,
543553
mkStore cloud.ExternalStorageFromURIFactory,
554+
resolvedSubdir string,
544555
fullyResolvedBaseDirectory []string,
545556
fullyResolvedIncrementalsDirectory []string,
546557
endTime hlc.Timestamp,
@@ -575,7 +586,23 @@ func ResolveBackupManifests(
575586

576587
var incrementalBackups []string
577588
if len(incStores) > 0 {
578-
incrementalBackups, err = FindPriorBackups(ctx, incStores[0], includeManifest)
589+
rootStore, err := mkStore(ctx, defaultCollectionURI, user)
590+
if err != nil {
591+
return nil, nil, nil, 0, err
592+
}
593+
defer rootStore.Close()
594+
incrementalBackups, err = FindAllIncrementalPaths(
595+
ctx,
596+
execCfg,
597+
incStores[0],
598+
rootStore,
599+
resolvedSubdir,
600+
includeManifest,
601+
// In the legacy path, there is no index to be used, so we may as well set
602+
// customIncLocation to true rather than try to compute it from the
603+
// parameters.
604+
true,
605+
)
579606
if err != nil {
580607
return nil, nil, nil, 0, err
581608
}

pkg/backup/backupdest/backup_index.go

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,25 @@ import (
2121
"github.com/cockroachdb/cockroach/pkg/clusterversion"
2222
"github.com/cockroachdb/cockroach/pkg/jobs/jobspb"
2323
"github.com/cockroachdb/cockroach/pkg/security/username"
24+
"github.com/cockroachdb/cockroach/pkg/settings"
2425
"github.com/cockroachdb/cockroach/pkg/sql"
2526
"github.com/cockroachdb/cockroach/pkg/util/ctxgroup"
2627
"github.com/cockroachdb/cockroach/pkg/util/hlc"
28+
"github.com/cockroachdb/cockroach/pkg/util/metamorphic"
2729
"github.com/cockroachdb/cockroach/pkg/util/protoutil"
2830
"github.com/cockroachdb/cockroach/pkg/util/tracing"
2931
"github.com/cockroachdb/errors"
3032
)
3133

34+
var (
35+
ReadBackupIndexEnabled = settings.RegisterBoolSetting(
36+
settings.ApplicationLevel,
37+
"backup.index.read.enabled",
38+
"if true, the backup index will be read when reading from a backup collection",
39+
metamorphic.ConstantWithTestBool("backup.index.read.enabled", false),
40+
)
41+
)
42+
3243
// WriteBackupIndexMetadata writes an index file for the backup described by the
3344
// job details. The provided ExternalStorage needs to be rooted at the specific
3445
// directory that the index file should be written to.
@@ -68,7 +79,7 @@ func WriteBackupIndexMetadata(
6879

6980
path, err := backuputils.AbsoluteBackupPathInCollectionURI(details.CollectionURI, details.URI)
7081
if err != nil {
71-
return errors.Wrapf(err, "get relative backup path")
82+
return err
7283
}
7384
metadata := &backuppb.BackupIndexMetadata{
7485
StartTime: details.StartTime,
@@ -262,6 +273,28 @@ func GetBackupTreeIndexMetadata(
262273
return indexes[:coveringIdx], nil
263274
}
264275

276+
// ParseBackupFilePathFromIndexFileName parses the path to a backup given the
277+
// basename of its index file and its subdirectory. For full backups, the
278+
// returned path is relative to the root of the backup. For incremental
279+
// backups, the path is relative to the incremental storage location starting
280+
// from the subdir (e.g. 20250730/130000.00-20250730-120000.00). It expects
281+
// that subdir is resolved and not `LATEST`.
282+
//
283+
// Note: While the path is stored in the index file, we can take a shortcut here
284+
// and derive it from the filename solely because backup paths are
285+
// millisecond-precise and so are the timestamps encoded in the filename.
286+
func parseBackupFilePathFromIndexFileName(subdir, basename string) (string, error) {
287+
start, end, err := parseIndexFilename(basename)
288+
if err != nil {
289+
return "", err
290+
}
291+
if start.IsZero() {
292+
return subdir, nil
293+
}
294+
295+
return ConstructDateBasedIncrementalFolderName(start, end), nil
296+
}
297+
265298
// ParseIndexFilename parses the start and end timestamps from the index
266299
// filename.
267300
//

pkg/backup/backupdest/backup_index_test.go

Lines changed: 0 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import (
1212
"fmt"
1313
"io"
1414
"maps"
15-
"math/rand/v2"
1615
"os"
1716
"path"
1817
"slices"
@@ -484,103 +483,6 @@ func TestIndexExists(t *testing.T) {
484483
}
485484
}
486485

487-
func TestListIndexes(t *testing.T) {
488-
defer leaktest.AfterTest(t)()
489-
defer log.Scope(t).Close(t)
490-
491-
ctx := context.Background()
492-
dir, dirCleanup := testutils.TempDir(t)
493-
defer dirCleanup()
494-
495-
st := cluster.MakeTestingClusterSettingsWithVersions(
496-
clusterversion.Latest.Version(),
497-
clusterversion.Latest.Version(),
498-
true,
499-
)
500-
execCfg := &sql.ExecutorConfig{Settings: st}
501-
502-
const collectionURI = "nodelocal://1/test"
503-
storage, err := cloud.ExternalStorageFromURI(
504-
ctx,
505-
collectionURI,
506-
base.ExternalIODirConfig{},
507-
st,
508-
blobs.TestBlobServiceClient(dir),
509-
username.RootUserName(),
510-
nil, /* db */
511-
nil, /* limiters */
512-
cloud.NilMetrics,
513-
)
514-
require.NoError(t, err)
515-
defer storage.Close()
516-
storageFactory := func(
517-
_ context.Context, _ string, _ username.SQLUsername, _ ...cloud.ExternalStorageOption,
518-
) (cloud.ExternalStorage, error) {
519-
return storage, nil
520-
}
521-
522-
fullEnd := intToTime(2)
523-
fullSubdir := fullEnd.GoTime().Format(backupbase.DateBasedIntoFolderName)
524-
details := jobspb.BackupDetails{
525-
Destination: jobspb.BackupDetails_Destination{
526-
To: []string{collectionURI},
527-
Subdir: fullSubdir,
528-
},
529-
StartTime: hlc.Timestamp{},
530-
EndTime: fullEnd,
531-
CollectionURI: collectionURI,
532-
// URI does not need to be set properly for this test since we are not
533-
// reading the contents of the files.
534-
URI: collectionURI + "/" + fullSubdir,
535-
}
536-
require.NoError(t, WriteBackupIndexMetadata(
537-
ctx, execCfg, username.RootUserName(), storageFactory, details,
538-
))
539-
540-
incsToWrite := [][2]int{
541-
{2, 4},
542-
{2, 6},
543-
{4, 6},
544-
{6, 8},
545-
}
546-
// We shuffle the order that we write backups in just to really stress the
547-
// ordering logic in ListIndexes.
548-
rand.Shuffle(len(incsToWrite), func(i, j int) {
549-
incsToWrite[i], incsToWrite[j] = incsToWrite[j], incsToWrite[i]
550-
})
551-
for _, times := range incsToWrite {
552-
start := intToTime(times[0])
553-
end := intToTime(times[1])
554-
details.StartTime = start
555-
details.EndTime = end
556-
require.NoError(
557-
t, WriteBackupIndexMetadata(ctx, execCfg, username.RootUserName(), storageFactory, details),
558-
)
559-
}
560-
561-
indexes, err := ListIndexes(ctx, storage, fullSubdir)
562-
require.NoError(t, err)
563-
564-
require.Len(t, indexes, len(incsToWrite)+1)
565-
require.True(t, slices.IsSortedFunc(indexes, func(a, b string) int {
566-
aStart, aEnd, err := parseIndexFilename(a)
567-
require.NoError(t, err)
568-
bStart, bEnd, err := parseIndexFilename(b)
569-
require.NoError(t, err)
570-
if aEnd.Before(bEnd) {
571-
return -1
572-
} else if aEnd.After(bEnd) {
573-
return 1
574-
}
575-
// end times are equal, compare start times
576-
if bStart.Before(aStart) {
577-
return 1
578-
} else {
579-
return -1
580-
}
581-
}), "indexes are not sorted by end time and then start time")
582-
}
583-
584486
func TestGetBackupTreeIndexMetadata(t *testing.T) {
585487
defer leaktest.AfterTest(t)()
586488
defer log.Scope(t).Close(t)

0 commit comments

Comments
 (0)