Skip to content

Commit 2b8e9ed

Browse files
feat: support multiplexed sessions for RO transactions (#3141)
* feat(spanner): environment variable to enable multiplexed sessions * chore: lint fix * feat(spanner): add comment for gRPC-GCP * feat(spanner): make strict check in env variable * feat(spanner): add test * feat(spanner): add test * chore(spanner): lint * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md --------- Co-authored-by: Owl Bot <gcf-owl-bot[bot]@users.noreply.github.com>
1 parent 203a3c7 commit 2b8e9ed

File tree

7 files changed

+149
-35
lines changed

7 files changed

+149
-35
lines changed

.github/workflows/ci.yaml

+2-2
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ jobs:
5252
- run: .kokoro/build.sh
5353
env:
5454
JOB_TYPE: test
55-
GOOGLE_CLOUD_SPANNER_ENABLE_MULTIPLEXED_SESSIONS: true
55+
GOOGLE_CLOUD_SPANNER_MULTIPLEXED_SESSIONS: true
5656
units-java8:
5757
# Building using Java 17 and run the tests with Java 8 runtime
5858
name: "units (8)"
@@ -91,7 +91,7 @@ jobs:
9191
- run: .kokoro/build.sh
9292
env:
9393
JOB_TYPE: test
94-
GOOGLE_CLOUD_SPANNER_ENABLE_MULTIPLEXED_SESSIONS: true
94+
GOOGLE_CLOUD_SPANNER_MULTIPLEXED_SESSIONS: true
9595
windows:
9696
runs-on: windows-latest
9797
steps:

.github/workflows/integration-tests-against-emulator-with-multiplexed-session.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,4 +39,4 @@ jobs:
3939
env:
4040
JOB_TYPE: test
4141
SPANNER_EMULATOR_HOST: localhost:9010
42-
GOOGLE_CLOUD_SPANNER_ENABLE_MULTIPLEXED_SESSIONS: true
42+
GOOGLE_CLOUD_SPANNER_MULTIPLEXED_SESSIONS: true

.kokoro/presubmit/integration-multiplexed-sessions-enabled.cfg

+2-2
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,6 @@ env_vars: {
3333
}
3434

3535
env_vars: {
36-
key: "GOOGLE_CLOUD_SPANNER_ENABLE_MULTIPLEXED_SESSIONS"
36+
key: "GOOGLE_CLOUD_SPANNER_MULTIPLEXED_SESSIONS"
3737
value: "true"
38-
}
38+
}

README.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -57,13 +57,13 @@ implementation 'com.google.cloud:google-cloud-spanner'
5757
If you are using Gradle without BOM, add this to your dependencies:
5858

5959
```Groovy
60-
implementation 'com.google.cloud:google-cloud-spanner:6.69.0'
60+
implementation 'com.google.cloud:google-cloud-spanner:6.70.0'
6161
```
6262

6363
If you are using SBT, add this to your dependencies:
6464

6565
```Scala
66-
libraryDependencies += "com.google.cloud" % "google-cloud-spanner" % "6.69.0"
66+
libraryDependencies += "com.google.cloud" % "google-cloud-spanner" % "6.70.0"
6767
```
6868
<!-- {x-version-update-end} -->
6969

@@ -687,7 +687,7 @@ Java is a registered trademark of Oracle and/or its affiliates.
687687
[kokoro-badge-link-5]: http://storage.googleapis.com/cloud-devrel-public/java/badges/java-spanner/java11.html
688688
[stability-image]: https://img.shields.io/badge/stability-stable-green
689689
[maven-version-image]: https://img.shields.io/maven-central/v/com.google.cloud/google-cloud-spanner.svg
690-
[maven-version-link]: https://central.sonatype.com/artifact/com.google.cloud/google-cloud-spanner/6.69.0
690+
[maven-version-link]: https://central.sonatype.com/artifact/com.google.cloud/google-cloud-spanner/6.70.0
691691
[authentication]: https://github.com/googleapis/google-cloud-java#authentication
692692
[auth-scopes]: https://developers.google.com/identity/protocols/oauth2/scopes
693693
[predefined-iam-roles]: https://cloud.google.com/iam/docs/understanding-roles#predefined_roles

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

+24-18
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616

1717
package com.google.cloud.spanner;
1818

