Skip to content

Commit 2a8f29f

Browse files
committed
feat: enable dynamic channel pooling by default
1 parent 4920266 commit 2a8f29f

File tree

4 files changed

+150
-9
lines changed

4 files changed

+150
-9
lines changed

google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java

Lines changed: 116 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,16 @@ public class SpannerOptions extends ServiceOptions<Spanner, SpannerOptions> {
153153
private final Duration partitionedDmlTimeout;
154154
private final boolean grpcGcpExtensionEnabled;
155155
private final GcpManagedChannelOptions grpcGcpOptions;
156+
// Whether dynamic channel pooling is enabled (via automatic gRPC-GCP enablement) by default.
157+
// This is derived from the builder flag at build time.
158+
private final boolean dynamicChannelPoolEnabled;
159+
// Dynamic Channel Pool parameters
160+
private final Integer dcpMaxRpcPerChannel;
161+
private final Integer dcpMinRpcPerChannel;
162+
private final Duration dcpScaleDownInterval;
163+
private final Integer dcpInitialSize;
164+
private final Integer dcpMaxChannels;
165+
private final Integer dcpMinChannels;
156166
private final boolean autoThrottleAdministrativeRequests;
157167
private final RetrySettings retryAdministrativeRequestsSettings;
158168
private final boolean trackTransactionStarter;
@@ -800,6 +810,13 @@ protected SpannerOptions(Builder builder) {
800810
partitionedDmlTimeout = builder.partitionedDmlTimeout;
801811
grpcGcpExtensionEnabled = builder.grpcGcpExtensionEnabled;
802812
grpcGcpOptions = builder.grpcGcpOptions;
813+
dynamicChannelPoolEnabled = builder.dynamicChannelPoolEnabled;
814+
dcpMaxRpcPerChannel = builder.dcpMaxRpcPerChannel;
815+
dcpMinRpcPerChannel = builder.dcpMinRpcPerChannel;
816+
dcpScaleDownInterval = builder.dcpScaleDownInterval;
817+
dcpInitialSize = builder.dcpInitialSize;
818+
dcpMaxChannels = builder.dcpMaxChannels;
819+
dcpMinChannels = builder.dcpMinChannels;
803820
autoThrottleAdministrativeRequests = builder.autoThrottleAdministrativeRequests;
804821
retryAdministrativeRequestsSettings = builder.retryAdministrativeRequestsSettings;
805822
trackTransactionStarter = builder.trackTransactionStarter;
@@ -1027,6 +1044,10 @@ public static class Builder
10271044
private Duration partitionedDmlTimeout = Duration.ofHours(2L);
10281045
private boolean grpcGcpExtensionEnabled = false;
10291046
private GcpManagedChannelOptions grpcGcpOptions;
1047+
// Tracks whether enable/disableGrpcGcpExtension has been explicitly called by the user.
1048+
private boolean grpcGcpExtensionExplicitlySet = false;
1049+
// Dynamic Channel Pool (DCP) toggle. Default: enabled.
1050+
private boolean dynamicChannelPoolEnabled = true;
10301051
private RetrySettings retryAdministrativeRequestsSettings =
10311052
DEFAULT_ADMIN_REQUESTS_LIMIT_EXCEEDED_RETRY_SETTINGS;
10321053
private boolean autoThrottleAdministrativeRequests = false;
@@ -1050,6 +1071,14 @@ public static class Builder
10501071
private boolean isExperimentalHost = false;
10511072
private TransactionOptions defaultTransactionOptions = TransactionOptions.getDefaultInstance();
10521073

1074+
// Dynamic Channel Pool configuration (defaults per dynamic_cahnnel_pooling.md)
1075+
private Integer dcpMaxRpcPerChannel = 25;
1076+
private Integer dcpMinRpcPerChannel = 15;
1077+
private Duration dcpScaleDownInterval = Duration.ofMinutes(3);
1078+
private Integer dcpInitialSize = 4;
1079+
private Integer dcpMaxChannels = 10;
1080+
private Integer dcpMinChannels = 2;
1081+
10531082
private static String createCustomClientLibToken(String token) {
10541083
return token + " " + ServiceOptions.getGoogApiClientLibName();
10551084
}
@@ -1557,30 +1586,87 @@ public Builder setExperimentalHost(String host) {
15571586
return this;
15581587
}
15591588

1560-
/**
1561-
* Enables gRPC-GCP extension with the default settings. Do not set
1562-
* GOOGLE_CLOUD_SPANNER_MULTIPLEXED_SESSIONS to true in combination with this option, as
1563-
* Multiplexed sessions are not supported for gRPC-GCP.
1564-
*/
1589+
/** Enables gRPC-GCP extension with the default settings. */
15651590
public Builder enableGrpcGcpExtension() {
15661591
return this.enableGrpcGcpExtension(null);
15671592
}
15681593

15691594
/**
15701595
* Enables gRPC-GCP extension and uses provided options for configuration. The metric registry
1571-
* and default Spanner metric labels will be added automatically. Do not set
1572-
* GOOGLE_CLOUD_SPANNER_MULTIPLEXED_SESSIONS to true in combination with this option, as
1573-
* Multiplexed sessions are not supported for gRPC-GCP.
1596+
* and default Spanner metric labels will be added automatically.
15741597
*/
15751598
public Builder enableGrpcGcpExtension(GcpManagedChannelOptions options) {
15761599
this.grpcGcpExtensionEnabled = true;
15771600
this.grpcGcpOptions = options;
1601+
this.grpcGcpExtensionExplicitlySet = true;
15781602
return this;
15791603
}
15801604

15811605
/** Disables gRPC-GCP extension. */
15821606
public Builder disableGrpcGcpExtension() {
15831607
this.grpcGcpExtensionEnabled = false;
1608+
this.grpcGcpExtensionExplicitlySet = true;
1609+
return this;
1610+
}
1611+
1612+
/**
1613+
* Enables or disables dynamic channel pooling. When enabled and no explicit number of channels
1614+
* has been configured and no custom {@link TransportChannelProvider} has been set, the client
1615+
* will automatically enable the gRPC-GCP channel pool. If multiplexed sessions are enabled,
1616+
* dynamic channel pooling will not be enabled.
1617+
*/
1618+
public Builder setDynamicChannelPoolEnabled(boolean enabled) {
1619+
this.dynamicChannelPoolEnabled = enabled;
1620+
return this;
1621+
}
1622+
1623+
// Granular DCP configuration setters with validation bounds
1624+
public Builder setDynamicPoolMaxRpc(int maxRpcPerChannel) {
1625+
Preconditions.checkArgument(maxRpcPerChannel >= 1 && maxRpcPerChannel <= 100,
1626+
"maxRpcPerChannel must be in [1, 100]");
1627+
this.dcpMaxRpcPerChannel = maxRpcPerChannel;
1628+
return this;
1629+
}
1630+
1631+
public Builder setDynamicPoolMinRpc(int minRpcPerChannel) {
1632+
Preconditions.checkArgument(minRpcPerChannel >= 1,
1633+
"minRpcPerChannel must be >= 1");
1634+
this.dcpMinRpcPerChannel = minRpcPerChannel;
1635+
return this;
1636+
}
1637+
1638+
public Builder setDynamicPoolScaleDownInterval(Duration interval) {
1639+
Preconditions.checkNotNull(interval, "interval cannot be null");
1640+
Preconditions.checkArgument(!interval.isNegative() && !interval.isZero(),
1641+
"interval must be > 0");
1642+
Preconditions.checkArgument(
1643+
interval.compareTo(Duration.ofSeconds(30)) >= 0,
1644+
"interval must be >= 30 seconds");
1645+
Preconditions.checkArgument(
1646+
interval.compareTo(Duration.ofMinutes(60)) <= 0,
1647+
"interval must be <= 60 minutes");
1648+
this.dcpScaleDownInterval = interval;
1649+
return this;
1650+
}
1651+
1652+
public Builder setDynamicPoolInitialSize(int initialSize) {
1653+
Preconditions.checkArgument(initialSize >= 1 && initialSize <= 256,
1654+
"initialSize must be in [1, 256]");
1655+
this.dcpInitialSize = initialSize;
1656+
return this;
1657+
}
1658+
1659+
public Builder setDynamicPoolMaxChannels(int maxChannels) {
1660+
Preconditions.checkArgument(maxChannels >= 1 && maxChannels <= 256,
1661+
"maxChannels must be in [1, 256]");
1662+
this.dcpMaxChannels = maxChannels;
1663+
return this;
1664+
}
1665+
1666+
public Builder setDynamicPoolMinChannels(int minChannels) {
1667+
Preconditions.checkArgument(minChannels >= 1,
1668+
"minChannels must be >= 1");
1669+
this.dcpMinChannels = minChannels;
15841670
return this;
15851671
}
15861672

@@ -1792,6 +1878,15 @@ public SpannerOptions build() {
17921878
} else if (isExperimentalHost && credentials == null) {
17931879
credentials = environment.getDefaultExperimentalHostCredentials();
17941880
}
1881+
// Auto-enable gRPC-GCP (dynamic channel pool) if allowed and not explicitly overridden.
1882+
if (!grpcGcpExtensionExplicitlySet && dynamicChannelPoolEnabled) {
1883+
boolean hasCustomChannelProvider = this.channelProvider != null;
1884+
boolean hasStaticNumChannels = this.numChannels != null;
1885+
if (!hasCustomChannelProvider && !hasStaticNumChannels) {
1886+
this.grpcGcpExtensionEnabled = true;
1887+
}
1888+
}
1889+
17951890
if (this.numChannels == null) {
17961891
this.numChannels =
17971892
this.grpcGcpExtensionEnabled ? GRPC_GCP_ENABLED_DEFAULT_CHANNELS : DEFAULT_CHANNELS;
@@ -1996,6 +2091,19 @@ public GcpManagedChannelOptions getGrpcGcpOptions() {
19962091
return grpcGcpOptions;
19972092
}
19982093

2094+
/** Returns whether dynamic channel pooling is enabled by default. */
2095+
public boolean isDynamicChannelPoolEnabled() {
2096+
return dynamicChannelPoolEnabled;
2097+
}
2098+
2099+
// Dynamic Channel Pool getters used by channel setup
2100+
public Integer getDcpMaxRpcPerChannel() { return dcpMaxRpcPerChannel; }
2101+
public Integer getDcpMinRpcPerChannel() { return dcpMinRpcPerChannel; }
2102+
public Duration getDcpScaleDownInterval() { return dcpScaleDownInterval; }
2103+
public Integer getDcpInitialSize() { return dcpInitialSize; }
2104+
public Integer getDcpMaxChannels() { return dcpMaxChannels; }
2105+
public Integer getDcpMinChannels() { return dcpMinChannels; }
2106+
19992107
public boolean isAutoThrottleAdministrativeRequests() {
20002108
return autoThrottleAdministrativeRequests;
20012109
}

google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpc.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -580,8 +580,34 @@ private static GcpManagedChannelOptions grpcGcpOptionsWithMetrics(SpannerOptions
580580
if (metricsOptions.getNamePrefix().equals("")) {
581581
metricsOptionsBuilder.withNamePrefix("cloud.google.com/java/spanner/gcp-channel-pool/");
582582
}
583+
584+
// Build channel pool options from SpannerOptions DCP settings
585+
GcpManagedChannelOptions.GcpChannelPoolOptions.Builder poolBuilder =
586+
GcpManagedChannelOptions.GcpChannelPoolOptions.newBuilder(
587+
grpcGcpOptions.getChannelPoolOptions());
588+
Integer maxChannels = options.getDcpMaxChannels();
589+
Integer minChannels = options.getDcpMinChannels();
590+
Integer initSize = options.getDcpInitialSize();
591+
Integer minRpc = options.getDcpMinRpcPerChannel();
592+
Integer maxRpc = options.getDcpMaxRpcPerChannel();
593+
java.time.Duration scaleDown = options.getDcpScaleDownInterval();
594+
595+
if (maxChannels != null) {
596+
poolBuilder.setMaxSize(maxChannels);
597+
}
598+
if (minChannels != null) {
599+
poolBuilder.setMinSize(minChannels);
600+
}
601+
if (initSize != null) {
602+
poolBuilder.setInitSize(initSize);
603+
}
604+
if (minRpc != null && maxRpc != null && scaleDown != null) {
605+
poolBuilder.setDynamicScaling(minRpc, maxRpc, scaleDown);
606+
}
607+
583608
return GcpManagedChannelOptions.newBuilder(grpcGcpOptions)
584609
.withMetricsOptions(metricsOptionsBuilder.build())
610+
.withChannelPoolOptions(poolBuilder.build())
585611
.build();
586612
}
587613

google-cloud-spanner/src/test/java/com/google/cloud/spanner/RetryOnDifferentGrpcChannelMockServerTest.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,9 @@ public void testSingleUseQuery_retriesOnNewChannel() {
288288
SpannerOptions.Builder builder = createSpannerOptionsBuilder();
289289
builder.setSessionPoolOption(
290290
SessionPoolOptions.newBuilder().setUseMultiplexedSession(true).build());
291+
// Ensure retry happens on a different underlying channel by disabling grpc-gcp and limiting
292+
// number of channels to 2 for this test.
293+
builder.disableGrpcGcpExtension().setNumChannels(2);
291294
mockSpanner.setExecuteStreamingSqlExecutionTime(
292295
SimulatedExecutionTime.ofException(Status.DEADLINE_EXCEEDED.asRuntimeException()));
293296

@@ -317,6 +320,8 @@ public void testSingleUseQuery_stopsRetrying() {
317320
SpannerOptions.Builder builder = createSpannerOptionsBuilder();
318321
builder.setSessionPoolOption(
319322
SessionPoolOptions.newBuilder().setUseMultiplexedSession(true).build());
323+
// Ensure a deterministic number of channels for this assertion.
324+
builder.disableGrpcGcpExtension().setNumChannels(8);
320325
mockSpanner.setExecuteStreamingSqlExecutionTime(
321326
SimulatedExecutionTime.ofStickyException(Status.DEADLINE_EXCEEDED.asRuntimeException()));
322327

google-cloud-spanner/src/test/java/com/google/cloud/spanner/SpannerOptionsTest.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1100,6 +1100,7 @@ public void testDefaultNumChannelsWithGrpcGcpExtensionDisabled() {
11001100
SpannerOptions.newBuilder()
11011101
.setProjectId("test-project")
11021102
.setCredentials(NoCredentials.getInstance())
1103+
.disableGrpcGcpExtension()
11031104
.build();
11041105

11051106
assertEquals(SpannerOptions.DEFAULT_CHANNELS, options.getNumChannels());
@@ -1135,7 +1136,8 @@ public void testNumChannelsWithGrpcGcpExtensionEnabled() {
11351136

11361137
@Test
11371138
public void checkCreatedInstanceWhenGrpcGcpExtensionDisabled() {
1138-
SpannerOptions options = SpannerOptions.newBuilder().setProjectId("test-project").build();
1139+
SpannerOptions options =
1140+
SpannerOptions.newBuilder().setProjectId("test-project").disableGrpcGcpExtension().build();
11391141
SpannerOptions options1 = options.toBuilder().build();
11401142
assertEquals(false, options.isGrpcGcpExtensionEnabled());
11411143
assertEquals(options.isGrpcGcpExtensionEnabled(), options1.isGrpcGcpExtensionEnabled());

0 commit comments

Comments
 (0)