Skip to content

Commit 276f942

Browse files
tonytangerkolea2
authored andcommitted
feat: add implementation of ChannelPrimer to establish connection to GFE and integrate into bigtable client (#115)
1 parent 12169d6 commit 276f942

File tree

5 files changed

+196
-0
lines changed

5 files changed

+196
-0
lines changed

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

+26
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,12 @@ public String getAppProfileId() {
184184
return stubSettings.getAppProfileId();
185185
}
186186

187+
/** Gets if channels will gracefully refresh connections to Cloud Bigtable service */
188+
@BetaApi("This API depends on experimental gRPC APIs")
189+
public boolean isRefreshingChannel() {
190+
return stubSettings.isRefreshingChannel();
191+
}
192+
187193
/** Returns the underlying RPC settings. */
188194
public EnhancedBigtableStubSettings getStubSettings() {
189195
return stubSettings;
@@ -275,6 +281,26 @@ public CredentialsProvider getCredentialsProvider() {
275281
return stubSettings.getCredentialsProvider();
276282
}
277283

284+
/**
285+
* Configure periodic gRPC channel refreshes.
286+
*
287+
* <p>This feature will gracefully refresh connections to the Cloud Bigtable service. This is an
288+
* experimental feature to address tail latency caused by the service dropping long lived gRPC
289+
* connections, which causes the client to renegotiate the gRPC connection in the request path,
290+
* which causes periodic spikes in latency
291+
*/
292+
@BetaApi("This API depends on experimental gRPC APIs")
293+
public Builder setRefreshingChannel(boolean isRefreshingChannel) {
294+
stubSettings.setRefreshingChannel(isRefreshingChannel);
295+
return this;
296+
}
297+
298+
/** Gets if channels will gracefully refresh connections to Cloud Bigtable service */
299+
@BetaApi("This API depends on experimental gRPC APIs")
300+
public boolean isRefreshingChannel() {
301+
return stubSettings.isRefreshingChannel();
302+
}
303+
278304
/**
279305
* Returns the underlying settings for making RPC calls. The settings should be changed with
280306
* care.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
* Copyright 2019 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.internal;
17+
18+
import com.google.api.core.BetaApi;
19+
import com.google.api.core.InternalApi;
20+
import com.google.api.gax.grpc.ChannelPrimer;
21+
import io.grpc.ConnectivityState;
22+
import io.grpc.ManagedChannel;
23+
import java.util.concurrent.TimeUnit;
24+
25+
/**
26+
* Establish a connection to the Cloud Bigtable service on managedChannel
27+
*
28+
* <p>This class is considered an internal implementation detail and not meant to be used by
29+
* applications.
30+
*/
31+
@BetaApi("This API depends on gRPC experimental API")
32+
@InternalApi
33+
public final class RefreshChannel implements ChannelPrimer {
34+
35+
/**
36+
* primeChannel establishes a connection to Cloud Bigtable service. This typically take less than
37+
* 1s. In case of service failure, an upper limit of 10s prevents primeChannel from looping
38+
* forever.
39+
*/
40+
@Override
41+
public void primeChannel(ManagedChannel managedChannel) {
42+
for (int i = 0; i < 10; i++) {
43+
ConnectivityState connectivityState = managedChannel.getState(true);
44+
if (connectivityState == ConnectivityState.READY) {
45+
break;
46+
}
47+
try {
48+
TimeUnit.SECONDS.sleep(1);
49+
} catch (InterruptedException e) {
50+
break;
51+
}
52+
}
53+
}
54+
}

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

+42
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package com.google.cloud.bigtable.data.v2.stub;
1717

18+
import com.google.api.core.BetaApi;
1819
import com.google.api.gax.batching.BatchingSettings;
1920
import com.google.api.gax.batching.FlowControlSettings;
2021
import com.google.api.gax.batching.FlowController.LimitExceededBehavior;
@@ -29,6 +30,7 @@
2930
import com.google.api.gax.rpc.TransportChannelProvider;
3031
import com.google.api.gax.rpc.UnaryCallSettings;
3132
import com.google.api.gax.tracing.OpencensusTracerFactory;
33+
import com.google.cloud.bigtable.data.v2.internal.RefreshChannel;
3234
import com.google.cloud.bigtable.data.v2.models.ConditionalRowMutation;
3335
import com.google.cloud.bigtable.data.v2.models.KeyOffset;
3436
import com.google.cloud.bigtable.data.v2.models.Query;
@@ -149,6 +151,7 @@ public class EnhancedBigtableStubSettings extends StubSettings<EnhancedBigtableS
149151
private final String projectId;
150152
private final String instanceId;
151153
private final String appProfileId;
154+
private final boolean isRefreshingChannel;
152155

153156
private final ServerStreamingCallSettings<Query, Row> readRowsSettings;
154157
private final UnaryCallSettings<Query, Row> readRowSettings;
@@ -179,6 +182,7 @@ private EnhancedBigtableStubSettings(Builder builder) {
179182
projectId = builder.projectId;
180183
instanceId = builder.instanceId;
181184
appProfileId = builder.appProfileId;
185+
isRefreshingChannel = builder.isRefreshingChannel;
182186

183187
// Per method settings.
184188
readRowsSettings = builder.readRowsSettings.build();
@@ -210,6 +214,12 @@ public String getAppProfileId() {
210214
return appProfileId;
211215
}
212216

217+
/** Returns if channels will gracefully refresh connections to Cloud Bigtable service */
218+
@BetaApi("This API depends on experimental gRPC APIs")
219+
public boolean isRefreshingChannel() {
220+
return isRefreshingChannel;
221+
}
222+
213223
/** Returns a builder for the default ChannelProvider for this service. */
214224
public static InstantiatingGrpcChannelProvider.Builder defaultGrpcTransportProviderBuilder() {
215225
return BigtableStubSettings.defaultGrpcTransportProviderBuilder()
@@ -413,6 +423,7 @@ public static class Builder extends StubSettings.Builder<EnhancedBigtableStubSet
413423
private String projectId;
414424
private String instanceId;
415425
private String appProfileId;
426+
private boolean isRefreshingChannel;
416427

417428
private final ServerStreamingCallSettings.Builder<Query, Row> readRowsSettings;
418429
private final UnaryCallSettings.Builder<Query, Row> readRowSettings;
@@ -433,6 +444,7 @@ public static class Builder extends StubSettings.Builder<EnhancedBigtableStubSet
433444
*/
434445
private Builder() {
435446
this.appProfileId = SERVER_DEFAULT_APP_PROFILE_ID;
447+
this.isRefreshingChannel = false;
436448
setCredentialsProvider(defaultCredentialsProviderBuilder().build());
437449

438450
// Defaults provider
@@ -515,6 +527,7 @@ private Builder(EnhancedBigtableStubSettings settings) {
515527
projectId = settings.projectId;
516528
instanceId = settings.instanceId;
517529
appProfileId = settings.appProfileId;
530+
isRefreshingChannel = settings.isRefreshingChannel;
518531

519532
// Per method settings.
520533
readRowsSettings = settings.readRowsSettings.toBuilder();
@@ -602,6 +615,23 @@ public String getAppProfileId() {
602615
return appProfileId;
603616
}
604617

618+
/**
619+
* Sets if channels will gracefully refresh connections to Cloud Bigtable service
620+
*
621+
* @see com.google.cloud.bigtable.data.v2.BigtableDataSettings.Builder#setRefreshingChannel
622+
*/
623+
@BetaApi("This API depends on experimental gRPC APIs")
624+
public Builder setRefreshingChannel(boolean isRefreshingChannel) {
625+
this.isRefreshingChannel = isRefreshingChannel;
626+
return this;
627+
}
628+
629+
/** Gets if channels will gracefully refresh connections to Cloud Bigtable service */
630+
@BetaApi("This API depends on experimental gRPC APIs")
631+
public boolean isRefreshingChannel() {
632+
return isRefreshingChannel;
633+
}
634+
605635
/** Returns the builder for the settings used for calls to readRows. */
606636
public ServerStreamingCallSettings.Builder<Query, Row> readRowsSettings() {
607637
return readRowsSettings;
@@ -642,6 +672,18 @@ public EnhancedBigtableStubSettings build() {
642672
Preconditions.checkState(projectId != null, "Project id must be set");
643673
Preconditions.checkState(instanceId != null, "Instance id must be set");
644674

675+
// Set ChannelPrimer on TransportChannelProvider so channels will gracefully refresh
676+
// connections to Cloud Bigtable service
677+
if (isRefreshingChannel) {
678+
Preconditions.checkArgument(
679+
getTransportChannelProvider() instanceof InstantiatingGrpcChannelProvider,
680+
"refreshingChannel only works with InstantiatingGrpcChannelProviders");
681+
InstantiatingGrpcChannelProvider.Builder channelBuilder =
682+
((InstantiatingGrpcChannelProvider) getTransportChannelProvider())
683+
.toBuilder()
684+
.setChannelPrimer(new RefreshChannel());
685+
setTransportChannelProvider(channelBuilder.build());
686+
}
645687
return new EnhancedBigtableStubSettings(this);
646688
}
647689
// </editor-fold>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Copyright 2019 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.internal;
17+
18+
import io.grpc.ConnectivityState;
19+
import io.grpc.ManagedChannel;
20+
import org.junit.Test;
21+
import org.junit.runner.RunWith;
22+
import org.junit.runners.JUnit4;
23+
import org.mockito.Mockito;
24+
25+
@RunWith(JUnit4.class)
26+
public class RefreshChannelTest {
27+
// RefreshChannel should establish connection to the server through managedChannel.getState(true)
28+
@Test
29+
public void testGetStateIsCalled() {
30+
RefreshChannel refreshChannel = new RefreshChannel();
31+
ManagedChannel managedChannel = Mockito.mock(ManagedChannel.class);
32+
33+
Mockito.doReturn(ConnectivityState.READY).when(managedChannel).getState(true);
34+
35+
refreshChannel.primeChannel(managedChannel);
36+
Mockito.verify(managedChannel, Mockito.atLeastOnce()).getState(true);
37+
}
38+
}

google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/EnhancedBigtableStubSettingsTest.java

+36
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ public void settingsAreNotLostTest() {
6161
String projectId = "my-project";
6262
String instanceId = "my-instance";
6363
String appProfileId = "my-app-profile-id";
64+
boolean isRefreshingChannel = true;
6465
String endpoint = "some.other.host:123";
6566
CredentialsProvider credentialsProvider = Mockito.mock(CredentialsProvider.class);
6667
WatchdogProvider watchdogProvider = Mockito.mock(WatchdogProvider.class);
@@ -71,6 +72,7 @@ public void settingsAreNotLostTest() {
7172
.setProjectId(projectId)
7273
.setInstanceId(instanceId)
7374
.setAppProfileId(appProfileId)
75+
.setRefreshingChannel(isRefreshingChannel)
7476
.setEndpoint(endpoint)
7577
.setCredentialsProvider(credentialsProvider)
7678
.setStreamWatchdogProvider(watchdogProvider)
@@ -81,6 +83,7 @@ public void settingsAreNotLostTest() {
8183
projectId,
8284
instanceId,
8385
appProfileId,
86+
isRefreshingChannel,
8487
endpoint,
8588
credentialsProvider,
8689
watchdogProvider,
@@ -90,6 +93,7 @@ public void settingsAreNotLostTest() {
9093
projectId,
9194
instanceId,
9295
appProfileId,
96+
isRefreshingChannel,
9397
endpoint,
9498
credentialsProvider,
9599
watchdogProvider,
@@ -99,6 +103,7 @@ public void settingsAreNotLostTest() {
99103
projectId,
100104
instanceId,
101105
appProfileId,
106+
isRefreshingChannel,
102107
endpoint,
103108
credentialsProvider,
104109
watchdogProvider,
@@ -110,13 +115,15 @@ private void verifyBuilder(
110115
String projectId,
111116
String instanceId,
112117
String appProfileId,
118+
boolean isRefreshingChannel,
113119
String endpoint,
114120
CredentialsProvider credentialsProvider,
115121
WatchdogProvider watchdogProvider,
116122
Duration watchdogInterval) {
117123
assertThat(builder.getProjectId()).isEqualTo(projectId);
118124
assertThat(builder.getInstanceId()).isEqualTo(instanceId);
119125
assertThat(builder.getAppProfileId()).isEqualTo(appProfileId);
126+
assertThat(builder.isRefreshingChannel()).isEqualTo(isRefreshingChannel);
120127
assertThat(builder.getEndpoint()).isEqualTo(endpoint);
121128
assertThat(builder.getCredentialsProvider()).isEqualTo(credentialsProvider);
122129
assertThat(builder.getStreamWatchdogProvider()).isSameInstanceAs(watchdogProvider);
@@ -128,13 +135,15 @@ private void verifySettings(
128135
String projectId,
129136
String instanceId,
130137
String appProfileId,
138+
boolean isRefreshingChannel,
131139
String endpoint,
132140
CredentialsProvider credentialsProvider,
133141
WatchdogProvider watchdogProvider,
134142
Duration watchdogInterval) {
135143
assertThat(settings.getProjectId()).isEqualTo(projectId);
136144
assertThat(settings.getInstanceId()).isEqualTo(instanceId);
137145
assertThat(settings.getAppProfileId()).isEqualTo(appProfileId);
146+
assertThat(settings.isRefreshingChannel()).isEqualTo(isRefreshingChannel);
138147
assertThat(settings.getEndpoint()).isEqualTo(endpoint);
139148
assertThat(settings.getCredentialsProvider()).isEqualTo(credentialsProvider);
140149
assertThat(settings.getStreamWatchdogProvider()).isSameInstanceAs(watchdogProvider);
@@ -521,4 +530,31 @@ private void verifyRetrySettingAreSane(Set<Code> retryCodes, RetrySettings retry
521530
assertThat(retrySettings.getRpcTimeoutMultiplier()).isAtLeast(1.0);
522531
assertThat(retrySettings.getMaxRpcTimeout()).isGreaterThan(Duration.ZERO);
523532
}
533+
534+
@Test
535+
public void isRefreshingChannelDefaultValueTest() {
536+
String dummyProjectId = "my-project";
537+
String dummyInstanceId = "my-instance";
538+
EnhancedBigtableStubSettings.Builder builder =
539+
EnhancedBigtableStubSettings.newBuilder()
540+
.setProjectId(dummyProjectId)
541+
.setInstanceId(dummyInstanceId);
542+
assertThat(builder.isRefreshingChannel()).isFalse();
543+
assertThat(builder.build().isRefreshingChannel()).isFalse();
544+
assertThat(builder.build().toBuilder().isRefreshingChannel()).isFalse();
545+
}
546+
547+
@Test
548+
public void isRefreshingChannelFalseValueTest() {
549+
String dummyProjectId = "my-project";
550+
String dummyInstanceId = "my-instance";
551+
EnhancedBigtableStubSettings.Builder builder =
552+
EnhancedBigtableStubSettings.newBuilder()
553+
.setProjectId(dummyProjectId)
554+
.setInstanceId(dummyInstanceId)
555+
.setRefreshingChannel(false);
556+
assertThat(builder.isRefreshingChannel()).isFalse();
557+
assertThat(builder.build().isRefreshingChannel()).isFalse();
558+
assertThat(builder.build().toBuilder().isRefreshingChannel()).isFalse();
559+
}
524560
}

0 commit comments

Comments
 (0)