Skip to content

chore: enable multplexed sessions for experimental host #3676

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Mar 11, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions google-cloud-spanner/clirr-ignored-differences.xml
Original file line number Diff line number Diff line change
Expand Up @@ -822,11 +822,11 @@
<method>java.lang.Object runTransaction(com.google.cloud.spanner.connection.Connection$TransactionCallable)</method>
</difference>

<!-- Added external host option -->
<!-- Added experimental host option -->
<difference>
<differenceType>7012</differenceType>
<className>com/google/cloud/spanner/SpannerOptions$SpannerEnvironment</className>
<method>com.google.auth.oauth2.GoogleCredentials getDefaultExternalHostCredentials()</method>
<method>com.google.auth.oauth2.GoogleCredentials getDefaultExperimentalHostCredentials()</method>
</difference>

<!-- Default sequence kind -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public class SessionPoolOptions {
private static final int DEFAULT_MAX_SESSIONS = 400;
private static final int DEFAULT_MIN_SESSIONS = 100;
private static final int DEFAULT_INC_STEP = 25;
private static final int EXPERIMENTAL_HOST_REGULAR_SESSIONS = 0;
private static final ActionOnExhaustion DEFAULT_ACTION = ActionOnExhaustion.BLOCK;
private final int minSessions;
private final int maxSessions;
Expand Down Expand Up @@ -89,8 +90,12 @@ private SessionPoolOptions(Builder builder) {
// minSessions > maxSessions is only possible if the user has only set a value for maxSessions.
// We allow that to prevent code that only sets a value for maxSessions to break if the
// maxSessions value is less than the default for minSessions.
this.minSessions = Math.min(builder.minSessions, builder.maxSessions);
this.maxSessions = builder.maxSessions;
this.minSessions =
builder.isExperimentalHost
? EXPERIMENTAL_HOST_REGULAR_SESSIONS
: Math.min(builder.minSessions, builder.maxSessions);
this.maxSessions =
builder.isExperimentalHost ? EXPERIMENTAL_HOST_REGULAR_SESSIONS : builder.maxSessions;
this.incStep = builder.incStep;
this.maxIdleSessions = builder.maxIdleSessions;
this.writeSessionsFraction = builder.writeSessionsFraction;
Expand All @@ -114,26 +119,30 @@ private SessionPoolOptions(Builder builder) {
// useMultiplexedSession priority => Environment var > private setter > client default
Boolean useMultiplexedSessionFromEnvVariable = getUseMultiplexedSessionFromEnvVariable();
this.useMultiplexedSession =
(useMultiplexedSessionFromEnvVariable != null)
? useMultiplexedSessionFromEnvVariable
: builder.useMultiplexedSession;
builder.isExperimentalHost
|| ((useMultiplexedSessionFromEnvVariable != null)
? useMultiplexedSessionFromEnvVariable
: builder.useMultiplexedSession);
// useMultiplexedSessionForRW priority => Environment var > private setter > client default
Boolean useMultiplexedSessionForRWFromEnvVariable =
getUseMultiplexedSessionForRWFromEnvVariable();
this.useMultiplexedSessionForRW =
(useMultiplexedSessionForRWFromEnvVariable != null)
? useMultiplexedSessionForRWFromEnvVariable
: builder.useMultiplexedSessionForRW;
builder.isExperimentalHost
|| ((useMultiplexedSessionForRWFromEnvVariable != null)
? useMultiplexedSessionForRWFromEnvVariable
: builder.useMultiplexedSessionForRW);
// useMultiplexedSessionPartitionedOps priority => Environment var > private setter > client
// default
Boolean useMultiplexedSessionFromEnvVariablePartitionedOps =
getUseMultiplexedSessionFromEnvVariablePartitionedOps();
this.useMultiplexedSessionForPartitionedOps =
(useMultiplexedSessionFromEnvVariablePartitionedOps != null)
? useMultiplexedSessionFromEnvVariablePartitionedOps
: builder.useMultiplexedSessionPartitionedOps;
builder.isExperimentalHost
|| ((useMultiplexedSessionFromEnvVariablePartitionedOps != null)
? useMultiplexedSessionFromEnvVariablePartitionedOps
: builder.useMultiplexedSessionPartitionedOps);
this.multiplexedSessionMaintenanceDuration = builder.multiplexedSessionMaintenanceDuration;
this.skipVerifyingBeginTransactionForMuxRW = builder.skipVerifyingBeginTransactionForMuxRW;
this.skipVerifyingBeginTransactionForMuxRW =
builder.isExperimentalHost || builder.skipVerifyingBeginTransactionForMuxRW;
}

@Override
Expand Down Expand Up @@ -617,6 +626,7 @@ public static class Builder {
private Duration multiplexedSessionMaintenanceDuration = Duration.ofDays(7);
private Clock poolMaintainerClock = Clock.INSTANCE;
private boolean skipVerifyingBeginTransactionForMuxRW = false;
private boolean isExperimentalHost = false;

private static Position getReleaseToPositionFromSystemProperty() {
// NOTE: This System property is a beta feature. Support for it can be removed in the future.
Expand Down Expand Up @@ -813,6 +823,12 @@ public Builder setWarnAndCloseIfInactiveTransactions() {
return this;
}

@InternalApi
public Builder setExperimentalHost() {
this.isExperimentalHost = true;
return this;
}

/**
* If there are inactive transactions, release the resources consumed by such transactions. A
* transaction is classified as inactive if it executes for more than a system defined duration.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,6 @@
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Pattern;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
Expand All @@ -117,10 +116,7 @@ public class SpannerOptions extends ServiceOptions<Spanner, SpannerOptions> {

private static final String API_SHORT_NAME = "Spanner";
private static final String DEFAULT_HOST = "https://spanner.googleapis.com";
private static final String CLOUD_SPANNER_HOST_FORMAT = ".*\\.googleapis\\.com.*";

@VisibleForTesting
static final Pattern CLOUD_SPANNER_HOST_PATTERN = Pattern.compile(CLOUD_SPANNER_HOST_FORMAT);
private static final String EXPERIMENTAL_HOST_PROJECT_ID = "default";

private static final ImmutableSet<String> SCOPES =
ImmutableSet.of(
Expand Down Expand Up @@ -856,13 +852,13 @@ default String getMonitoringHost() {
return null;
}

default GoogleCredentials getDefaultExternalHostCredentials() {
default GoogleCredentials getDefaultExperimentalHostCredentials() {
return null;
}
}

static final String DEFAULT_SPANNER_EXTERNAL_HOST_CREDENTIALS =
"SPANNER_EXTERNAL_HOST_AUTH_TOKEN";
static final String DEFAULT_SPANNER_EXPERIMENTAL_HOST_CREDENTIALS =
"SPANNER_EXPERIMENTAL_HOST_AUTH_TOKEN";

/**
* Default implementation of {@link SpannerEnvironment}. Reads all configuration from environment
Expand Down Expand Up @@ -921,8 +917,8 @@ public String getMonitoringHost() {
}

@Override
public GoogleCredentials getDefaultExternalHostCredentials() {
return getOAuthTokenFromFile(System.getenv(DEFAULT_SPANNER_EXTERNAL_HOST_CREDENTIALS));
public GoogleCredentials getDefaultExperimentalHostCredentials() {
return getOAuthTokenFromFile(System.getenv(DEFAULT_SPANNER_EXPERIMENTAL_HOST_CREDENTIALS));
}
}

Expand Down Expand Up @@ -991,7 +987,7 @@ public static class Builder
private boolean enableBuiltInMetrics = SpannerOptions.environment.isEnableBuiltInMetrics();
private String monitoringHost = SpannerOptions.environment.getMonitoringHost();
private SslContext mTLSContext = null;
private boolean isExternalHost = false;
private boolean isExperimentalHost = false;

private static String createCustomClientLibToken(String token) {
return token + " " + ServiceOptions.getGoogApiClientLibName();
Expand Down Expand Up @@ -1484,14 +1480,20 @@ public Builder setDecodeMode(DecodeMode decodeMode) {
@Override
public Builder setHost(String host) {
super.setHost(host);
if (!CLOUD_SPANNER_HOST_PATTERN.matcher(host).matches()) {
this.isExternalHost = true;
}
// Setting a host should override any SPANNER_EMULATOR_HOST setting.
setEmulatorHost(null);
return this;
}

@ExperimentalApi("https://github.com/googleapis/java-spanner/pull/3676")
public Builder setExperimentalHost(String host) {
super.setHost(host);
super.setProjectId(EXPERIMENTAL_HOST_PROJECT_ID);
setSessionPoolOption(SessionPoolOptions.newBuilder().setExperimentalHost().build());
this.isExperimentalHost = true;
return this;
}

/**
* Enables gRPC-GCP extension with the default settings. Do not set
* GOOGLE_CLOUD_SPANNER_MULTIPLEXED_SESSIONS to true in combination with this option, as
Expand Down Expand Up @@ -1530,7 +1532,7 @@ public Builder setEmulatorHost(String emulatorHost) {

/**
* Configures mTLS authentication using the provided client certificate and key files. mTLS is
* only supported for external spanner hosts.
* only supported for experimental spanner hosts.
*
* @param clientCertificate Path to the client certificate file.
* @param clientCertificateKey Path to the client private key file.
Expand Down Expand Up @@ -1657,8 +1659,8 @@ public SpannerOptions build() {
this.setChannelConfigurator(ManagedChannelBuilder::usePlaintext);
// As we are using plain text, we should never send any credentials.
this.setCredentials(NoCredentials.getInstance());
} else if (isExternalHost && credentials == null) {
credentials = environment.getDefaultExternalHostCredentials();
} else if (isExperimentalHost && credentials == null) {
credentials = environment.getDefaultExperimentalHostCredentials();
}
if (this.numChannels == null) {
this.numChannels =
Expand Down Expand Up @@ -1700,8 +1702,8 @@ public static void useDefaultEnvironment() {
}

@InternalApi
public static GoogleCredentials getDefaultExternalHostCredentialsFromSysEnv() {
return getOAuthTokenFromFile(System.getenv(DEFAULT_SPANNER_EXTERNAL_HOST_CREDENTIALS));
public static GoogleCredentials getDefaultExperimentalCredentialsFromSysEnv() {
return getOAuthTokenFromFile(System.getenv(DEFAULT_SPANNER_EXPERIMENTAL_HOST_CREDENTIALS));
}

private static @Nullable GoogleCredentials getOAuthTokenFromFile(@Nullable String file) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import static com.google.cloud.spanner.connection.ConnectionProperties.ENABLE_EXTENDED_TRACING;
import static com.google.cloud.spanner.connection.ConnectionProperties.ENCODED_CREDENTIALS;
import static com.google.cloud.spanner.connection.ConnectionProperties.ENDPOINT;
import static com.google.cloud.spanner.connection.ConnectionProperties.IS_EXPERIMENTAL_HOST;
import static com.google.cloud.spanner.connection.ConnectionProperties.LENIENT;
import static com.google.cloud.spanner.connection.ConnectionProperties.MAX_COMMIT_DELAY;
import static com.google.cloud.spanner.connection.ConnectionProperties.MAX_PARTITIONED_PARALLELISM;
Expand Down Expand Up @@ -221,6 +222,7 @@ public String[] getValidValues() {
private static final LocalConnectionChecker LOCAL_CONNECTION_CHECKER =
new LocalConnectionChecker();
static final boolean DEFAULT_USE_PLAIN_TEXT = false;
static final boolean DEFAULT_IS_EXPERIMENTAL_HOST = false;
static final boolean DEFAULT_AUTOCOMMIT = true;
static final boolean DEFAULT_READONLY = false;
static final boolean DEFAULT_RETRY_ABORTS_INTERNALLY = true;
Expand Down Expand Up @@ -260,6 +262,8 @@ public String[] getValidValues() {
static final boolean DEFAULT_AUTO_BATCH_DML = false;
static final long DEFAULT_AUTO_BATCH_DML_UPDATE_COUNT = 1L;
static final boolean DEFAULT_AUTO_BATCH_DML_UPDATE_COUNT_VERIFICATION = true;
private static final String EXPERIMENTAL_HOST_PROJECT_ID = "default";
private static final String DEFAULT_EXPERIMENTAL_HOST_INSTANCE_ID = "default";

private static final String PLAIN_TEXT_PROTOCOL = "http:";
private static final String HOST_PROTOCOL = "https:";
Expand All @@ -268,6 +272,8 @@ public String[] getValidValues() {
private static final String DEFAULT_EMULATOR_HOST = "http://localhost:9010";
/** Use plain text is only for local testing purposes. */
static final String USE_PLAIN_TEXT_PROPERTY_NAME = "usePlainText";
/** Connect to a Experimental Host * */
static final String IS_EXPERIMENTAL_HOST_PROPERTY_NAME = "isExperimentalHost";
/** Client certificate path to establish mTLS */
static final String CLIENT_CERTIFICATE_PROPERTY_NAME = "clientCertificate";
/** Client key path to establish mTLS */
Expand Down Expand Up @@ -444,6 +450,10 @@ static boolean isEnableTransactionalConnectionStateForPostgreSQL() {
USE_PLAIN_TEXT_PROPERTY_NAME,
"Use a plain text communication channel (i.e. non-TLS) for communicating with the server (true/false). Set this value to true for communication with the Cloud Spanner emulator.",
DEFAULT_USE_PLAIN_TEXT),
ConnectionProperty.createBooleanProperty(
IS_EXPERIMENTAL_HOST_PROPERTY_NAME,
"Set this value to true for communication with an Experimental Host.",
DEFAULT_IS_EXPERIMENTAL_HOST),
ConnectionProperty.createStringProperty(
CLIENT_CERTIFICATE_PROPERTY_NAME,
"Specifies the file path to the client certificate required for establishing an mTLS connection."),
Expand Down Expand Up @@ -664,7 +674,7 @@ private boolean isValidUri(String uri) {
return SPANNER_URI_PATTERN.matcher(uri).matches();
}

private boolean isValidExternalHostUri(String uri) {
private boolean isValidExperimentalHostUri(String uri) {
return EXTERNAL_HOST_PATTERN.matcher(uri).matches();
}

Expand Down Expand Up @@ -725,7 +735,7 @@ private boolean isValidExternalHostUri(String uri) {
* @return this builder
*/
public Builder setUri(String uri) {
if (!isValidExternalHostUri(uri)) {
if (!isValidExperimentalHostUri(uri)) {
Preconditions.checkArgument(
isValidUri(uri),
"The specified URI is not a valid Cloud Spanner connection URI. Please specify a URI in the format \"cloudspanner:[//host[:port]]/projects/project-id[/instances/instance-id[/databases/database-name]][\\?property-name=property-value[;property-name=property-value]*]?\"");
Expand Down Expand Up @@ -857,10 +867,10 @@ public static Builder newBuilder() {

private ConnectionOptions(Builder builder) {
Matcher matcher;
boolean isExternalHost = false;
if (builder.isValidExternalHostUri(builder.uri)) {
boolean isExperimentalHostPattern = false;
if (builder.isValidExperimentalHostUri(builder.uri)) {
matcher = Builder.EXTERNAL_HOST_PATTERN.matcher(builder.uri);
isExternalHost = true;
isExperimentalHostPattern = true;
} else {
matcher = Builder.SPANNER_URI_PATTERN.matcher(builder.uri);
}
Expand Down Expand Up @@ -923,8 +933,8 @@ private ConnectionOptions(Builder builder) {
getInitialConnectionPropertyValue(AUTO_CONFIG_EMULATOR),
usePlainText,
System.getenv());
GoogleCredentials defaultExternalHostCredentials =
SpannerOptions.getDefaultExternalHostCredentialsFromSysEnv();
GoogleCredentials defaultExperimentalHostCredentials =
SpannerOptions.getDefaultExperimentalCredentialsFromSysEnv();
// Using credentials on a plain text connection is not allowed, so if the user has not specified
// any credentials and is using a plain text connection, we should not try to get the
// credentials from the environment, but default to NoCredentials.
Expand All @@ -939,8 +949,9 @@ && getInitialConnectionPropertyValue(OAUTH_TOKEN) == null
this.credentials =
new GoogleCredentials(
new AccessToken(getInitialConnectionPropertyValue(OAUTH_TOKEN), null));
} else if (isExternalHost && defaultExternalHostCredentials != null) {
this.credentials = defaultExternalHostCredentials;
} else if ((isExperimentalHostPattern || isExperimentalHost())
&& defaultExperimentalHostCredentials != null) {
this.credentials = defaultExperimentalHostCredentials;
} else if (getInitialConnectionPropertyValue(CREDENTIALS_PROVIDER) != null) {
try {
this.credentials = getInitialConnectionPropertyValue(CREDENTIALS_PROVIDER).getCredentials();
Expand Down Expand Up @@ -981,16 +992,19 @@ && getInitialConnectionPropertyValue(OAUTH_TOKEN) == null
this.sessionPoolOptions = sessionPoolOptionsBuilder.build();
} else if (builder.sessionPoolOptions != null) {
this.sessionPoolOptions = builder.sessionPoolOptions;
} else if (isExperimentalHostPattern || isExperimentalHost()) {
this.sessionPoolOptions =
SessionPoolOptions.newBuilder().setExperimentalHost().setAutoDetectDialect(true).build();
} else {
this.sessionPoolOptions = SessionPoolOptions.newBuilder().setAutoDetectDialect(true).build();
}

String projectId = "default";
String projectId = EXPERIMENTAL_HOST_PROJECT_ID;
String instanceId = matcher.group(Builder.INSTANCE_GROUP);
if (!isExternalHost) {
if (!isExperimentalHost() && !isExperimentalHostPattern) {
projectId = matcher.group(Builder.PROJECT_GROUP);
} else if (instanceId == null) {
instanceId = "default";
} else if (instanceId == null && isExperimentalHost()) {
instanceId = DEFAULT_EXPERIMENTAL_HOST_INSTANCE_ID;
}
if (Builder.DEFAULT_PROJECT_ID_PLACEHOLDER.equalsIgnoreCase(projectId)) {
projectId = getDefaultProjectId(this.credentials);
Expand Down Expand Up @@ -1311,6 +1325,10 @@ boolean isUsePlainText() {
|| getInitialConnectionPropertyValue(USE_PLAIN_TEXT);
}

boolean isExperimentalHost() {
return getInitialConnectionPropertyValue(IS_EXPERIMENTAL_HOST);
}

String getClientCertificate() {
return getInitialConnectionPropertyValue(CLIENT_CERTIFICATE);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
import static com.google.cloud.spanner.connection.ConnectionOptions.DEFAULT_ENABLE_END_TO_END_TRACING;
import static com.google.cloud.spanner.connection.ConnectionOptions.DEFAULT_ENABLE_EXTENDED_TRACING;
import static com.google.cloud.spanner.connection.ConnectionOptions.DEFAULT_ENDPOINT;
import static com.google.cloud.spanner.connection.ConnectionOptions.DEFAULT_IS_EXPERIMENTAL_HOST;
import static com.google.cloud.spanner.connection.ConnectionOptions.DEFAULT_KEEP_TRANSACTION_ALIVE;
import static com.google.cloud.spanner.connection.ConnectionOptions.DEFAULT_LENIENT;
import static com.google.cloud.spanner.connection.ConnectionOptions.DEFAULT_MAX_PARTITIONED_PARALLELISM;
Expand Down Expand Up @@ -76,6 +77,7 @@
import static com.google.cloud.spanner.connection.ConnectionOptions.ENABLE_EXTENDED_TRACING_PROPERTY_NAME;
import static com.google.cloud.spanner.connection.ConnectionOptions.ENCODED_CREDENTIALS_PROPERTY_NAME;
import static com.google.cloud.spanner.connection.ConnectionOptions.ENDPOINT_PROPERTY_NAME;
import static com.google.cloud.spanner.connection.ConnectionOptions.IS_EXPERIMENTAL_HOST_PROPERTY_NAME;
import static com.google.cloud.spanner.connection.ConnectionOptions.KEEP_TRANSACTION_ALIVE_PROPERTY_NAME;
import static com.google.cloud.spanner.connection.ConnectionOptions.LENIENT_PROPERTY_NAME;
import static com.google.cloud.spanner.connection.ConnectionOptions.MAX_PARTITIONED_PARALLELISM_PROPERTY_NAME;
Expand Down Expand Up @@ -197,7 +199,14 @@ public class ConnectionProperties {
BOOLEANS,
BooleanConverter.INSTANCE,
Context.STARTUP);

static final ConnectionProperty<Boolean> IS_EXPERIMENTAL_HOST =
create(
IS_EXPERIMENTAL_HOST_PROPERTY_NAME,
"Set this value to true for communication with a Experimental Host.",
DEFAULT_IS_EXPERIMENTAL_HOST,
BOOLEANS,
BooleanConverter.INSTANCE,
Context.STARTUP);
static final ConnectionProperty<String> CLIENT_CERTIFICATE =
create(
CLIENT_CERTIFICATE_PROPERTY_NAME,
Expand Down
Loading
Loading