Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

backupccl: allow restoring into multi-region databases #65015

Merged
merged 1 commit into from Jul 6, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
21 changes: 7 additions & 14 deletions pkg/ccl/backupccl/restore_planning.go
Expand Up @@ -654,21 +654,14 @@ func allocateDescriptorRewrites(
return err
}

// We're restoring a table and not its parent database. If the
// new database we're placing the table in is a multi-region database,
// block the restore. We do this because we currently have no way to
// modify this table and make it multi-region friendly. Long-term we'd
// want to modify the table so that it can exist in the multi-region
// database.
// https://github.com/cockroachdb/cockroach/issues/59804
if parentDB.IsMultiRegion() {
return pgerror.Newf(pgcode.FeatureNotSupported,
"cannot restore individual table %d into multi-region database %d",
table.GetID(),
parentDB.GetID(),
)
if parentDB.IsMultiRegion() && table.GetLocalityConfig() != nil {
// We're restoring a table and not its parent database. We may block
// restoring multi-region tables to multi-region databases since regions
// may mismatch.
if err := checkMultiRegionCompatible(ctx, txn, p.ExecCfg().Codec, table, parentDB); err != nil {
return pgerror.WithCandidateCode(err, pgcode.FeatureNotSupported)
}
}

// Create the table rewrite with the new parent ID. We've done all the
// up-front validation that we can.
descriptorRewrites[table.ID] = &jobspb.RestoreDetails_DescriptorRewrite{ParentID: parentID}
Expand Down
70 changes: 70 additions & 0 deletions pkg/ccl/backupccl/targets.go
Expand Up @@ -10,6 +10,7 @@ package backupccl

import (
"context"
"fmt"
"reflect"
"sort"
"strings"
Expand All @@ -32,6 +33,7 @@ import (
"github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgcode"
"github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgerror"
"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
"github.com/cockroachdb/cockroach/pkg/util/errorutil/unimplemented"
"github.com/cockroachdb/cockroach/pkg/util/hlc"
"github.com/cockroachdb/cockroach/pkg/util/timeutil"
"github.com/cockroachdb/errors"
Expand Down Expand Up @@ -612,3 +614,71 @@ func findLastSchemaChangeTime(
}
return lastSchemaChangeTime
}

// checkMultiRegionCompatible checks if the given table is compatible to be
// restored into the given database according to its multi-region locality.
// It returns an error describing the incompatibility if not.
func checkMultiRegionCompatible(
ctx context.Context,
txn *kv.Txn,
codec keys.SQLCodec,
table *tabledesc.Mutable,
database catalog.DatabaseDescriptor,
) error {
// If either the database or table are non-MR then allow it.
if !database.IsMultiRegion() || table.GetLocalityConfig() == nil {
return nil
}

if table.IsLocalityGlobal() {
// Global tables are allowed because they do not reference a particular
// region.
return nil
}

if table.IsLocalityRegionalByTable() {
regionName, _ := table.GetRegionalByTableRegion()
if regionName == descpb.RegionName(tree.PrimaryRegionNotSpecifiedName) {
// REGIONAL BY PRIMARY REGION tables are allowed since they do not
// reference a particular region.
return nil
}

// For REGION BY TABLE IN <region> tables, allow the restore if the
// database has the region.
regionEnumID := database.GetRegionConfig().RegionEnumID
regionEnum, err := catalogkv.MustGetTypeDescByID(ctx, txn, codec, regionEnumID)
if err != nil {
return err
}
dbRegionNames, err := regionEnum.RegionNames()
if err != nil {
return err
}
existingRegions := make([]string, len(dbRegionNames))
for i, dbRegionName := range dbRegionNames {
if dbRegionName == regionName {
return nil
}
existingRegions[i] = fmt.Sprintf("%q", dbRegionName)
}

return errors.Newf(
"cannot restore REGIONAL BY TABLE %s IN REGION %q (table ID: %d) into database %q; region %q not found in database regions %s",
table.GetName(), regionName, table.GetID(),
database.GetName(), regionName, strings.Join(existingRegions, ", "),
)
}

if table.IsLocalityRegionalByRow() {
return unimplemented.NewWithIssuef(67269,
"cannot restore REGIONAL BY ROW table %s (ID: %d) individually into a multi-region database %s",
table.GetName(), table.GetID(), database.GetName(),
)
}

return errors.AssertionFailedf(
"locality config of table %s (ID: %d) has locality %v which is unknown by RESTORE",
table.GetName(), table.GetID(), table.GetLocalityConfig(),
)
}
195 changes: 194 additions & 1 deletion pkg/ccl/logictestccl/testdata/logic_test/multi_region_backup
Expand Up @@ -204,6 +204,13 @@ CREATE TABLE regional_by_table_in_ca_central_1 (
FAMILY (pk, i)
) LOCALITY REGIONAL BY TABLE IN "ca-central-1"

statement ok
CREATE TABLE regional_by_table_in_ap_southeast_2 (
pk INT PRIMARY KEY,
i INT,
FAMILY (pk, i)
) LOCALITY REGIONAL BY TABLE IN "ap-southeast-2"

query TTT
SELECT partition_name, index_name, zone_config FROM [SHOW PARTITIONS FROM TABLE global_table]
ORDER BY partition_name, index_name
Expand Down Expand Up @@ -1025,8 +1032,194 @@ USE non_mr_backup
statement ok
CREATE TABLE non_mr_table (i int)

# For comparing the zone configurations before restore.
query TT
SHOW ZONE CONFIGURATION FROM TABLE non_mr_table
----
RANGE default ALTER RANGE default CONFIGURE ZONE USING
range_min_bytes = 134217728,
range_max_bytes = 536870912,
gc.ttlseconds = 90000,
num_replicas = 3,
constraints = '[]',
lease_preferences = '[]'

statement ok
BACKUP TABLE non_mr_table TO 'nodelocal://1/non_mr_table/'

statement error cannot restore individual table
statement ok
DROP TABLE non_mr_table

statement ok
RESTORE TABLE non_mr_table FROM 'nodelocal://1/non_mr_table/'

query TT
SHOW ZONE CONFIGURATION FROM TABLE non_mr_table
----
RANGE default ALTER RANGE default CONFIGURE ZONE USING
range_min_bytes = 134217728,
range_max_bytes = 536870912,
gc.ttlseconds = 90000,
num_replicas = 3,
constraints = '[]',
lease_preferences = '[]'

statement ok
RESTORE TABLE non_mr_table FROM 'nodelocal://1/non_mr_table/' WITH into_db = 'mr-backup-1'

statement ok
USE 'mr-backup-1'

# Verify that the zone configurations of non_mr_table that is restored to a multi-region database are set.
query TT
SHOW ZONE CONFIGURATION FROM TABLE non_mr_table
----
DATABASE "mr-backup-1" ALTER DATABASE "mr-backup-1" CONFIGURE ZONE USING
range_min_bytes = 134217728,
range_max_bytes = 536870912,
gc.ttlseconds = 90000,
num_replicas = 5,
num_voters = 3,
constraints = '{+region=ap-southeast-2: 1, +region=ca-central-1: 1, +region=us-east-1: 1}',
voter_constraints = '[+region=ca-central-1]',
lease_preferences = '[[+region=ca-central-1]]'

query TT
SHOW CREATE TABLE non_mr_table
----
non_mr_table CREATE TABLE public.non_mr_table (
i INT8 NULL,
rowid INT8 NOT VISIBLE NOT NULL DEFAULT unique_rowid(),
CONSTRAINT "primary" PRIMARY KEY (rowid ASC),
FAMILY "primary" (i, rowid)
) LOCALITY REGIONAL BY TABLE IN PRIMARY REGION


statement ok
USE "mr-backup-2"

statement ok
DROP TABLE regional_by_row_table;
DROP TABLE regional_by_table_in_primary_region;
DROP TABLE regional_by_table_in_ca_central_1;
DROP TABLE global_table;

# Restore individual tables into a database with the same regions.
subtest restore_tables_into_database_with_same_regions

statement error cannot restore REGIONAL BY ROW table regional_by_row_table .* individually into a multi-region database
RESTORE TABLE regional_by_row_table FROM 'nodelocal://1/mr-backup-2/'

statement ok
RESTORE TABLE regional_by_table_in_primary_region FROM 'nodelocal://1/mr-backup-2/'

query TT
SHOW CREATE TABLE regional_by_table_in_primary_region
----
regional_by_table_in_primary_region CREATE TABLE public.regional_by_table_in_primary_region (
pk INT8 NOT NULL,
i INT8 NULL,
CONSTRAINT "primary" PRIMARY KEY (pk ASC),
FAMILY fam_0_pk_i (pk, i)
) LOCALITY REGIONAL BY TABLE IN PRIMARY REGION


statement ok
RESTORE TABLE regional_by_table_in_ca_central_1 FROM 'nodelocal://1/mr-backup-2/'

# REGIONAL BY TABLE tables with a specific region are permitted if that region
# exists in the database.
query TT
SHOW CREATE TABLE regional_by_table_in_ca_central_1
----
regional_by_table_in_ca_central_1 CREATE TABLE public.regional_by_table_in_ca_central_1 (
pk INT8 NOT NULL,
i INT8 NULL,
CONSTRAINT "primary" PRIMARY KEY (pk ASC),
FAMILY fam_0_pk_i (pk, i)
) LOCALITY REGIONAL BY TABLE IN "ca-central-1"

statement ok
RESTORE TABLE global_table FROM 'nodelocal://1/mr-backup-2/'

query TT
SHOW CREATE TABLE global_table
----
global_table CREATE TABLE public.global_table (
pk INT8 NOT NULL,
i INT8 NULL,
CONSTRAINT "primary" PRIMARY KEY (pk ASC),
FAMILY fam_0_pk_i (pk, i)
) LOCALITY GLOBAL

# Restore individual tables into a database with different regions.
subtest restore_tables_into_database_with_different_regions

# Create a database with different regions (missing ca-central-1).
statement ok
CREATE DATABASE "mr-restore-1" primary region "ap-southeast-2" regions "us-east-1"

statement ok
RESTORE TABLE "mr-backup-2".global_table FROM 'nodelocal://1/mr-backup-2/' WITH into_db='mr-restore-1';
USE "mr-restore-1";

query TT
SHOW CREATE TABLE global_table
----
global_table CREATE TABLE public.global_table (
pk INT8 NOT NULL,
i INT8 NULL,
CONSTRAINT "primary" PRIMARY KEY (pk ASC),
FAMILY fam_0_pk_i (pk, i)
) LOCALITY GLOBAL

query TT
SHOW ZONE CONFIGURATION FROM TABLE global_table
----
TABLE global_table ALTER TABLE global_table CONFIGURE ZONE USING
range_min_bytes = 134217728,
range_max_bytes = 536870912,
gc.ttlseconds = 90000,
global_reads = true,
num_replicas = 4,
num_voters = 3,
constraints = '{+region=ap-southeast-2: 1, +region=us-east-1: 1}',
voter_constraints = '[+region=ap-southeast-2]',
lease_preferences = '[[+region=ap-southeast-2]]'


statement ok
RESTORE TABLE "mr-backup-2".regional_by_table_in_primary_region FROM 'nodelocal://1/mr-backup-2/' WITH into_db='mr-restore-1';

query TT
SHOW CREATE TABLE regional_by_table_in_primary_region
----
regional_by_table_in_primary_region CREATE TABLE public.regional_by_table_in_primary_region (
pk INT8 NOT NULL,
i INT8 NULL,
CONSTRAINT "primary" PRIMARY KEY (pk ASC),
FAMILY fam_0_pk_i (pk, i)
) LOCALITY REGIONAL BY TABLE IN PRIMARY REGION

query TT
SHOW ZONE CONFIGURATION FROM TABLE regional_by_table_in_primary_region
----
DATABASE "mr-restore-1" ALTER DATABASE "mr-restore-1" CONFIGURE ZONE USING
range_min_bytes = 134217728,
range_max_bytes = 536870912,
gc.ttlseconds = 90000,
num_replicas = 4,
num_voters = 3,
constraints = '{+region=ap-southeast-2: 1, +region=us-east-1: 1}',
voter_constraints = '[+region=ap-southeast-2]',
lease_preferences = '[[+region=ap-southeast-2]]'

statement error "crdb_internal_region" is not compatible with type "crdb_internal_region" existing in cluster: could not find enum value "ca-central-1" in "crdb_internal_region"
RESTORE TABLE "mr-backup-2".regional_by_table_in_ap_southeast_2 FROM 'nodelocal://1/mr-backup-2/' WITH into_db='mr-restore-1';

# Cannot restore a REGIONAL BY TABLE table that has different regions.
statement error cannot restore REGIONAL BY TABLE regional_by_table_in_ca_central_1 IN REGION "ca-central-1" \(table ID: [0-9]+\) into database "mr-restore-1"; region "ca-central-1" not found in database regions "ap-southeast-2", "us-east-1"
RESTORE TABLE "mr-backup-2".regional_by_table_in_ca_central_1 FROM 'nodelocal://1/mr-backup-2/' WITH into_db='mr-restore-1'

statement error cannot restore REGIONAL BY ROW table regional_by_row_table
RESTORE TABLE "mr-backup-2".regional_by_row_table FROM 'nodelocal://1/mr-backup-2/' WITH into_db='mr-restore-1'