Skip to content

Commit ed8c316

Browse files
authored
feature(spanner): low-cost instances (#6895)
Support the provisioning of Spanner instances using `processing_units` as an alternative to `node_count`. 1 node == 1,000 processing units.
1 parent 44d8a9e commit ed8c316

File tree

5 files changed

+102
-14
lines changed

5 files changed

+102
-14
lines changed

google/cloud/spanner/create_instance_request_builder.h

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,15 +47,14 @@ class CreateInstanceRequestBuilder {
4747
default;
4848

4949
/**
50-
* Constructor requires Instance and Cloud Spanner instance config name. It
51-
* sets node_count = 1, and display_name = instance_id as the default values.
50+
* Constructor requires Instance and Cloud Spanner instance config name.
51+
* The display_name is set to a default value of in.instance_id().
5252
*/
5353
CreateInstanceRequestBuilder(Instance const& in, std::string config) {
5454
request_.set_parent("projects/" + in.project_id());
5555
request_.set_instance_id(in.instance_id());
5656
request_.mutable_instance()->set_name(in.FullName());
5757
request_.mutable_instance()->set_display_name(in.instance_id());
58-
request_.mutable_instance()->set_node_count(1);
5958
request_.mutable_instance()->set_config(std::move(config));
6059
}
6160

@@ -79,6 +78,16 @@ class CreateInstanceRequestBuilder {
7978
return std::move(*this);
8079
}
8180

81+
CreateInstanceRequestBuilder& SetProcessingUnits(int processing_units) & {
82+
request_.mutable_instance()->set_processing_units(processing_units);
83+
return *this;
84+
}
85+
86+
CreateInstanceRequestBuilder&& SetProcessingUnits(int processing_units) && {
87+
request_.mutable_instance()->set_processing_units(processing_units);
88+
return std::move(*this);
89+
}
90+
8291
CreateInstanceRequestBuilder& SetLabels(
8392
std::map<std::string, std::string> const& labels) & {
8493
for (auto const& pair : labels) {
@@ -98,9 +107,21 @@ class CreateInstanceRequestBuilder {
98107
}
99108

100109
google::spanner::admin::instance::v1::CreateInstanceRequest& Build() & {
110+
// Preserve original behavior of defaulting node_count to 1.
111+
if (request_.instance().processing_units() == 0) {
112+
if (request_.instance().node_count() == 0) {
113+
request_.mutable_instance()->set_node_count(1);
114+
}
115+
}
101116
return request_;
102117
}
103118
google::spanner::admin::instance::v1::CreateInstanceRequest&& Build() && {
119+
// Preserve original behavior of defaulting node_count to 1.
120+
if (request_.instance().processing_units() == 0) {
121+
if (request_.instance().node_count() == 0) {
122+
request_.mutable_instance()->set_node_count(1);
123+
}
124+
}
104125
return std::move(request_);
105126
}
106127

google/cloud/spanner/create_instance_request_builder_test.cc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,14 +70,14 @@ TEST(CreateInstanceRequestBuilder, Lvalue) {
7070

7171
auto builder = CreateInstanceRequestBuilder(in, expected_config);
7272
auto req = builder.SetDisplayName(expected_display_name)
73-
.SetNodeCount(1)
73+
.SetProcessingUnits(500)
7474
.SetLabels({{"key", "value"}})
7575
.Build();
7676
EXPECT_EQ("projects/test-project", req.parent());
7777
EXPECT_EQ("test-instance", req.instance_id());
7878
EXPECT_EQ(expected_name, req.instance().name());
7979
EXPECT_EQ(expected_config, req.instance().config());
80-
EXPECT_EQ(1, req.instance().node_count());
80+
EXPECT_EQ(500, req.instance().processing_units());
8181
EXPECT_EQ(1, req.instance().labels_size());
8282
EXPECT_EQ("value", req.instance().labels().at("key"));
8383
EXPECT_EQ(expected_display_name, req.instance().display_name());

google/cloud/spanner/samples/samples.cc

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,19 +86,56 @@ void CreateInstance(google::cloud::spanner::InstanceAdminClient client,
8686
.Build())
8787
.get();
8888
if (!instance) throw std::runtime_error(instance.status().message());
89-
std::cout << "Created instance [" << in << "]\n";
89+
std::cout << "Created instance [" << in << "]:\n" << instance->DebugString();
9090
}
9191
//! [END spanner_create_instance] [create-instance]
9292

93+
// [START spanner_create_instance_with_processing_units]
94+
void CreateInstanceWithProcessingUnits(
95+
google::cloud::spanner::InstanceAdminClient client,
96+
std::string const& project_id, std::string const& instance_id,
97+
std::string const& display_name, std::string const& config) {
98+
namespace spanner = google::cloud::spanner;
99+
spanner::Instance in(project_id, instance_id);
100+
101+
std::string instance_config =
102+
"projects/" + project_id + "/instanceConfigs/" + config;
103+
auto instance =
104+
client
105+
.CreateInstance(
106+
spanner::CreateInstanceRequestBuilder(in, instance_config)
107+
.SetDisplayName(display_name)
108+
.SetProcessingUnits(500)
109+
.SetLabels({{"cloud_spanner_samples", "true"}})
110+
.Build())
111+
.get();
112+
if (!instance) throw std::runtime_error(instance.status().message());
113+
std::cout << "Created instance [" << in << "]:\n" << instance->DebugString();
114+
115+
instance = client.GetInstance(in);
116+
if (!instance) throw std::runtime_error(instance.status().message());
117+
std::cout << "Instance " << in << " has " << instance->processing_units()
118+
<< " processing units.\n";
119+
}
120+
// [END spanner_create_instance_with_processing_units]
121+
93122
void CreateInstanceCommand(std::vector<std::string> argv) {
123+
bool low_cost = !argv.empty() && argv.front() == "--low-cost";
124+
if (low_cost) argv.erase(argv.begin());
94125
if (argv.size() != 3 && argv.size() != 4) {
95126
throw std::runtime_error(
96-
"create-instance <project-id> <instance-id> <display_name> [config]");
127+
"create-instance [--low-cost] <project-id> <instance-id>"
128+
" <display_name> [config]");
97129
}
98130
google::cloud::spanner::InstanceAdminClient client(
99131
google::cloud::spanner::MakeInstanceAdminConnection());
100132
std::string config = argv.size() == 4 ? argv[3] : "regional-us-central1";
101-
CreateInstance(std::move(client), argv[0], argv[1], argv[2], config);
133+
if (low_cost) {
134+
CreateInstanceWithProcessingUnits(std::move(client), argv[0], argv[1],
135+
argv[2], config);
136+
} else {
137+
CreateInstance(std::move(client), argv[0], argv[1], argv[2], config);
138+
}
102139
}
103140

104141
//! [update-instance]
@@ -3418,6 +3455,21 @@ void RunAllSlowInstanceTests(
34183455

34193456
std::cout << "\nRunning delete-instance sample" << std::endl;
34203457
DeleteInstance(instance_admin_client, project_id, crud_instance_id);
3458+
3459+
if (!emulator) {
3460+
std::cout
3461+
<< "\nRunning spanner_create_instance_with_processing_units sample"
3462+
<< std::endl;
3463+
// TODO(#6894): Change "regional-us-central1" to `config` when low-cost
3464+
// instances are generally available (well, at least in the regions that
3465+
// `PickConfig()` might return).
3466+
CreateInstanceWithProcessingUnits(
3467+
instance_admin_client, project_id, crud_instance_id,
3468+
"Test Low-Cost Instance", "regional-us-central1");
3469+
3470+
std::cout << "\nRunning delete-instance sample" << std::endl;
3471+
DeleteInstance(instance_admin_client, project_id, crud_instance_id);
3472+
}
34213473
}
34223474

34233475
void RunAll(bool emulator) {

google/cloud/spanner/update_instance_request_builder.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,14 @@ class UpdateInstanceRequestBuilder {
9393
SetNodeCountImpl(node_count);
9494
return std::move(*this);
9595
}
96+
UpdateInstanceRequestBuilder& SetProcessingUnits(int processing_units) & {
97+
SetProcessingUnitsImpl(processing_units);
98+
return *this;
99+
}
100+
UpdateInstanceRequestBuilder&& SetProcessingUnits(int processing_units) && {
101+
SetProcessingUnitsImpl(processing_units);
102+
return std::move(*this);
103+
}
96104
UpdateInstanceRequestBuilder& AddLabels(
97105
std::map<std::string, std::string> const& labels) & {
98106
AddLabelsImpl(labels);
@@ -138,6 +146,13 @@ class UpdateInstanceRequestBuilder {
138146
}
139147
request_.mutable_instance()->set_node_count(node_count);
140148
}
149+
void SetProcessingUnitsImpl(int processing_units) {
150+
if (!google::protobuf::util::FieldMaskUtil::IsPathInFieldMask(
151+
"processing_units", request_.field_mask())) {
152+
request_.mutable_field_mask()->add_paths("processing_units");
153+
}
154+
request_.mutable_instance()->set_processing_units(processing_units);
155+
}
141156
void AddLabelsImpl(std::map<std::string, std::string> const& labels) {
142157
if (!google::protobuf::util::FieldMaskUtil::IsPathInFieldMask(
143158
"labels", request_.field_mask())) {

google/cloud/spanner/update_instance_request_builder_test.cc

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -106,19 +106,19 @@ TEST(UpdateInstanceRequestBuilder, SetLabels) {
106106
instance.set_node_count(1);
107107
instance.mutable_labels()->insert({"key", "value"});
108108
auto builder = UpdateInstanceRequestBuilder(instance);
109-
auto req = builder.SetNodeCount(2)
109+
auto req = builder.SetProcessingUnits(500)
110110
.SetDisplayName(expected_display_name)
111111
.SetLabels({{"newkey", "newvalue"}})
112112
.Build();
113113
EXPECT_EQ(expected_name, req.instance().name());
114114
EXPECT_EQ(expected_display_name, req.instance().display_name());
115-
EXPECT_EQ(2, req.instance().node_count());
115+
EXPECT_EQ(500, req.instance().processing_units());
116116
EXPECT_EQ(1, req.instance().labels_size());
117117
EXPECT_EQ("newvalue", req.instance().labels().at("newkey"));
118118
EXPECT_TRUE(google::protobuf::util::FieldMaskUtil::IsPathInFieldMask(
119119
"display_name", req.field_mask()));
120120
EXPECT_TRUE(google::protobuf::util::FieldMaskUtil::IsPathInFieldMask(
121-
"node_count", req.field_mask()));
121+
"processing_units", req.field_mask()));
122122
EXPECT_TRUE(google::protobuf::util::FieldMaskUtil::IsPathInFieldMask(
123123
"labels", req.field_mask()));
124124
}
@@ -133,19 +133,19 @@ TEST(UpdateInstanceRequestBuilder, SetLabelsRvalueReference) {
133133
instance.set_node_count(1);
134134
instance.mutable_labels()->insert({"key", "value"});
135135
auto req = UpdateInstanceRequestBuilder(instance)
136-
.SetNodeCount(2)
136+
.SetProcessingUnits(500)
137137
.SetDisplayName(expected_display_name)
138138
.SetLabels({{"newkey", "newvalue"}})
139139
.Build();
140140
EXPECT_EQ(expected_name, req.instance().name());
141141
EXPECT_EQ(expected_display_name, req.instance().display_name());
142-
EXPECT_EQ(2, req.instance().node_count());
142+
EXPECT_EQ(500, req.instance().processing_units());
143143
EXPECT_EQ(1, req.instance().labels_size());
144144
EXPECT_EQ("newvalue", req.instance().labels().at("newkey"));
145145
EXPECT_TRUE(google::protobuf::util::FieldMaskUtil::IsPathInFieldMask(
146146
"display_name", req.field_mask()));
147147
EXPECT_TRUE(google::protobuf::util::FieldMaskUtil::IsPathInFieldMask(
148-
"node_count", req.field_mask()));
148+
"processing_units", req.field_mask()));
149149
EXPECT_TRUE(google::protobuf::util::FieldMaskUtil::IsPathInFieldMask(
150150
"labels", req.field_mask()));
151151
}

0 commit comments

Comments
 (0)