Skip to content

Commit

Permalink
Merge #43828 #44684
Browse files Browse the repository at this point in the history
43828: backupccl: add full cluster restore r=pbardea a=pbardea

This commit adds the ability to entirely restore a full cluster backup.
This includes replacing the contents of some system tables to match the
data in the backup. Full cluster restore can be used using the `RESTORE
FROM ...` syntax and is expected to be run on a new cluster with no user
data.

Release note (enterprise change): Add full cluster restore feature which
restores all the information contained in a full cluster backup. This
includes all of the user data as well as relevant data from system
tables. It is expected to be run on a new cluster with no user data.

44684: sql: move the existing savepoint logic to a separate file r=yuzefovich a=knz

This commit re-organizes the code without any functional change.

(Split from #43051 for ease of review.)

Co-authored-by: Paul Bardea <pbardea@gmail.com>
Co-authored-by: Raphael 'kena' Poss <knz@thaumogen.net>
  • Loading branch information
3 people committed Feb 5, 2020
3 parents b798c6a + e168928 + afc823e commit 6f601e2
Show file tree
Hide file tree
Showing 15 changed files with 1,151 additions and 471 deletions.
5 changes: 4 additions & 1 deletion docs/generated/sql/bnf/restore.bnf
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
restore_stmt ::=
'RESTORE' ( ( 'TABLE' | ) table_pattern ( ( ',' table_pattern ) )* | 'DATABASE' database_name ( ( ',' database_name ) )* ) 'FROM' partitioned_backup_list opt_as_of_clause 'WITH' kv_option_list
'RESTORE' 'FROM' partitioned_backup_list opt_as_of_clause 'WITH' kv_option_list
| 'RESTORE' 'FROM' partitioned_backup_list opt_as_of_clause
| 'RESTORE' 'FROM' partitioned_backup_list opt_as_of_clause
| 'RESTORE' ( ( 'TABLE' | ) table_pattern ( ( ',' table_pattern ) )* | 'DATABASE' database_name ( ( ',' database_name ) )* ) 'FROM' partitioned_backup_list opt_as_of_clause 'WITH' kv_option_list
| 'RESTORE' ( ( 'TABLE' | ) table_pattern ( ( ',' table_pattern ) )* | 'DATABASE' database_name ( ( ',' database_name ) )* ) 'FROM' partitioned_backup_list opt_as_of_clause
| 'RESTORE' ( ( 'TABLE' | ) table_pattern ( ( ',' table_pattern ) )* | 'DATABASE' database_name ( ( ',' database_name ) )* ) 'FROM' partitioned_backup_list opt_as_of_clause
3 changes: 2 additions & 1 deletion docs/generated/sql/bnf/stmt_block.bnf
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,8 @@ reset_stmt ::=
| reset_csetting_stmt

restore_stmt ::=
'RESTORE' targets 'FROM' partitioned_backup_list opt_as_of_clause opt_with_options
'RESTORE' 'FROM' partitioned_backup_list opt_as_of_clause opt_with_options
| 'RESTORE' targets 'FROM' partitioned_backup_list opt_as_of_clause opt_with_options

resume_stmt ::=
'RESUME' 'JOB' a_expr
Expand Down
154 changes: 103 additions & 51 deletions pkg/ccl/backupccl/backup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,28 @@ const (
localFoo = "nodelocal:///foo"
)

func backupRestoreTestSetupEmptyWithParams(
t testing.TB,
clusterSize int,
dir string,
init func(tc *testcluster.TestCluster),
params base.TestClusterArgs,
) (ctx context.Context, tc *testcluster.TestCluster, sqlDB *sqlutils.SQLRunner, cleanup func()) {
ctx = context.Background()

params.ServerArgs.ExternalIODir = dir
tc = testcluster.StartTestCluster(t, clusterSize, params)
init(tc)

sqlDB = sqlutils.MakeSQLRunner(tc.Conns[0])

cleanupFn := func() {
tc.Stopper().Stop(context.TODO()) // cleans up in memory storage's auxiliary dirs
}

return ctx, tc, sqlDB, cleanupFn
}

func backupRestoreTestSetupWithParams(
t testing.TB,
clusterSize int,
Expand Down Expand Up @@ -131,6 +153,12 @@ func backupRestoreTestSetup(
return backupRestoreTestSetupWithParams(t, clusterSize, numAccounts, init, base.TestClusterArgs{})
}

func backupRestoreTestSetupEmpty(
t testing.TB, clusterSize int, tempDir string, init func(*testcluster.TestCluster),
) (ctx context.Context, tc *testcluster.TestCluster, sqlDB *sqlutils.SQLRunner, cleanup func()) {
return backupRestoreTestSetupEmptyWithParams(t, clusterSize, tempDir, init, base.TestClusterArgs{})
}

func verifyBackupRestoreStatementResult(
t *testing.T, sqlDB *sqlutils.SQLRunner, query string, args ...interface{},
) error {
Expand Down Expand Up @@ -183,6 +211,64 @@ func verifyBackupRestoreStatementResult(
return nil
}

func generateInterleavedData(
sqlDB *sqlutils.SQLRunner, t *testing.T, numAccounts int,
) (int, []string) {
_ = sqlDB.Exec(t, `SET CLUSTER SETTING kv.range_merge.queue_enabled = false`)
// TODO(dan): The INTERLEAVE IN PARENT clause currently doesn't allow the
// `db.table` syntax. Fix that and use it here instead of `SET DATABASE`.
_ = sqlDB.Exec(t, `SET DATABASE = data`)
_ = sqlDB.Exec(t, `CREATE TABLE strpk (id string, v int, primary key (id, v)) PARTITION BY LIST (id) ( PARTITION ab VALUES IN (('a'), ('b')), PARTITION xy VALUES IN (('x'), ('y')) );`)
_ = sqlDB.Exec(t, `ALTER PARTITION ab OF TABLE strpk CONFIGURE ZONE USING gc.ttlseconds = 60`)
_ = sqlDB.Exec(t, `INSERT INTO strpk VALUES ('a', 1), ('a', 2), ('x', 100), ('y', 101)`)
const numStrPK = 4
_ = sqlDB.Exec(t, `CREATE TABLE strpkchild (a string, b int, c int, primary key (a, b, c)) INTERLEAVE IN PARENT strpk (a, b)`)
// i0 interleaves in parent with a, and has a multi-col PK of its own b, c
_ = sqlDB.Exec(t, `CREATE TABLE i0 (a INT, b INT, c INT, PRIMARY KEY (a, b, c)) INTERLEAVE IN PARENT bank (a)`)
// Split at at a _strict prefix_ of the cols in i_0's PK
_ = sqlDB.Exec(t, `ALTER TABLE i0 SPLIT AT VALUES (1, 1)`)
// i0_0 interleaves into i0.
_ = sqlDB.Exec(t, `CREATE TABLE i0_0 (a INT, b INT, c INT, d INT, PRIMARY KEY (a, b, c, d)) INTERLEAVE IN PARENT i0 (a, b, c)`)
_ = sqlDB.Exec(t, `CREATE TABLE i1 (a INT, b CHAR, PRIMARY KEY (a, b)) INTERLEAVE IN PARENT bank (a)`)
_ = sqlDB.Exec(t, `CREATE TABLE i2 (a INT, b CHAR, PRIMARY KEY (a, b)) INTERLEAVE IN PARENT bank (a)`)
// The bank table has numAccounts accounts, put 2x that in i0, 3x in i0_0,
// and 4x in i1.
totalRows := numAccounts + numStrPK
for i := 0; i < numAccounts; i++ {
_ = sqlDB.Exec(t, `INSERT INTO i0 VALUES ($1, 1, 1), ($1, 2, 2)`, i)
totalRows += 2
_ = sqlDB.Exec(t, `INSERT INTO i0_0 VALUES ($1, 1, 1, 1), ($1, 2, 2, 2), ($1, 3, 3, 3)`, i)
totalRows += 3
_ = sqlDB.Exec(t, `INSERT INTO i1 VALUES ($1, 'a'), ($1, 'b'), ($1, 'c'), ($1, 'd')`, i)
totalRows += 4
_ = sqlDB.Exec(t, `INSERT INTO i2 VALUES ($1, 'e'), ($1, 'f'), ($1, 'g'), ($1, 'h')`, i)
totalRows += 4
}
// Split some rows to attempt to exercise edge conditions in the key rewriter.
_ = sqlDB.Exec(t, `ALTER TABLE i0 SPLIT AT SELECT * from i0 where a % 2 = 0 LIMIT $1`, numAccounts)
_ = sqlDB.Exec(t, `ALTER TABLE i0_0 SPLIT AT SELECT * from i0_0 LIMIT $1`, numAccounts)
_ = sqlDB.Exec(t, `ALTER TABLE i1 SPLIT AT SELECT * from i1 WHERE a % 3 = 0`)
_ = sqlDB.Exec(t, `ALTER TABLE i2 SPLIT AT SELECT * from i2 WHERE a % 5 = 0`)
// Truncate will allocate a new ID for i1. At that point the splits we created
// above will still exist, but will contain the old table ID. Since the table
// does not exist anymore, it will not be in the backup, so the rewriting will
// not have a configured rewrite for that part of those splits, but we still
// expect RESTORE to succeed.
_ = sqlDB.Exec(t, `TRUNCATE i1`)
for i := 0; i < numAccounts; i++ {
_ = sqlDB.Exec(t, `INSERT INTO i1 VALUES ($1, 'a'), ($1, 'b'), ($1, 'c'), ($1, 'd')`, i)
}
tableNames := []string{
"strpk",
"strpkchild",
"i0",
"i0_0",
"i1",
"i2",
}
return totalRows, tableNames
}

func TestBackupRestoreStatementResult(t *testing.T) {
defer leaktest.AfterTest(t)()

Expand Down Expand Up @@ -1275,56 +1361,7 @@ func TestBackupRestoreInterleaved(t *testing.T) {
defer cleanupFn()
args := base.TestServerArgs{ExternalIODir: dir}

_ = sqlDB.Exec(t, `SET CLUSTER SETTING kv.range_merge.queue_enabled = false`)

// TODO(dan): The INTERLEAVE IN PARENT clause currently doesn't allow the
// `db.table` syntax. Fix that and use it here instead of `SET DATABASE`.
_ = sqlDB.Exec(t, `SET DATABASE = data`)

_ = sqlDB.Exec(t, `CREATE TABLE strpk (id string, v int, primary key (id, v)) PARTITION BY LIST (id) ( PARTITION ab VALUES IN (('a'), ('b')), PARTITION xy VALUES IN (('x'), ('y')) );`)
_ = sqlDB.Exec(t, `ALTER PARTITION ab OF TABLE strpk CONFIGURE ZONE USING gc.ttlseconds = 60`)
_ = sqlDB.Exec(t, `INSERT INTO strpk VALUES ('a', 1), ('a', 2), ('x', 100), ('y', 101)`)
const numStrPK = 4
_ = sqlDB.Exec(t, `CREATE TABLE strpkchild (a string, b int, c int, primary key (a, b, c)) INTERLEAVE IN PARENT strpk (a, b)`)

// i0 interleaves in parent with a, and has a multi-col PK of its own b, c
_ = sqlDB.Exec(t, `CREATE TABLE i0 (a INT, b INT, c INT, PRIMARY KEY (a, b, c)) INTERLEAVE IN PARENT bank (a)`)
// Split at at a _strict prefix_ of the cols in i_0's PK
_ = sqlDB.Exec(t, `ALTER TABLE i0 SPLIT AT VALUES (1, 1)`)

// i0_0 interleaves into i0.
_ = sqlDB.Exec(t, `CREATE TABLE i0_0 (a INT, b INT, c INT, d INT, PRIMARY KEY (a, b, c, d)) INTERLEAVE IN PARENT i0 (a, b, c)`)
_ = sqlDB.Exec(t, `CREATE TABLE i1 (a INT, b CHAR, PRIMARY KEY (a, b)) INTERLEAVE IN PARENT bank (a)`)
_ = sqlDB.Exec(t, `CREATE TABLE i2 (a INT, b CHAR, PRIMARY KEY (a, b)) INTERLEAVE IN PARENT bank (a)`)

// The bank table has numAccounts accounts, put 2x that in i0, 3x in i0_0,
// and 4x in i1.
totalRows := numAccounts + numStrPK
for i := 0; i < numAccounts; i++ {
_ = sqlDB.Exec(t, `INSERT INTO i0 VALUES ($1, 1, 1), ($1, 2, 2)`, i)
totalRows += 2
_ = sqlDB.Exec(t, `INSERT INTO i0_0 VALUES ($1, 1, 1, 1), ($1, 2, 2, 2), ($1, 3, 3, 3)`, i)
totalRows += 3
_ = sqlDB.Exec(t, `INSERT INTO i1 VALUES ($1, 'a'), ($1, 'b'), ($1, 'c'), ($1, 'd')`, i)
totalRows += 4
_ = sqlDB.Exec(t, `INSERT INTO i2 VALUES ($1, 'e'), ($1, 'f'), ($1, 'g'), ($1, 'h')`, i)
totalRows += 4
}
// Split some rows to attempt to exercise edge conditions in the key rewriter.
_ = sqlDB.Exec(t, `ALTER TABLE i0 SPLIT AT SELECT * from i0 where a % 2 = 0 LIMIT $1`, numAccounts)
_ = sqlDB.Exec(t, `ALTER TABLE i0_0 SPLIT AT SELECT * from i0_0 LIMIT $1`, numAccounts)
_ = sqlDB.Exec(t, `ALTER TABLE i1 SPLIT AT SELECT * from i1 WHERE a % 3 = 0`)
_ = sqlDB.Exec(t, `ALTER TABLE i2 SPLIT AT SELECT * from i2 WHERE a % 5 = 0`)

// Truncate will allocate a new ID for i1. At that point the splits we created
// above will still exist, but will contain the old table ID. Since the table
// does not exist anymore, it will not be in the backup, so the rewriting will
// not have a configured rewrite for that part of those splits, but we still
// expect RESTORE to succeed.
_ = sqlDB.Exec(t, `TRUNCATE i1`)
for i := 0; i < numAccounts; i++ {
_ = sqlDB.Exec(t, `INSERT INTO i1 VALUES ($1, 'a'), ($1, 'b'), ($1, 'c'), ($1, 'd')`, i)
}
totalRows, _ := generateInterleavedData(sqlDB, t, numAccounts)

var unused string
var exportedRows int
Expand Down Expand Up @@ -2557,9 +2594,15 @@ func TestRestoredPrivileges(t *testing.T) {
sqlDB.Exec(t, `CREATE USER someone`)
sqlDB.Exec(t, `GRANT SELECT, INSERT, UPDATE, DELETE ON data.bank TO someone`)

sqlDB.Exec(t, `CREATE DATABASE data2`)
// Explicitly don't restore grants when just restoring a database since we
// cannot ensure that the same users exist in the restoring cluster.
data2Grants := sqlDB.QueryStr(t, `SHOW GRANTS ON DATABASE data2`)
sqlDB.Exec(t, `GRANT SELECT, INSERT, UPDATE, DELETE ON DATABASE data2 TO someone`)

withGrants := sqlDB.QueryStr(t, `SHOW GRANTS ON data.bank`)

sqlDB.Exec(t, `BACKUP DATABASE data TO $1`, localFoo)
sqlDB.Exec(t, `BACKUP DATABASE data, data2 TO $1`, localFoo)
sqlDB.Exec(t, `DROP TABLE data.bank`)

t.Run("into fresh db", func(t *testing.T) {
Expand All @@ -2581,6 +2624,15 @@ func TestRestoredPrivileges(t *testing.T) {
sqlDBRestore.Exec(t, `RESTORE data.bank FROM $1`, localFoo)
sqlDBRestore.CheckQueryResults(t, `SHOW GRANTS ON data.bank`, withGrants)
})

t.Run("into db on db grants", func(t *testing.T) {
tc := testcluster.StartTestCluster(t, singleNode, base.TestClusterArgs{ServerArgs: args})
defer tc.Stopper().Stop(context.TODO())
sqlDBRestore := sqlutils.MakeSQLRunner(tc.Conns[0])
sqlDBRestore.Exec(t, `CREATE USER someone`)
sqlDBRestore.Exec(t, `RESTORE DATABASE data2 FROM $1`, localFoo)
sqlDBRestore.CheckQueryResults(t, `SHOW GRANTS ON DATABASE data2`, data2Grants)
})
}

func TestRestoreInto(t *testing.T) {
Expand Down
Loading

0 comments on commit 6f601e2

Please sign in to comment.