Skip to content

Commit ec820a1

Browse files
perf: minor optimizations to the standard query path (#3101)
* perf: minor optimizations to the standard query path Optimizes the standard query path slightly by adding some caching and removing regex matching that happened on the critical path of each query that was executed. * 🦉 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 cc3352b commit ec820a1

10 files changed

+245
-119
lines changed

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

+30-21
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
import com.google.spanner.v1.DirectedReadOptions;
4646
import com.google.spanner.v1.ExecuteBatchDmlRequest;
4747
import com.google.spanner.v1.ExecuteSqlRequest;
48+
import com.google.spanner.v1.ExecuteSqlRequest.Builder;
4849
import com.google.spanner.v1.ExecuteSqlRequest.QueryMode;
4950
import com.google.spanner.v1.ExecuteSqlRequest.QueryOptions;
5051
import com.google.spanner.v1.PartialResultSet;
@@ -457,7 +458,7 @@ void initTransaction() {
457458

458459
// A per-transaction sequence number used to identify this ExecuteSqlRequests. Required for DML,
459460
// ignored for query by the server.
460-
private AtomicLong seqNo = new AtomicLong();
461+
private final AtomicLong seqNo = new AtomicLong();
461462

462463
// Allow up to 512MB to be buffered (assuming 1MB chunks). In practice, restart tokens are sent
463464
// much more frequently.
@@ -488,6 +489,10 @@ long getSeqNo() {
488489
return seqNo.incrementAndGet();
489490
}
490491

492+
protected boolean isReadOnly() {
493+
return true;
494+
}
495+
491496
protected boolean isRouteToLeader() {
492497
return false;
493498
}
@@ -622,19 +627,18 @@ private ResultSet executeQueryInternal(
622627
@VisibleForTesting
623628
QueryOptions buildQueryOptions(QueryOptions requestOptions) {
624629
// Shortcut for the most common return value.
625-
if (defaultQueryOptions.equals(QueryOptions.getDefaultInstance()) && requestOptions == null) {
626-
return QueryOptions.getDefaultInstance();
630+
if (requestOptions == null) {
631+
return defaultQueryOptions;
627632
}
628-
// Create a builder based on the default query options.
629-
QueryOptions.Builder builder = defaultQueryOptions.toBuilder();
630-
// Then overwrite with specific options for this query.
631-
if (requestOptions != null) {
632-
builder.mergeFrom(requestOptions);
633-
}
634-
return builder.build();
633+
return defaultQueryOptions.toBuilder().mergeFrom(requestOptions).build();
635634
}
636635

637636
RequestOptions buildRequestOptions(Options options) {
637+
// Shortcut for the most common return value.
638+
if (!(options.hasPriority() || options.hasTag() || getTransactionTag() != null)) {
639+
return RequestOptions.getDefaultInstance();
640+
}
641+
638642
RequestOptions.Builder builder = RequestOptions.newBuilder();
639643
if (options.hasPriority()) {
640644
builder.setPriority(options.priority());
@@ -655,16 +659,7 @@ ExecuteSqlRequest.Builder getExecuteSqlRequestBuilder(
655659
.setSql(statement.getSql())
656660
.setQueryMode(queryMode)
657661
.setSession(session.getName());
658-
Map<String, Value> stmtParameters = statement.getParameters();
659-
if (!stmtParameters.isEmpty()) {
660-
com.google.protobuf.Struct.Builder paramsBuilder = builder.getParamsBuilder();
661-
for (Map.Entry<String, Value> param : stmtParameters.entrySet()) {
662-
paramsBuilder.putFields(param.getKey(), Value.toProto(param.getValue()));
663-
if (param.getValue() != null && param.getValue().getType() != null) {
664-
builder.putParamTypes(param.getKey(), param.getValue().getType().toProto());
665-
}
666-
}
667-
}
662+
addParameters(builder, statement.getParameters());
668663
if (withTransactionSelector) {
669664
TransactionSelector selector = getTransactionSelector();
670665
if (selector != null) {
@@ -679,12 +674,26 @@ ExecuteSqlRequest.Builder getExecuteSqlRequestBuilder(
679674
} else if (defaultDirectedReadOptions != null) {
680675
builder.setDirectedReadOptions(defaultDirectedReadOptions);
681676
}
682-
builder.setSeqno(getSeqNo());
677+
if (!isReadOnly()) {
678+
builder.setSeqno(getSeqNo());
679+
}
683680
builder.setQueryOptions(buildQueryOptions(statement.getQueryOptions()));
684681
builder.setRequestOptions(buildRequestOptions(options));
685682
return builder;
686683
}
687684

685+
static void addParameters(ExecuteSqlRequest.Builder builder, Map<String, Value> stmtParameters) {
686+
if (!stmtParameters.isEmpty()) {
687+
com.google.protobuf.Struct.Builder paramsBuilder = builder.getParamsBuilder();
688+
for (Map.Entry<String, Value> param : stmtParameters.entrySet()) {
689+
paramsBuilder.putFields(param.getKey(), Value.toProto(param.getValue()));
690+
if (param.getValue() != null && param.getValue().getType() != null) {
691+
builder.putParamTypes(param.getKey(), param.getValue().getType().toProto());
692+
}
693+
}
694+
}
695+
}
696+
688697
ExecuteBatchDmlRequest.Builder getExecuteBatchDmlRequestBuilder(
689698
Iterable<Statement> statements, Options options) {
690699
ExecuteBatchDmlRequest.Builder builder =
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/*
2+
* Copyright 2021 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.spanner;
17+
18+
import com.google.auth.oauth2.GoogleCredentials;
19+
import com.google.cloud.spanner.SpannerOptions.FixedCloseableExecutorProvider;
20+
import java.nio.file.Files;
21+
import java.nio.file.Paths;
22+
import java.util.concurrent.Executors;
23+
import java.util.concurrent.ScheduledExecutorService;
24+
import java.util.concurrent.ThreadFactory;
25+
import java.util.concurrent.ThreadLocalRandom;
26+
import org.threeten.bp.Duration;
27+
28+
public class LatencyTest {
29+
30+
public static void main(String[] args) throws Exception {
31+
ThreadFactory threadFactory =
32+
ThreadFactoryUtil.tryCreateVirtualThreadFactory("spanner-async-worker");
33+
if (threadFactory == null) {
34+
return;
35+
}
36+
ScheduledExecutorService service = Executors.newScheduledThreadPool(0, threadFactory);
37+
Spanner spanner =
38+
SpannerOptions.newBuilder()
39+
.setCredentials(
40+
GoogleCredentials.fromStream(
41+
Files.newInputStream(
42+
Paths.get("/Users/loite/Downloads/appdev-soda-spanner-staging.json"))))
43+
.setSessionPoolOption(
44+
SessionPoolOptions.newBuilder()
45+
.setWaitForMinSessions(Duration.ofSeconds(5L))
46+
// .setUseMultiplexedSession(true)
47+
.build())
48+
.setUseVirtualThreads(true)
49+
.setAsyncExecutorProvider(FixedCloseableExecutorProvider.create(service))
50+
.build()
51+
.getService();
52+
DatabaseClient client =
53+
spanner.getDatabaseClient(
54+
DatabaseId.of("appdev-soda-spanner-staging", "knut-test-ycsb", "latencytest"));
55+
for (int i = 0; i < 1000000; i++) {
56+
try (AsyncResultSet resultSet =
57+
client
58+
.singleUse()
59+
.executeQueryAsync(
60+
Statement.newBuilder("select col_varchar from latency_test where col_bigint=$1")
61+
.bind("p1")
62+
.to(ThreadLocalRandom.current().nextLong(100000L))
63+
.build())) {
64+
while (resultSet.next()) {
65+
for (int col = 0; col < resultSet.getColumnCount(); col++) {
66+
if (resultSet.getValue(col) == null) {
67+
throw new IllegalStateException();
68+
}
69+
}
70+
}
71+
}
72+
}
73+
}
74+
}

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

+1-9
Original file line numberDiff line numberDiff line change
@@ -221,14 +221,6 @@ private ByteString initTransaction(final Options options) {
221221
private void setParameters(
222222
final ExecuteSqlRequest.Builder requestBuilder,
223223
final Map<String, Value> statementParameters) {
224-
if (!statementParameters.isEmpty()) {
225-
com.google.protobuf.Struct.Builder paramsBuilder = requestBuilder.getParamsBuilder();
226-
for (Map.Entry<String, Value> param : statementParameters.entrySet()) {
227-
paramsBuilder.putFields(param.getKey(), Value.toProto(param.getValue()));
228-
if (param.getValue() != null && param.getValue().getType() != null) {
229-
requestBuilder.putParamTypes(param.getKey(), param.getValue().getType().toProto());
230-
}
231-
}
232-
}
224+
AbstractReadContext.addParameters(requestBuilder, statementParameters);
233225
}
234226
}

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

+5-3
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ abstract class ResumableStreamIterator extends AbstractIterator<PartialResultSet
6060
private final RetrySettings streamingRetrySettings;
6161
private final Set<Code> retryableCodes;
6262
private static final Logger logger = Logger.getLogger(ResumableStreamIterator.class.getName());
63-
private final BackOff backOff;
63+
private BackOff backOff;
6464
private final LinkedList<PartialResultSet> buffer = new LinkedList<>();
6565
private final int maxBufferSize;
6666
private final ISpan span;
@@ -106,7 +106,6 @@ protected ResumableStreamIterator(
106106
this.span = tracer.spanBuilderWithExplicitParent(streamName, parent, attributes);
107107
this.streamingRetrySettings = Preconditions.checkNotNull(streamingRetrySettings);
108108
this.retryableCodes = Preconditions.checkNotNull(retryableCodes);
109-
this.backOff = newBackOff();
110109
}
111110

112111
private ExponentialBackOff newBackOff() {
@@ -271,7 +270,10 @@ protected PartialResultSet computeNext() {
271270
if (delay != -1) {
272271
backoffSleep(context, delay);
273272
} else {
274-
backoffSleep(context, backOff);
273+
if (this.backOff == null) {
274+
this.backOff = newBackOff();
275+
}
276+
backoffSleep(context, this.backOff);
275277
}
276278
}
277279

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

+10-1
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,15 @@ interface SessionTransaction {
9999
void close();
100100
}
101101

102+
private static final Map<SpannerRpc.Option, ?>[] CHANNEL_HINT_OPTIONS =
103+
new Map[SpannerOptions.MAX_CHANNELS];
104+
105+
static {
106+
for (int i = 0; i < CHANNEL_HINT_OPTIONS.length; i++) {
107+
CHANNEL_HINT_OPTIONS[i] = optionMap(SessionOption.channelHint(i));
108+
}
109+
}
110+
102111
static final int NO_CHANNEL_HINT = -1;
103112

104113
private final SpannerImpl spanner;
@@ -125,7 +134,7 @@ interface SessionTransaction {
125134
if (channelHint == NO_CHANNEL_HINT) {
126135
return sessionReference.getOptions();
127136
}
128-
return optionMap(SessionOption.channelHint(channelHint));
137+
return CHANNEL_HINT_OPTIONS[channelHint % CHANNEL_HINT_OPTIONS.length];
129138
}
130139

131140
@Override

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -755,7 +755,7 @@ Builder setPoolMaintainerClock(Clock poolMaintainerClock) {
755755
* SessionPoolOptions#maxSessions} based on the traffic load. Failing to do so will result in
756756
* higher latencies.
757757
*/
758-
Builder setUseMultiplexedSession(boolean useMultiplexedSession) {
758+
public Builder setUseMultiplexedSession(boolean useMultiplexedSession) {
759759
this.useMultiplexedSession = useMultiplexedSession;
760760
return this;
761761
}

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ public class SpannerOptions extends ServiceOptions<Spanner, SpannerOptions> {
9999
ImmutableSet.of(
100100
"https://www.googleapis.com/auth/spanner.admin",
101101
"https://www.googleapis.com/auth/spanner.data");
102-
private static final int MAX_CHANNELS = 256;
102+
static final int MAX_CHANNELS = 256;
103103
@VisibleForTesting static final int DEFAULT_CHANNELS = 4;
104104
// Set the default number of channels to GRPC_GCP_ENABLED_DEFAULT_CHANNELS when gRPC-GCP extension
105105
// is enabled, to make sure there are sufficient channels available to move the sessions to a

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

+5
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,11 @@ private TransactionContextImpl(Builder builder) {
218218
session.getOptions(), ThreadLocalRandom.current().nextLong(Long.MAX_VALUE));
219219
}
220220

221+
@Override
222+
protected boolean isReadOnly() {
223+
return false;
224+
}
225+
221226
@Override
222227
protected boolean isRouteToLeader() {
223228
return true;

0 commit comments

Comments
 (0)