19-
import com.google.api.core.BetaApi;
2019
import com.google.api.core.InternalApi;
2120
import com.google.cloud.spanner.SessionPool.Position;
2221
import com.google.common.annotations.VisibleForTesting;
@@ -103,11 +102,12 @@ private SessionPoolOptions(Builder builder) {
103102
this.randomizePositionQPSThreshold = builder.randomizePositionQPSThreshold;
104103
this.inactiveTransactionRemovalOptions = builder.inactiveTransactionRemovalOptions;
105104
this.poolMaintainerClock = builder.poolMaintainerClock;
106-
// TODO: Remove when multiplexed sessions are guaranteed to be supported.
105+
// useMultiplexedSession priority => Environment var > private setter > client default
106+
Boolean useMultiplexedSessionFromEnvVariable = getUseMultiplexedSessionFromEnvVariable();
107107
this.useMultiplexedSession =
108-
builder.useMultiplexedSession
109-
&& !Boolean.parseBoolean(
110-
System.getenv("GOOGLE_CLOUD_SPANNER_FORCE_DISABLE_MULTIPLEXED_SESSIONS"));
108+
(useMultiplexedSessionFromEnvVariable != null)
109+
? useMultiplexedSessionFromEnvVariable
110+
: builder.useMultiplexedSession;
111111
this.multiplexedSessionMaintenanceDuration = builder.multiplexedSessionMaintenanceDuration;
112112
}
113113

@@ -307,6 +307,22 @@ public boolean getUseMultiplexedSession() {
307307
return useMultiplexedSession;
308308
}
309309

