Skip to content

Commit 6f1ead2

Browse files
feat: extend channel priming logic to also send fake requests (#398)
* feat: extend channel priming logic to also send fake requests * finish * remove debug * copyright * remove debug code from test * fix deps * address some feedback * add additional error handling test + some comments
1 parent 08f7d84 commit 6f1ead2

File tree

10 files changed

+593
-179
lines changed

10 files changed

+593
-179
lines changed

google-cloud-bigtable/pom.xml

+4
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,10 @@
8787
<groupId>com.google.api.grpc</groupId>
8888
<artifactId>proto-google-iam-v1</artifactId>
8989
</dependency>
90+
<dependency>
91+
<groupId>com.google.auth</groupId>
92+
<artifactId>google-auth-library-credentials</artifactId>
93+
</dependency>
9094
<dependency>
9195
<groupId>com.google.auth</groupId>
9296
<artifactId>google-auth-library-oauth2-http</artifactId>

google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/BigtableDataSettings.java

+35-3
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import com.google.cloud.bigtable.data.v2.stub.EnhancedBigtableStubSettings;
2727
import com.google.common.base.Strings;
2828
import io.grpc.ManagedChannelBuilder;
29+
import java.util.List;
2930
import java.util.logging.Logger;
3031
import javax.annotation.Nonnull;
3132

@@ -185,11 +186,20 @@ public String getAppProfileId() {
185186
}
186187

187188
/** Gets if channels will gracefully refresh connections to Cloud Bigtable service */
188-
@BetaApi("This API depends on experimental gRPC APIs")
189+
@BetaApi("Channel priming is not currently stable and may change in the future")
189190
public boolean isRefreshingChannel() {
190191
return stubSettings.isRefreshingChannel();
191192
}
192193

194+
/**
195+
* Gets the table ids that will be used to send warmup requests when {@link
196+
* #isRefreshingChannel()} is enabled.
197+
*/
198+
@BetaApi("Channel priming is not currently stable and may change in the future")
199+
public List<String> getPrimingTableIds() {
200+
return stubSettings.getPrimedTableIds();
201+
}
202+
193203
/** Returns the underlying RPC settings. */
194204
public EnhancedBigtableStubSettings getStubSettings() {
195205
return stubSettings;
@@ -307,18 +317,40 @@ public CredentialsProvider getCredentialsProvider() {
307317
* connections, which causes the client to renegotiate the gRPC connection in the request path,
308318
* which causes periodic spikes in latency
309319
*/
310-
@BetaApi("This API depends on experimental gRPC APIs")
320+
@BetaApi("Channel priming is not currently stable and may change in the future")
311321
public Builder setRefreshingChannel(boolean isRefreshingChannel) {
312322
stubSettings.setRefreshingChannel(isRefreshingChannel);
313323
return this;
314324
}
315325

316326
/** Gets if channels will gracefully refresh connections to Cloud Bigtable service */
317-
@BetaApi("This API depends on experimental gRPC APIs")
327+
@BetaApi("Channel priming is not currently stable and may change in the future")
318328
public boolean isRefreshingChannel() {
319329
return stubSettings.isRefreshingChannel();
320330
}
321331

332+
/**
333+
* Configure the tables that can be used to prime a channel during a refresh.
334+
*
335+
* <p>These tables work in conjunction with {@link #setRefreshingChannel(boolean)}. When a
336+
* channel is refreshed, it will send a request to each table to warm up the serverside caches
337+
* before admitting the new channel into the channel pool.
338+
*/
339+
@BetaApi("Channel priming is not currently stable and may change in the future")
340+
public Builder setPrimingTableIds(String... tableIds) {
341+
stubSettings.setPrimedTableIds(tableIds);
342+
return this;
343+
}
344+
345+
/**
346+
* Gets the table ids that will be used to send warmup requests when {@link
347+
* #setRefreshingChannel(boolean)} is enabled.
348+
*/
349+
@BetaApi("Channel priming is not currently stable and may change in the future")
350+
public List<String> getPrimingTableIds() {
351+
return stubSettings.getPrimedTableIds();
352+
}
353+
322354
/**
323355
* Returns the underlying settings for making RPC calls. The settings should be changed with
324356
* care.

google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/RefreshChannel.java

-54
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
/*
2+
* Copyright 2020 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.google.cloud.bigtable.data.v2.stub;
17+
18+
import static com.google.cloud.bigtable.data.v2.models.Filters.FILTERS;
19+
20+
import com.google.api.core.ApiFuture;
21+
import com.google.api.core.BetaApi;
22+
import com.google.api.gax.core.FixedCredentialsProvider;
23+
import com.google.api.gax.core.InstantiatingExecutorProvider;
24+
import com.google.api.gax.grpc.ChannelPrimer;
25+
import com.google.api.gax.grpc.GrpcTransportChannel;
26+
import com.google.api.gax.rpc.FixedTransportChannelProvider;
27+
import com.google.auth.Credentials;
28+
import com.google.cloud.bigtable.data.v2.models.Query;
29+
import com.google.cloud.bigtable.data.v2.models.Row;
30+
import com.google.common.base.Preconditions;
31+
import com.google.common.collect.ImmutableList;
32+
import com.google.protobuf.ByteString;
33+
import io.grpc.ConnectivityState;
34+
import io.grpc.ManagedChannel;
35+
import java.io.IOException;
36+
import java.util.HashMap;
37+
import java.util.List;
38+
import java.util.Map;
39+
import java.util.concurrent.ExecutionException;
40+
import java.util.concurrent.TimeUnit;
41+
import java.util.logging.Logger;
42+
import org.threeten.bp.Duration;
43+
44+
/**
45+
* A channel warmer that ensures that a Bigtable channel is ready to be used before being added to
46+
* the active {@link com.google.api.gax.grpc.ChannelPool}.
47+
*
48+
* <p>This implementation is subject to change in the future, but currently it will prime the
49+
* channel by sending a ReadRow request for a hardcoded, non-existent row key.
50+
*/
51+
@BetaApi("Channel priming is not currently stable and might change in the future")
52+
class BigtableChannelPrimer implements ChannelPrimer {
53+
private static Logger LOG = Logger.getLogger(BigtableChannelPrimer.class.toString());
54+
55+
static ByteString PRIMING_ROW_KEY = ByteString.copyFromUtf8("nonexistent-priming-row");
56+
private static Duration PRIME_REQUEST_TIMEOUT = Duration.ofSeconds(30);
57+
58+
private final EnhancedBigtableStubSettings settingsTemplate;
59+
private final List<String> tableIds;
60+
61+
static BigtableChannelPrimer create(
62+
Credentials credentials,
63+
String projectId,
64+
String instanceId,
65+
String appProfileId,
66+
List<String> tableIds) {
67+
EnhancedBigtableStubSettings.Builder builder =
68+
EnhancedBigtableStubSettings.newBuilder()
69+
.setProjectId(projectId)
70+
.setInstanceId(instanceId)
71+
.setAppProfileId(appProfileId)
72+
.setCredentialsProvider(FixedCredentialsProvider.create(credentials))
73+
.setExecutorProvider(
74+
InstantiatingExecutorProvider.newBuilder().setExecutorThreadCount(1).build());
75+
76+
// Disable retries for priming request
77+
builder
78+
.readRowSettings()
79+
.setRetrySettings(
80+
builder
81+
.readRowSettings()
82+
.getRetrySettings()
83+
.toBuilder()
84+
.setMaxAttempts(1)
85+
.setJittered(false)
86+
.setInitialRpcTimeout(PRIME_REQUEST_TIMEOUT)
87+
.setMaxRpcTimeout(PRIME_REQUEST_TIMEOUT)
88+
.setTotalTimeout(PRIME_REQUEST_TIMEOUT)
89+
.build());
90+
return new BigtableChannelPrimer(builder.build(), tableIds);
91+
}
92+
93+
private BigtableChannelPrimer(
94+
EnhancedBigtableStubSettings settingsTemplate, List<String> tableIds) {
95+
Preconditions.checkNotNull(settingsTemplate, "settingsTemplate can't be null");
96+
this.settingsTemplate = settingsTemplate;
97+
this.tableIds = ImmutableList.copyOf(tableIds);
98+
}
99+
100+
@Override
101+
public void primeChannel(ManagedChannel managedChannel) {
102+
try {
103+
primeChannelUnsafe(managedChannel);
104+
} catch (IOException | RuntimeException e) {
105+
LOG.warning(
106+
String.format("Unexpected error while trying to prime a channel: %s", e.getMessage()));
107+
}
108+
}
109+
110+
private void primeChannelUnsafe(ManagedChannel managedChannel) throws IOException {
111+
if (tableIds.isEmpty()) {
112+
waitForChannelReady(managedChannel);
113+
} else {
114+
sendPrimeRequests(managedChannel);
115+
}
116+
}
117+
118+
private void waitForChannelReady(ManagedChannel managedChannel) {
119+
for (int i = 0; i < 30; i++) {
120+
ConnectivityState connectivityState = managedChannel.getState(true);
121+
if (connectivityState == ConnectivityState.READY) {
122+
break;
123+
}
124+
try {
125+
TimeUnit.SECONDS.sleep(1);
126+
} catch (InterruptedException e) {
127+
break;
128+
}
129+
}
130+
}
131+
132+
private void sendPrimeRequests(ManagedChannel managedChannel) throws IOException {
133+
// Wrap the channel in a temporary stub
134+
EnhancedBigtableStubSettings primingSettings =
135+
settingsTemplate
136+
.toBuilder()
137+
.setTransportChannelProvider(
138+
FixedTransportChannelProvider.create(GrpcTransportChannel.create(managedChannel)))
139+
.build();
140+
141+
try (EnhancedBigtableStub stub = EnhancedBigtableStub.create(primingSettings)) {
142+
Map<String, ApiFuture<?>> primeFutures = new HashMap<>();
143+
144+
// Prime all of the table ids in parallel
145+
for (String tableId : tableIds) {
146+
ApiFuture<Row> f =
147+
stub.readRowCallable()
148+
.futureCall(Query.create(tableId).rowKey(PRIMING_ROW_KEY).filter(FILTERS.block()));
149+
150+
primeFutures.put(tableId, f);
151+
}
152+
153+
// Wait for all of the prime requests to complete.
154+
for (Map.Entry<String, ApiFuture<?>> entry : primeFutures.entrySet()) {
155+
try {
156+
entry.getValue().get();
157+
} catch (Throwable e) {
158+
if (e instanceof ExecutionException) {
159+
e = e.getCause();
160+
}
161+
LOG.warning(
162+
String.format(
163+
"Failed to prime channel for table: %s: %s", entry.getKey(), e.getMessage()));
164+
}
165+
}
166+
}
167+
}
168+
}

0 commit comments

Comments
 (0)