Skip to content

Commit

Permalink
docs: add sample for transaction timeouts (#2599)
Browse files Browse the repository at this point in the history
Our current samples show how to set timeout and retry settings for all RPC invocations for a given RPC method, and how to set a timeout for a single statement, but we did not have a sample for setting a timeout for an entire transaction. This is something that customers have been asking multiple times. This sample shows how a transaction timeout can be set by using a gRPC context with a timeout.
  • Loading branch information
olavloite committed Aug 23, 2023
1 parent dd3e9a0 commit 59cec9b
Show file tree
Hide file tree
Showing 4 changed files with 136 additions and 3 deletions.
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,13 +57,13 @@ implementation 'com.google.cloud:google-cloud-spanner'
If you are using Gradle without BOM, add this to your dependencies:

```Groovy
implementation 'com.google.cloud:google-cloud-spanner:6.45.2'
implementation 'com.google.cloud:google-cloud-spanner:6.45.3'
```

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

```Scala
libraryDependencies += "com.google.cloud" % "google-cloud-spanner" % "6.45.2"
libraryDependencies += "com.google.cloud" % "google-cloud-spanner" % "6.45.3"
```
<!-- {x-version-update-end} -->

Expand Down Expand Up @@ -320,6 +320,7 @@ Samples are in the [`samples/`](https://github.com/googleapis/java-spanner/tree/
| Statement Timeout Example | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/StatementTimeoutExample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/StatementTimeoutExample.java) |
| Tag Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/TagSample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/TagSample.java) |
| Tracing Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/TracingSample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/TracingSample.java) |
| Transaction Timeout Example | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/TransactionTimeoutExample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/TransactionTimeoutExample.java) |
| Update Database Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/UpdateDatabaseSample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/UpdateDatabaseSample.java) |
| Update Database With Default Leader Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/UpdateDatabaseWithDefaultLeaderSample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/UpdateDatabaseWithDefaultLeaderSample.java) |
| Update Instance Config Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/UpdateInstanceConfigSample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/UpdateInstanceConfigSample.java) |
Expand Down Expand Up @@ -430,7 +431,7 @@ Java is a registered trademark of Oracle and/or its affiliates.
[kokoro-badge-link-5]: http://storage.googleapis.com/cloud-devrel-public/java/badges/java-spanner/java11.html
[stability-image]: https://img.shields.io/badge/stability-stable-green
[maven-version-image]: https://img.shields.io/maven-central/v/com.google.cloud/google-cloud-spanner.svg
[maven-version-link]: https://central.sonatype.com/artifact/com.google.cloud/google-cloud-spanner/6.45.2
[maven-version-link]: https://central.sonatype.com/artifact/com.google.cloud/google-cloud-spanner/6.45.3
[authentication]: https://github.com/googleapis/google-cloud-java#authentication
[auth-scopes]: https://developers.google.com/identity/protocols/oauth2/scopes
[predefined-iam-roles]: https://cloud.google.com/iam/docs/understanding-roles#predefined_roles
Expand Down
1 change: 1 addition & 0 deletions samples/snippets/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
<groupId>com.google.cloud.samples</groupId>
<artifactId>shared-configuration</artifactId>
<version>1.2.0</version>
<relativePath/>
</parent>

