Skip to content

Commit d71c87c

Browse files
authored
chore: turn off autosavepoints by default (#3303)
Auto-savepoints are no longer needed when using Emulator v1.5.23 or higher, as that version introduced support for aborting the existing transaction. That solution is better than auto-savepoints, as auto-savepoints only work as long as all transactions come from the same JVM.
1 parent 4fc3b7c commit d71c87c

File tree

4 files changed

+67
-13
lines changed

4 files changed

+67
-13
lines changed

google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionImpl.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1921,6 +1921,7 @@ UnitOfWork createNewUnitOfWork(
19211921
.build();
19221922
case READ_WRITE_TRANSACTION:
19231923
return ReadWriteTransaction.newBuilder()
1924+
.setUsesEmulator(options.usesEmulator())
19241925
.setUseAutoSavepointsForEmulator(options.useAutoSavepointsForEmulator())
19251926
.setDatabaseClient(dbClient)
19261927
.setDelayTransactionStartUntilFirstWrite(delayTransactionStartUntilFirstWrite)

google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionOptions.java

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
import com.google.common.annotations.VisibleForTesting;
3838
import com.google.common.base.Preconditions;
3939
import com.google.common.base.Strings;
40+
import com.google.common.base.Suppliers;
4041
import com.google.common.collect.Sets;
4142
import com.google.spanner.v1.ExecuteSqlRequest.QueryOptions;
4243
import io.opentelemetry.api.OpenTelemetry;
@@ -382,6 +383,10 @@ private static String generateGuardedConnectionPropertyError(
382383
+ "The instance and database in the connection string will automatically be created if these do not yet exist on the emulator. "
383384
+ "Add dialect=postgresql to the connection string to make sure that the database that is created uses the PostgreSQL dialect.",
384385
false),
386+
ConnectionProperty.createBooleanProperty(
387+
"useAutoSavepointsForEmulator",
388+
"Automatically creates savepoints for each statement in a read/write transaction when using the Emulator. This is no longer needed when using Emulator version 1.5.23 or higher.",
389+
false),
385390
ConnectionProperty.createBooleanProperty(
386391
LENIENT_PROPERTY_NAME,
387392
"Silently ignore unknown properties in the connection string/properties (true/false)",
@@ -740,6 +745,7 @@ public static Builder newBuilder() {
740745
private final boolean returnCommitStats;
741746
private final Long maxCommitDelay;
742747
private final boolean autoConfigEmulator;
748+
private final boolean useAutoSavepointsForEmulator;
743749
private final Dialect dialect;
744750
private final RpcPriority rpcPriority;
745751
private final DdlInTransactionMode ddlInTransactionMode;
@@ -801,6 +807,7 @@ private ConnectionOptions(Builder builder) {
801807
this.returnCommitStats = parseReturnCommitStats(this.uri);
802808
this.maxCommitDelay = parseMaxCommitDelay(this.uri);
803809
this.autoConfigEmulator = parseAutoConfigEmulator(this.uri);
810+
this.useAutoSavepointsForEmulator = parseUseAutoSavepointsForEmulator(this.uri);
804811
this.dialect = parseDialect(this.uri);
805812
this.usePlainText = this.autoConfigEmulator || parseUsePlainText(this.uri);
806813
this.host =
@@ -1170,6 +1177,11 @@ static boolean parseAutoConfigEmulator(String uri) {
11701177
return Boolean.parseBoolean(value);
11711178
}
11721179

1180+
static boolean parseUseAutoSavepointsForEmulator(String uri) {
1181+
String value = parseUriProperty(uri, "useAutoSavepointsForEmulator");
1182+
return Boolean.parseBoolean(value);
1183+
}
1184+
11731185
@VisibleForTesting
11741186
static Dialect parseDialect(String uri) {
11751187
String value = parseUriProperty(uri, DIALECT_PROPERTY_NAME);
@@ -1535,6 +1547,14 @@ public Duration getMaxCommitDelay() {
15351547
return maxCommitDelay == null ? null : Duration.ofMillis(maxCommitDelay);
15361548
}
15371549

1550+
boolean usesEmulator() {
1551+
return Suppliers.memoize(
1552+
() ->
1553+
this.autoConfigEmulator
1554+
|| !Strings.isNullOrEmpty(System.getenv("SPANNER_EMULATOR_HOST")))
1555+
.get();
1556+
}
1557+
15381558
/**
15391559
* Whether connections created by this {@link ConnectionOptions} will automatically try to connect
15401560
* to the emulator using the default host/port of the emulator, and automatically create the
@@ -1548,11 +1568,11 @@ public boolean isAutoConfigEmulator() {
15481568
/**
15491569
* Returns true if a connection should generate auto-savepoints for retrying transactions on the
15501570
* emulator. This allows some more concurrent transactions on the emulator.
1571+
*
1572+
* <p>This is no longer needed since version 1.5.23 of the emulator.
15511573
*/
15521574
boolean useAutoSavepointsForEmulator() {
1553-
// For now, this option is directly linked to the option autoConfigEmulator=true, which is the
1554-
// recommended way to configure the emulator for the Connection API.
1555-
return autoConfigEmulator;
1575+
return useAutoSavepointsForEmulator;
15561576
}
15571577

15581578
public Dialect getDialect() {

google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ReadWriteTransaction.java

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,8 @@ class ReadWriteTransaction extends AbstractMultiUseTransaction {
121121
*/
122122
private static final String AUTO_SAVEPOINT_NAME = "_auto_savepoint";
123123

124+
private final boolean usesEmulator;
125+
124126
/**
125127
* Indicates whether an automatic savepoint should be generated after each statement, so the
126128
* transaction can be manually aborted and retried by the Connection API when connected to the
@@ -191,6 +193,7 @@ public void onSuccess(V result) {
191193
}
192194

193195
static class Builder extends AbstractMultiUseTransaction.Builder<Builder, ReadWriteTransaction> {
196+
private boolean usesEmulator;
194197
private boolean useAutoSavepointsForEmulator;
195198
private DatabaseClient dbClient;
196199
private Boolean retryAbortsInternally;
@@ -203,6 +206,11 @@ static class Builder extends AbstractMultiUseTransaction.Builder<Builder, ReadWr
203206

204207
private Builder() {}
205208

209+
Builder setUsesEmulator(boolean usesEmulator) {
210+
this.usesEmulator = usesEmulator;
211+
return this;
212+
}
213+
206214
Builder setUseAutoSavepointsForEmulator(boolean useAutoSavepoints) {
207215
this.useAutoSavepointsForEmulator = useAutoSavepoints;
208216
return this;
@@ -269,13 +277,13 @@ static Builder newBuilder() {
269277
private ReadWriteTransaction(Builder builder) {
270278
super(builder);
271279
this.transactionId = ID_GENERATOR.incrementAndGet();
272-
this.useAutoSavepointsForEmulator =
273-
builder.useAutoSavepointsForEmulator && builder.retryAbortsInternally;
280+
this.usesEmulator = builder.usesEmulator;
281+
this.useAutoSavepointsForEmulator = builder.useAutoSavepointsForEmulator;
274282
// Use a higher max for internal retries if auto-savepoints have been enabled for the emulator.
275283
// This can cause a larger number of transactions to be aborted and retried, and retrying on the
276284
// emulator is fast, so increasing the limit is reasonable.
277285
this.maxInternalRetries =
278-
this.useAutoSavepointsForEmulator
286+
builder.usesEmulator && builder.retryAbortsInternally
279287
? DEFAULT_MAX_INTERNAL_RETRIES * 10
280288
: DEFAULT_MAX_INTERNAL_RETRIES;
281289
this.dbClient = builder.dbClient;
@@ -1076,7 +1084,7 @@ private void handleAborted(AbortedException aborted) {
10761084
Thread.sleep(delay);
10771085
} else if (aborted.isEmulatorOnlySupportsOneTransactionException()) {
10781086
//noinspection BusyWait
1079-
Thread.sleep(ThreadLocalRandom.current().nextInt(50));
1087+
Thread.sleep(ThreadLocalRandom.current().nextInt(1, 5));
10801088
}
10811089
} catch (InterruptedException ie) {
10821090
Thread.currentThread().interrupt();

google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITEmulatorConcurrentTransactionsTest.java

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,20 +19,23 @@
1919
import static com.google.cloud.spanner.testing.EmulatorSpannerHelper.isUsingEmulator;
2020
import static org.junit.Assert.assertEquals;
2121
import static org.junit.Assert.assertFalse;
22+
import static org.junit.Assert.assertNull;
2223
import static org.junit.Assert.assertTrue;
2324
import static org.junit.Assume.assumeTrue;
2425

2526
import com.google.cloud.spanner.KeySet;
2627
import com.google.cloud.spanner.Mutation;
2728
import com.google.cloud.spanner.ParallelIntegrationTest;
2829
import com.google.cloud.spanner.ResultSet;
30+
import com.google.cloud.spanner.SpannerExceptionFactory;
2931
import com.google.cloud.spanner.Statement;
3032
import com.google.cloud.spanner.connection.Connection;
3133
import com.google.cloud.spanner.connection.ITAbstractSpannerTest;
3234
import java.util.ArrayList;
3335
import java.util.List;
3436
import java.util.concurrent.ExecutorService;
3537
import java.util.concurrent.Executors;
38+
import java.util.concurrent.Future;
3639
import java.util.concurrent.ThreadLocalRandom;
3740
import java.util.concurrent.TimeUnit;
3841
import java.util.concurrent.atomic.AtomicInteger;
@@ -41,14 +44,24 @@
4144
import org.junit.Test;
4245
import org.junit.experimental.categories.Category;
4346
import org.junit.runner.RunWith;
44-
import org.junit.runners.JUnit4;
47+
import org.junit.runners.Parameterized;
48+
import org.junit.runners.Parameterized.Parameter;
49+
import org.junit.runners.Parameterized.Parameters;
4550

4651
@Category(ParallelIntegrationTest.class)
47-
@RunWith(JUnit4.class)
52+
@RunWith(Parameterized.class)
4853
public class ITEmulatorConcurrentTransactionsTest extends ITAbstractSpannerTest {
54+
@Parameters(name = "Use auto-savepoints={0}")
55+
public static Object[] parameters() {
56+
return new Object[] {Boolean.TRUE, Boolean.FALSE};
57+
}
58+
59+
@Parameter public boolean useAutoSavepointsForEmulator;
60+
4961
@Override
5062
public void appendConnectionUri(StringBuilder uri) {
51-
uri.append(";autoConfigEmulator=true;autoCommit=false");
63+
uri.append(";autoConfigEmulator=true;autoCommit=false;useAutoSavepointsForEmulator=")
64+
.append(useAutoSavepointsForEmulator);
5265
}
5366

5467
@Override
@@ -118,15 +131,21 @@ public void testSingleThreadRandomTransactions() {
118131
}
119132

120133
@Test
121-
public void testMultiThreadedRandomTransactions() throws InterruptedException {
134+
public void testMultiThreadedRandomTransactions() throws Exception {
122135
int numThreads = ThreadLocalRandom.current().nextInt(10) + 5;
123136
ExecutorService executor = Executors.newFixedThreadPool(numThreads);
124137
AtomicInteger numRowsInserted = new AtomicInteger();
138+
List<Future<?>> futures = new ArrayList<>(numThreads);
125139
for (int thread = 0; thread < numThreads; thread++) {
126-
executor.submit(() -> runRandomTransactions(numRowsInserted));
140+
futures.add(executor.submit(() -> runRandomTransactions(numRowsInserted)));
127141
}
128142
executor.shutdown();
129143
assertTrue(executor.awaitTermination(60L, TimeUnit.SECONDS));
144+
// Get the results of each transaction so the test case fails with a logical error message if
145+
// any of the transactions failed.
146+
for (Future<?> future : futures) {
147+
assertNull(future.get());
148+
}
130149
verifyRowCount(numRowsInserted.get());
131150
}
132151

@@ -141,7 +160,7 @@ private void runRandomTransactions(AtomicInteger numRowsInserted) {
141160
while (!connections.isEmpty()) {
142161
int index = ThreadLocalRandom.current().nextInt(connections.size());
143162
Connection connection = connections.get(index);
144-
if (ThreadLocalRandom.current().nextInt(10) < 3) {
163+
if (ThreadLocalRandom.current().nextInt(10) < 5) {
145164
connection.commit();
146165
connection.close();
147166
assertEquals(connection, connections.remove(index));
@@ -155,6 +174,12 @@ private void runRandomTransactions(AtomicInteger numRowsInserted) {
155174
.build()));
156175
numRowsInserted.incrementAndGet();
157176
}
177+
try {
178+
// Make sure to have a small wait between statements.
179+
Thread.sleep(ThreadLocalRandom.current().nextInt(1, 5));
180+
} catch (InterruptedException interruptedException) {
181+
throw SpannerExceptionFactory.propagateInterrupt(interruptedException);
182+
}
158183
}
159184
} finally {
160185
for (Connection connection : connections) {

0 commit comments

Comments
 (0)