310+
private static Boolean getUseMultiplexedSessionFromEnvVariable() {
311+
String useMultiplexedSessionFromEnvVariable =
312+
System.getenv("GOOGLE_CLOUD_SPANNER_MULTIPLEXED_SESSIONS");
313+
if (useMultiplexedSessionFromEnvVariable != null
314+
&& useMultiplexedSessionFromEnvVariable.length() > 0) {
315+
if ("true".equalsIgnoreCase(useMultiplexedSessionFromEnvVariable)
316+
|| "false".equalsIgnoreCase(useMultiplexedSessionFromEnvVariable)) {
317+
return Boolean.parseBoolean(useMultiplexedSessionFromEnvVariable);
318+
} else {
319+
throw new IllegalArgumentException(
320+
"GOOGLE_CLOUD_SPANNER_MULTIPLEXED_SESSIONS should be either true or false.");
321+
}
322+
}
323+
return null;
324+
}
325+
310326
Duration getMultiplexedSessionMaintenanceDuration() {
311327
return multiplexedSessionMaintenanceDuration;
312328
}
@@ -509,7 +525,9 @@ public static class Builder {
509525
*/
510526
private long randomizePositionQPSThreshold = 0L;
511527

512-
private boolean useMultiplexedSession = getUseMultiplexedSessionFromEnvVariable();
528+
// This field controls the default behavior of session management in Java client.
529+
// Set useMultiplexedSession to true to make multiplexed session the default.
530+
private boolean useMultiplexedSession = false;
513531

514532
private Duration multiplexedSessionMaintenanceDuration = Duration.ofDays(7);
515533
private Clock poolMaintainerClock = Clock.INSTANCE;
@@ -527,18 +545,6 @@ private static Position getReleaseToPositionFromSystemProperty() {
527545
return Position.FIRST;
528546
}
529547

530-
/**
531-
* This environment is only added to support internal spanner testing. Support for it can be
532-
* removed in the future. Use {@link SessionPoolOptions#useMultiplexedSession} instead to use
533-
* multiplexed sessions.
534-
*/
535-
@InternalApi
536-
@BetaApi
537-
private static boolean getUseMultiplexedSessionFromEnvVariable() {
538-
return Boolean.parseBoolean(
539-
System.getenv("GOOGLE_CLOUD_SPANNER_ENABLE_MULTIPLEXED_SESSIONS"));
540-
}
541-
542548
public Builder() {}
543549

544550
private Builder(SessionPoolOptions options) {

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

+8-2
Original file line numberDiff line numberDiff line change
@@ -1287,14 +1287,20 @@ public Builder setHost(String host) {
12871287
return this;
12881288
}
12891289

1290-
/** Enables gRPC-GCP extension with the default settings. */
1290+
/**
1291+
* Enables gRPC-GCP extension with the default settings. Do not set
1292+
* GOOGLE_CLOUD_SPANNER_MULTIPLEXED_SESSIONS to true in combination with this option, as
1293+
* Multiplexed sessions are not supported for gRPC-GCP.
1294+
*/
12911295
public Builder enableGrpcGcpExtension() {
12921296
return this.enableGrpcGcpExtension(null);
12931297
}
12941298

12951299
/**
12961300
* Enables gRPC-GCP extension and uses provided options for configuration. The metric registry
1297-
* and default Spanner metric labels will be added automatically.
1301+
* and default Spanner metric labels will be added automatically. Do not set
1302+
* GOOGLE_CLOUD_SPANNER_MULTIPLEXED_SESSIONS to true in combination with this option, as
1303+
* Multiplexed sessions are not supported for gRPC-GCP.
12981304
*/
12991305
public Builder enableGrpcGcpExtension(GcpManagedChannelOptions options) {
13001306
this.grpcGcpExtensionEnabled = true;

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

+109-7
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@
1616

1717
package com.google.cloud.spanner;
1818

19+
import static com.google.common.truth.Truth.assertThat;
1920
import static org.junit.Assert.assertEquals;
2021
import static org.junit.Assert.assertFalse;
22+
import static org.junit.Assert.assertThrows;
2123
import static org.junit.Assert.assertTrue;
2224
import static org.junit.Assume.assumeFalse;
2325
import static org.junit.Assume.assumeTrue;
@@ -27,6 +29,8 @@
2729
import static org.mockito.Mockito.when;
2830

2931
import com.google.cloud.spanner.SessionClient.SessionConsumer;
32+
import java.io.PrintWriter;
33+
import java.io.StringWriter;
3034
import java.lang.reflect.Field;
3135
import java.time.Clock;
3236
import java.time.Duration;
@@ -110,10 +114,9 @@ public void testMaintainer() {
110114
}
111115

112116
@Test
113-
public void testForceDisableEnvVar() throws Exception {
117+
public void testDisableMultiplexedSessionEnvVar() throws Exception {
114118
assumeTrue(isJava8() && !isWindows());
115-
assumeFalse(
116-
System.getenv().containsKey("GOOGLE_CLOUD_SPANNER_FORCE_DISABLE_MULTIPLEXED_SESSIONS"));
119+
assumeFalse(System.getenv().containsKey("GOOGLE_CLOUD_SPANNER_MULTIPLEXED_SESSIONS"));
117120

118121
// Assert that the mux sessions setting is respected by default.
119122
assertTrue(
@@ -129,17 +132,116 @@ public void testForceDisableEnvVar() throws Exception {
129132
(Map<String, String>) field.get(System.getenv());
130133

131134
try {
132-
writeableEnvironmentVariables.put(
133-
"GOOGLE_CLOUD_SPANNER_FORCE_DISABLE_MULTIPLEXED_SESSIONS", "true");
135+
writeableEnvironmentVariables.put("GOOGLE_CLOUD_SPANNER_MULTIPLEXED_SESSIONS", "false");
134136
// Assert that the env var overrides the mux sessions setting.
135137
assertFalse(
136138
SessionPoolOptions.newBuilder()
137139
.setUseMultiplexedSession(true)
138140
.build()
139141
.getUseMultiplexedSession());
140142
} finally {
141-
writeableEnvironmentVariables.remove(
142-
"GOOGLE_CLOUD_SPANNER_FORCE_DISABLE_MULTIPLEXED_SESSIONS");
143+
writeableEnvironmentVariables.remove("GOOGLE_CLOUD_SPANNER_MULTIPLEXED_SESSIONS");
144+
}
145+
}
146+
147+
@Test
148+
public void testEnableMultiplexedSessionEnvVar() throws Exception {
149+
assumeTrue(isJava8() && !isWindows());
150+
assumeFalse(System.getenv().containsKey("GOOGLE_CLOUD_SPANNER_MULTIPLEXED_SESSIONS"));
151+
152+
// Assert that the mux sessions setting is respected by default.
153+
assertFalse(
154+
SessionPoolOptions.newBuilder()
155+
.setUseMultiplexedSession(false)
156+
.build()
157+
.getUseMultiplexedSession());
158+
159+
Class<?> classOfMap = System.getenv().getClass();
160+
Field field = classOfMap.getDeclaredField("m");
161+
field.setAccessible(true);
162+
Map<String, String> writeableEnvironmentVariables =
163+
(Map<String, String>) field.get(System.getenv());
164+
165+
try {
166+
writeableEnvironmentVariables.put("GOOGLE_CLOUD_SPANNER_MULTIPLEXED_SESSIONS", "true");
167+
// Assert that the env var overrides the mux sessions setting.
168+
assertTrue(
169+
SessionPoolOptions.newBuilder()
170+
.setUseMultiplexedSession(false)
171+
.build()
172+
.getUseMultiplexedSession());
173+
} finally {
174+
writeableEnvironmentVariables.remove("GOOGLE_CLOUD_SPANNER_MULTIPLEXED_SESSIONS");
175+
}
176+
}
177+
178+
@Test
179+
public void testIgnoreMultiplexedSessionEnvVar() throws Exception {
180+
assumeTrue(isJava8() && !isWindows());
181+
assumeFalse(System.getenv().containsKey("GOOGLE_CLOUD_SPANNER_MULTIPLEXED_SESSIONS"));
182+
183+
// Assert that the mux sessions setting is respected by default.
184+
assertFalse(
185+
SessionPoolOptions.newBuilder()
186+
.setUseMultiplexedSession(false)
187+
.build()
188+
.getUseMultiplexedSession());
189+
190+
Class<?> classOfMap = System.getenv().getClass();
191+
Field field = classOfMap.getDeclaredField("m");
192+
field.setAccessible(true);
193+
Map<String, String> writeableEnvironmentVariables =
194+
(Map<String, String>) field.get(System.getenv());
195+
196+
try {
197+
writeableEnvironmentVariables.put("GOOGLE_CLOUD_SPANNER_MULTIPLEXED_SESSIONS", "");
198+
// Assert that the env var overrides the mux sessions setting.
199+
assertFalse(
200+
SessionPoolOptions.newBuilder()
201+
.setUseMultiplexedSession(false)
202+
.build()
203+
.getUseMultiplexedSession());
204+
} finally {
205+
writeableEnvironmentVariables.remove("GOOGLE_CLOUD_SPANNER_MULTIPLEXED_SESSIONS");
206+
}
207+
}
208+
209+
@Test
210+
public void testThrowExceptionMultiplexedSessionEnvVarInvalidValues() throws Exception {
211+
assumeTrue(isJava8() && !isWindows());
212+
assumeFalse(System.getenv().containsKey("GOOGLE_CLOUD_SPANNER_MULTIPLEXED_SESSIONS"));
213+
214+
// Assert that the mux sessions setting is respected by default.
215+
assertFalse(
216+
SessionPoolOptions.newBuilder()
217+
.setUseMultiplexedSession(false)
218+
.build()
219+
.getUseMultiplexedSession());
220+
221+
Class<?> classOfMap = System.getenv().getClass();
222+
Field field = classOfMap.getDeclaredField("m");
223+
field.setAccessible(true);
224+
Map<String, String> writeableEnvironmentVariables =
225+
(Map<String, String>) field.get(System.getenv());
226+
227+
try {
228+
writeableEnvironmentVariables.put("GOOGLE_CLOUD_SPANNER_MULTIPLEXED_SESSIONS", "test");
229+
230+
// setting an invalid GOOGLE_CLOUD_SPANNER_MULTIPLEXED_SESSIONS value throws error.
231+
IllegalArgumentException e =
232+
assertThrows(
233+
IllegalArgumentException.class,
234+
() ->
235+
SessionPoolOptions.newBuilder()
236+
.setUseMultiplexedSession(false)
237+
.build()
238+
.getUseMultiplexedSession());
239+
StringWriter sw = new StringWriter();
240+
e.printStackTrace(new PrintWriter(sw));
241+
assertThat(sw.toString())
242+
.contains("GOOGLE_CLOUD_SPANNER_MULTIPLEXED_SESSIONS should be either true or false");
243+
} finally {
244+
writeableEnvironmentVariables.remove("GOOGLE_CLOUD_SPANNER_MULTIPLEXED_SESSIONS");
143245
}
144246
}
145247

0 commit comments

Comments
 (0)