<properties>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/*
* Copyright 2023 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.example.spanner;

// [START spanner_transaction_timeout]

import com.google.cloud.spanner.DatabaseClient;
import com.google.cloud.spanner.DatabaseId;
import com.google.cloud.spanner.ResultSet;
import com.google.cloud.spanner.Spanner;
import com.google.cloud.spanner.SpannerOptions;
import com.google.cloud.spanner.Statement;
import io.grpc.Context;
import io.grpc.Context.CancellableContext;
import io.grpc.Deadline;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
* Sample showing how to set a timeout for an entire transaction for the Cloud Spanner Java client.
*/
class TransactionTimeoutExample {

static void executeTransactionWithTimeout() {
// TODO(developer): Replace these variables before running the sample.
String projectId = "my-project";
String instanceId = "my-instance";
String databaseId = "my-database";

executeTransactionWithTimeout(projectId, instanceId, databaseId, 60L, TimeUnit.SECONDS);
}

// Execute a read/write transaction with a timeout for the entire transaction.
static void executeTransactionWithTimeout(
String projectId,
String instanceId,
String databaseId,
long timeoutValue,
TimeUnit timeoutUnit) {
try (Spanner spanner = SpannerOptions.newBuilder().setProjectId(projectId).build()
.getService()) {
DatabaseClient client =
spanner.getDatabaseClient(DatabaseId.of(projectId, instanceId, databaseId));
// Create a gRPC context with a deadline and with cancellation.
// gRPC context deadlines require the use of a scheduled executor.
ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
try (CancellableContext context =
Context.current()
.withDeadline(Deadline.after(timeoutValue, timeoutUnit), executor)
.withCancellation()) {
context.run(
() -> {
client
.readWriteTransaction()
.run(
transaction -> {
try (ResultSet resultSet =
transaction.executeQuery(
Statement.of(
"SELECT SingerId, FirstName, LastName\n"
+ "FROM Singers\n"
+ "ORDER BY LastName, FirstName"))) {
while (resultSet.next()) {
System.out.printf(
"%d %s %s\n",
resultSet.getLong("SingerId"),
resultSet.getString("FirstName"),
resultSet.getString("LastName"));
}
}
String sql =
"INSERT INTO Singers (SingerId, FirstName, LastName)\n"
+ "VALUES (20, 'George', 'Washington')";
long rowCount = transaction.executeUpdate(Statement.of(sql));
System.out.printf("%d record inserted.%n", rowCount);
return null;
});
});
}
}
}
}
// [END spanner_transaction_timeout]
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,20 @@
package com.example.spanner;

import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;

import com.google.api.gax.longrunning.OperationFuture;
import com.google.cloud.spanner.DatabaseAdminClient;
import com.google.cloud.spanner.DatabaseClient;
import com.google.cloud.spanner.DatabaseId;
import com.google.cloud.spanner.ErrorCode;
import com.google.cloud.spanner.Instance;
import com.google.cloud.spanner.KeySet;
import com.google.cloud.spanner.Mutation;
import com.google.cloud.spanner.Spanner;
import com.google.cloud.spanner.SpannerException;
import com.google.cloud.spanner.SpannerOptions;
import com.google.cloud.spanner.Value;
import com.google.common.collect.ImmutableList;
Expand All @@ -36,6 +41,7 @@
import java.util.Collections;
import java.util.Iterator;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
Expand Down Expand Up @@ -133,6 +139,33 @@ public void executeSqlWithTimeout_shouldWriteData() {
assertThat(out).contains("1 record inserted.");
}

@Test
public void testTransactionWithTimeout_shouldWriteData() {
String projectId = spanner.getOptions().getProjectId();
String out =
runExample(
() ->
TransactionTimeoutExample.executeTransactionWithTimeout(
projectId, instanceId, databaseId, 60L, TimeUnit.SECONDS));
assertTrue(out, out.contains("1 record inserted"));
}

@Test
public void testTransactionWithTimeout_shouldFailWithDeadlineExceeded() {
String projectId = spanner.getOptions().getProjectId();
// Execute a transaction with a 5 millisecond timeout. The transaction executes both a read, a
// write, and a commit operation. Each of these would normally take at least 5 milliseconds.
SpannerException exception =
assertThrows(
SpannerException.class,
() ->
runExample(
() ->
TransactionTimeoutExample.executeTransactionWithTimeout(
projectId, instanceId, databaseId, 5L, TimeUnit.MILLISECONDS)));
assertEquals(ErrorCode.DEADLINE_EXCEEDED, exception.getErrorCode());
}

@Test
public void addNumericColumn_shouldSuccessfullyAddColumn()
throws InterruptedException, ExecutionException {
Expand Down

0 comments on commit 59cec9b

Please sign in to comment.