Skip to content

Commit 19e2837

Browse files
authored
feat(bigtable): Allow configuring multicluster routing and isolation (#11980)
* feat(bigtable): Allow configuring multicluster routing and isolation * add comments * add proper comments
1 parent f7cffc2 commit 19e2837

File tree

3 files changed

+822
-82
lines changed

3 files changed

+822
-82
lines changed

bigtable/admin.go

Lines changed: 257 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,10 @@ func materializedlViewPath(project, instance, materializedView string) string {
138138
return fmt.Sprintf("%s/materializedViews/%s", instancePrefix(project, instance), materializedView)
139139
}
140140

141+
func appProfilePath(project, instance, appProfile string) string {
142+
return fmt.Sprintf("%s/appProfiles/%s", instancePrefix(project, instance), appProfile)
143+
}
144+
141145
// EncryptionInfo represents the encryption info of a table.
142146
type EncryptionInfo struct {
143147
Status *Status
@@ -1843,33 +1847,125 @@ func (iac *InstanceAdminClient) InstanceIAM(instanceID string) *iam.Handle {
18431847

18441848
// Routing policies.
18451849
const (
1850+
// Deprecated: Use MultiClusterRoutingUseAnyConfig instead.
18461851
// MultiClusterRouting is a policy that allows read/write requests to be
18471852
// routed to any cluster in the instance. Requests will will fail over to
18481853
// another cluster in the event of transient errors or delays. Choosing
18491854
// this option sacrifices read-your-writes consistency to improve
18501855
// availability.
18511856
MultiClusterRouting = "multi_cluster_routing_use_any"
1857+
// Deprecated: Use SingleClusterRoutingConfig instead.
18521858
// SingleClusterRouting is a policy that unconditionally routes all
18531859
// read/write requests to a specific cluster. This option preserves
18541860
// read-your-writes consistency, but does not improve availability.
18551861
SingleClusterRouting = "single_cluster_routing"
18561862
)
18571863

1858-
// ProfileConf contains the information necessary to create an profile
1864+
// ProfileConf contains the information necessary to create a profile
18591865
type ProfileConf struct {
1860-
Name string
1861-
ProfileID string
1862-
InstanceID string
1863-
Etag string
1864-
Description string
1865-
RoutingPolicy string
1866-
ClusterID string
1866+
Name string
1867+
ProfileID string
1868+
InstanceID string
1869+
Etag string
1870+
Description string
1871+
1872+
RoutingConfig RoutingPolicyConfig
1873+
Isolation AppProfileIsolation
1874+
1875+
// Deprecated: Use RoutingConfig instead.
1876+
// Ignored when RoutingConfig is set.
1877+
RoutingPolicy string
1878+
// Deprecated: Use RoutingConfig with SingleClusterRoutingConfig instead.
1879+
// Ignored when RoutingConfig is set.
1880+
// To use with RoutingPolicy field while specifying SingleClusterRouting.
1881+
ClusterID string
1882+
// Deprecated: Use RoutingConfig with SingleClusterRoutingConfig instead.
1883+
// Ignored when RoutingConfig is set.
1884+
// To use with RoutingPolicy field while specifying SingleClusterRouting.
18671885
AllowTransactionalWrites bool
18681886

18691887
// If true, warnings are ignored
18701888
IgnoreWarnings bool
18711889
}
18721890

1891+
func setIsolation(profile *btapb.AppProfile, isolation AppProfileIsolation) error {
1892+
if isolation != nil {
1893+
switch cfg := isolation.(type) {
1894+
case *StandardIsolation:
1895+
profile.Isolation = &btapb.AppProfile_StandardIsolation_{
1896+
StandardIsolation: &btapb.AppProfile_StandardIsolation{
1897+
Priority: btapb.AppProfile_Priority(cfg.Priority),
1898+
},
1899+
}
1900+
case *DataBoostIsolationReadOnly:
1901+
dataBoostProto := &btapb.AppProfile_DataBoostIsolationReadOnly{}
1902+
cbo := btapb.AppProfile_DataBoostIsolationReadOnly_ComputeBillingOwner(cfg.ComputeBillingOwner)
1903+
dataBoostProto.ComputeBillingOwner = &cbo
1904+
profile.Isolation = &btapb.AppProfile_DataBoostIsolationReadOnly_{DataBoostIsolationReadOnly: dataBoostProto}
1905+
default:
1906+
return fmt.Errorf("bigtable: unknown isolation config type: %T", cfg)
1907+
}
1908+
}
1909+
return nil
1910+
}
1911+
1912+
func setRoutingPolicy(appProfile *btapb.AppProfile, rpc RoutingPolicyConfig, routingPolicy optional.String,
1913+
clusterID string, allowTransactionalWrites bool, allowNil bool) error {
1914+
if allowNil && routingPolicy == nil && rpc == nil {
1915+
return nil
1916+
}
1917+
if rpc != nil {
1918+
switch cfg := rpc.(type) {
1919+
case *MultiClusterRoutingUseAnyConfig:
1920+
appProfile.RoutingPolicy = &btapb.AppProfile_MultiClusterRoutingUseAny_{
1921+
MultiClusterRoutingUseAny: &btapb.AppProfile_MultiClusterRoutingUseAny{
1922+
ClusterIds: cfg.ClusterIDs,
1923+
},
1924+
}
1925+
if cfg.Affinity != nil {
1926+
switch cfg.Affinity.(type) {
1927+
case *RowAffinity:
1928+
appProfile.GetMultiClusterRoutingUseAny().Affinity = &btapb.AppProfile_MultiClusterRoutingUseAny_RowAffinity_{
1929+
RowAffinity: &btapb.AppProfile_MultiClusterRoutingUseAny_RowAffinity{},
1930+
}
1931+
default:
1932+
return errors.New("bigtable: invalid affinity in MultiClusterRoutingUseAnyConfig")
1933+
}
1934+
}
1935+
case *SingleClusterRoutingConfig:
1936+
appProfile.RoutingPolicy = &btapb.AppProfile_SingleClusterRouting_{
1937+
SingleClusterRouting: &btapb.AppProfile_SingleClusterRouting{
1938+
ClusterId: cfg.ClusterID,
1939+
AllowTransactionalWrites: cfg.AllowTransactionalWrites,
1940+
},
1941+
}
1942+
default:
1943+
return fmt.Errorf("bigtable: unknown RoutingConfig type: %T", cfg)
1944+
}
1945+
} else { // Fallback to deprecated fields
1946+
if routingPolicy == nil {
1947+
return errors.New("bigtable: at least one of RoutingPolicy or RoutingConfig must be set")
1948+
}
1949+
1950+
switch routingPolicy {
1951+
case MultiClusterRouting:
1952+
appProfile.RoutingPolicy = &btapb.AppProfile_MultiClusterRoutingUseAny_{
1953+
MultiClusterRoutingUseAny: &btapb.AppProfile_MultiClusterRoutingUseAny{},
1954+
}
1955+
case SingleClusterRouting:
1956+
appProfile.RoutingPolicy = &btapb.AppProfile_SingleClusterRouting_{
1957+
SingleClusterRouting: &btapb.AppProfile_SingleClusterRouting{
1958+
ClusterId: clusterID,
1959+
AllowTransactionalWrites: allowTransactionalWrites,
1960+
},
1961+
}
1962+
default:
1963+
return errors.New("bigtable: invalid RoutingPolicy " + optional.ToString(routingPolicy))
1964+
}
1965+
}
1966+
return nil
1967+
}
1968+
18731969
// ProfileIterator iterates over profiles.
18741970
type ProfileIterator struct {
18751971
items []*btapb.AppProfile
@@ -1882,11 +1978,22 @@ type ProfileAttrsToUpdate struct {
18821978
// If set, updates the description.
18831979
Description optional.String
18841980

1885-
//If set, updates the routing policy.
1981+
// If set, updates the routing policy.
1982+
// Takes precedence over deprecated RoutingPolicy, ClusterID and AllowTransactionalWrites.
1983+
RoutingConfig RoutingPolicyConfig
1984+
1985+
// If set, updates the isolation options.
1986+
Isolation AppProfileIsolation
1987+
1988+
// If set, updates the routing policy.
1989+
// Deprecated: Use RoutingConfig instead.
18861990
RoutingPolicy optional.String
18871991

1888-
//If RoutingPolicy is updated to SingleClusterRouting, set these fields as well.
1889-
ClusterID string
1992+
// If RoutingPolicy is updated to SingleClusterRouting, set this field as well.
1993+
// Deprecated: Use RoutingConfig with SingleClusterRoutingConfig instead
1994+
ClusterID string
1995+
// If RoutingPolicy is updated to SingleClusterRouting, set this field as well.
1996+
// Deprecated: Use RoutingConfig with SingleClusterRoutingConfig instead
18901997
AllowTransactionalWrites bool
18911998

18921999
// If true, warnings are ignored
@@ -1900,12 +2007,131 @@ func (p *ProfileAttrsToUpdate) GetFieldMaskPath() []string {
19002007
path = append(path, "description")
19012008
}
19022009

1903-
if p.RoutingPolicy != nil {
2010+
if p.RoutingConfig != nil {
2011+
path = append(path, p.RoutingConfig.getFieldMaskPath())
2012+
} else if p.RoutingPolicy != nil {
19042013
path = append(path, optional.ToString(p.RoutingPolicy))
19052014
}
2015+
if p.Isolation != nil {
2016+
path = append(path, p.Isolation.getFieldMaskPath())
2017+
}
2018+
19062019
return path
19072020
}
19082021

2022+
// RoutingPolicyConfig represents the configuration for a specific routing policy.
2023+
type RoutingPolicyConfig interface {
2024+
isRoutingPolicyConfig()
2025+
getFieldMaskPath() string
2026+
}
2027+
2028+
// SingleClusterRoutingConfig is a policy that unconditionally routes all
2029+
// read/write requests to a specific cluster. This option preserves
2030+
// read-your-writes consistency, but does not improve availability.
2031+
type SingleClusterRoutingConfig struct {
2032+
// The cluster to which read/write requests should be routed.
2033+
ClusterID string
2034+
// Whether or not `CheckAndMutateRow` and `ReadModifyWriteRow` requests are
2035+
// allowed by this app profile. It is unsafe to send these requests to
2036+
// the same table/row/column in multiple clusters.
2037+
AllowTransactionalWrites bool
2038+
}
2039+
2040+
func (*SingleClusterRoutingConfig) isRoutingPolicyConfig() {}
2041+
func (*SingleClusterRoutingConfig) getFieldMaskPath() string { return "single_cluster_routing" }
2042+
2043+
// MultiClusterRoutingUseAnyConfig is a policy whererin read/write requests are
2044+
// routed to the nearest cluster in the instance, and
2045+
// will fail over to the nearest cluster that is available in the event of
2046+
// transient errors or delays. Clusters in a region are considered
2047+
// equidistant. Choosing this option sacrifices read-your-writes consistency
2048+
// to improve availability.
2049+
type MultiClusterRoutingUseAnyConfig struct {
2050+
// The set of clusters to route to. The order is ignored; clusters will be
2051+
// tried in order of distance. If left empty, all clusters are eligible.
2052+
ClusterIDs []string
2053+
2054+
// Possible algorithms for routing affinity. If enabled, Bigtable will
2055+
// route between equidistant clusters in a deterministic order rather than
2056+
// choosing randomly.
2057+
Affinity MultiClusterRoutingUseAnyAffinity
2058+
}
2059+
2060+
func (*MultiClusterRoutingUseAnyConfig) isRoutingPolicyConfig() {}
2061+
func (*MultiClusterRoutingUseAnyConfig) getFieldMaskPath() string {
2062+
return "multi_cluster_routing_use_any"
2063+
}
2064+
2065+
// MultiClusterRoutingUseAnyAffinity represents the configuration for a specific affinity strategy.
2066+
type MultiClusterRoutingUseAnyAffinity interface {
2067+
isMultiClusterRoutingUseAnyAffinity()
2068+
}
2069+
2070+
// RowAffinity enables row-based affinity.
2071+
// If enabled, Bigtable will route the request based on the row key of the
2072+
// request, rather than randomly. Instead, each row key will be assigned
2073+
// to a cluster, and will stick to that cluster.
2074+
type RowAffinity struct{}
2075+
2076+
func (*RowAffinity) isMultiClusterRoutingUseAnyAffinity() {}
2077+
2078+
// AppProfileIsolation represents the configuration for a specific traffic isolation policy.
2079+
type AppProfileIsolation interface {
2080+
isAppProfileIsolation()
2081+
getFieldMaskPath() string
2082+
}
2083+
2084+
// StandardIsolation configures standard traffic isolation.
2085+
type StandardIsolation struct {
2086+
Priority AppProfilePriority
2087+
}
2088+
2089+
func (*StandardIsolation) isAppProfileIsolation() {}
2090+
func (*StandardIsolation) getFieldMaskPath() string { return "standard_isolation" }
2091+
2092+
// AppProfilePriority represents possible priorities for an app profile.
2093+
type AppProfilePriority int32
2094+
2095+
const (
2096+
// AppProfilePriorityUnspecified is the default value. Mapped to PRIORITY_HIGH (the legacy behavior) on creation.
2097+
AppProfilePriorityUnspecified AppProfilePriority = AppProfilePriority(btapb.AppProfile_PRIORITY_UNSPECIFIED)
2098+
// AppProfilePriorityLow represents the lowest priority.
2099+
AppProfilePriorityLow AppProfilePriority = AppProfilePriority(btapb.AppProfile_PRIORITY_LOW)
2100+
// AppProfilePriorityMedium represents the medium priority.
2101+
AppProfilePriorityMedium AppProfilePriority = AppProfilePriority(btapb.AppProfile_PRIORITY_MEDIUM)
2102+
// AppProfilePriorityHigh represents the highest priority.
2103+
AppProfilePriorityHigh AppProfilePriority = AppProfilePriority(btapb.AppProfile_PRIORITY_HIGH)
2104+
)
2105+
2106+
// DataBoostIsolationReadOnly configures Data Boost isolation.
2107+
// Data Boost is a serverless compute capability that lets you run
2108+
// high-throughput read jobs and queries on your Bigtable data, without
2109+
// impacting the performance of the clusters that handle your application
2110+
// traffic. Data Boost supports read-only use cases with single-cluster
2111+
// routing.
2112+
type DataBoostIsolationReadOnly struct {
2113+
// Compute Billing Owner specifies how usage should be accounted when using
2114+
// Data Boost. Compute Billing Owner also configures which Cloud Project is
2115+
// charged for relevant quota.
2116+
ComputeBillingOwner IsolationComputeBillingOwner
2117+
}
2118+
2119+
func (*DataBoostIsolationReadOnly) isAppProfileIsolation() {}
2120+
func (*DataBoostIsolationReadOnly) getFieldMaskPath() string { return "data_boost_isolation_read_only" }
2121+
2122+
// IsolationComputeBillingOwner specifies how usage should be accounted when using
2123+
// Data Boost. Compute Billing Owner also configures which Cloud Project is
2124+
// charged for relevant quota.
2125+
type IsolationComputeBillingOwner int32
2126+
2127+
const (
2128+
// ComputeBillingOwnerUnspecified is the default value.
2129+
ComputeBillingOwnerUnspecified IsolationComputeBillingOwner = IsolationComputeBillingOwner(btapb.AppProfile_DataBoostIsolationReadOnly_COMPUTE_BILLING_OWNER_UNSPECIFIED)
2130+
// HostPays indicates that the host Cloud Project containing the targeted Bigtable Instance /
2131+
// Table pays for compute.
2132+
HostPays IsolationComputeBillingOwner = IsolationComputeBillingOwner(btapb.AppProfile_DataBoostIsolationReadOnly_HOST_PAYS)
2133+
)
2134+
19092135
// PageInfo supports pagination. See https://godoc.org/google.golang.org/api/iterator package for details.
19102136
func (it *ProfileIterator) PageInfo() *iterator.PageInfo {
19112137
return it.pageInfo
@@ -1932,24 +2158,14 @@ func (iac *InstanceAdminClient) CreateAppProfile(ctx context.Context, profile Pr
19322158
Description: profile.Description,
19332159
}
19342160

1935-
if profile.RoutingPolicy == "" {
1936-
return nil, errors.New("invalid routing policy")
2161+
err := setRoutingPolicy(appProfile, profile.RoutingConfig, optional.String(profile.RoutingPolicy), profile.ClusterID, profile.AllowTransactionalWrites, false)
2162+
if err != nil {
2163+
return nil, err
19372164
}
19382165

1939-
switch profile.RoutingPolicy {
1940-
case MultiClusterRouting:
1941-
appProfile.RoutingPolicy = &btapb.AppProfile_MultiClusterRoutingUseAny_{
1942-
MultiClusterRoutingUseAny: &btapb.AppProfile_MultiClusterRoutingUseAny{},
1943-
}
1944-
case SingleClusterRouting:
1945-
appProfile.RoutingPolicy = &btapb.AppProfile_SingleClusterRouting_{
1946-
SingleClusterRouting: &btapb.AppProfile_SingleClusterRouting{
1947-
ClusterId: profile.ClusterID,
1948-
AllowTransactionalWrites: profile.AllowTransactionalWrites,
1949-
},
1950-
}
1951-
default:
1952-
return nil, errors.New("invalid routing policy")
2166+
err = setIsolation(appProfile, profile.Isolation)
2167+
if err != nil {
2168+
return nil, err
19532169
}
19542170

19552171
return iac.iClient.CreateAppProfile(ctx, &btapb.CreateAppProfileRequest{
@@ -2012,32 +2228,28 @@ func (iac *InstanceAdminClient) ListAppProfiles(ctx context.Context, instanceID
20122228
// UpdateAppProfile updates an app profile within an instance.
20132229
// updateAttrs should be set. If unset, all fields will be replaced.
20142230
func (iac *InstanceAdminClient) UpdateAppProfile(ctx context.Context, instanceID, profileID string, updateAttrs ProfileAttrsToUpdate) error {
2231+
fmt.Println("Entering UpdateAppProfile")
20152232
ctx = mergeOutgoingMetadata(ctx, iac.md)
20162233

20172234
profile := &btapb.AppProfile{
2018-
Name: "projects/" + iac.project + "/instances/" + instanceID + "/appProfiles/" + profileID,
2235+
Name: appProfilePath(iac.project, instanceID, profileID),
20192236
}
20202237

20212238
if updateAttrs.Description != nil {
20222239
profile.Description = optional.ToString(updateAttrs.Description)
20232240
}
2024-
if updateAttrs.RoutingPolicy != nil {
2025-
switch optional.ToString(updateAttrs.RoutingPolicy) {
2026-
case MultiClusterRouting:
2027-
profile.RoutingPolicy = &btapb.AppProfile_MultiClusterRoutingUseAny_{
2028-
MultiClusterRoutingUseAny: &btapb.AppProfile_MultiClusterRoutingUseAny{},
2029-
}
2030-
case SingleClusterRouting:
2031-
profile.RoutingPolicy = &btapb.AppProfile_SingleClusterRouting_{
2032-
SingleClusterRouting: &btapb.AppProfile_SingleClusterRouting{
2033-
ClusterId: updateAttrs.ClusterID,
2034-
AllowTransactionalWrites: updateAttrs.AllowTransactionalWrites,
2035-
},
2036-
}
2037-
default:
2038-
return errors.New("invalid routing policy")
2039-
}
2241+
2242+
err := setRoutingPolicy(profile, updateAttrs.RoutingConfig, updateAttrs.RoutingPolicy,
2243+
updateAttrs.ClusterID, updateAttrs.AllowTransactionalWrites, true)
2244+
if err != nil {
2245+
return err
2246+
}
2247+
2248+
err = setIsolation(profile, updateAttrs.Isolation)
2249+
if err != nil {
2250+
return err
20402251
}
2252+
20412253
patchRequest := &btapb.UpdateAppProfileRequest{
20422254
AppProfile: profile,
20432255
UpdateMask: &field_mask.FieldMask{

0 commit comments

Comments
 (0)