Skip to content
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

feat: delay transaction start option #2462

Merged
merged 8 commits into from Jun 2, 2023
Merged

Conversation

olavloite
Copy link
Collaborator

@olavloite olavloite commented May 23, 2023

Adds an opt-in to delay the actual start of a read/write transaction until the first write operation. This reduces lock contention and can reduce latency for read/write transactions that execute (many) read operations before any write operations, at the expense of a lower transaction isolation level. Typical workloads that benefit from this option are ORMs (e.g. Hibernate). The application must be able to handle the lower isolation level.

This feature will automatically propagate into clients that use the Connection API, such as the JDBC driver and PGAdapter.

Adds an opt-in to delay the actual start of a read/write transaction
until the first write operation. This reduces lock contention and can
reduce latency for read/write transactions that execute (many) read
operations before any write operations, at the expense of a lower
transaction isolation level. Typical workloads that benefit from this
option are ORMs (e.g. Hibernate). The application must be able to handle
the lower isolation level.
@product-auto-label product-auto-label bot added size: xl Pull request size is extra large. api: spanner Issues related to the googleapis/java-spanner API. labels May 23, 2023
@olavloite olavloite marked this pull request as ready for review May 26, 2023 10:32
@olavloite olavloite requested review from a team as code owners May 26, 2023 10:32
@@ -225,13 +237,22 @@ public boolean isReadOnly() {
}

@Override
void checkValidTransaction() {
void checkValidTransaction(ParsedStatement statement) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: checkValidationTransaction mutates state within the transaction (it did before as well with the future), so perhaps it is a good chance to rename the method? (very nitty, so feel free to ignore)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's indeed a good point. Renamed to checkOrCreateValidTransaction.

@@ -622,7 +651,8 @@ public void onSuccess(long[] result) {}
@Override
public ApiFuture<Void> writeAsync(Iterable<Mutation> mutations) {
Preconditions.checkNotNull(mutations);
checkValidTransaction();
// We actually don't need a transaction yet, as mutations are buffered until commit.
checkValidTransaction(null);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: can we avoid passing null here? Do we need this call to set the transactionStarted or can we avoid this call?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to set the transactionStarted, and we need to verify that the transaction is in a valid state, so we can't skip it altogether. Also; the method takes a ParsedStatement as an input argument so it can check the type of statement, and we don't have a SQL statement that is equivalent with writing/buffering mutations.

What we can however do is slightly rearrange the methods so we have a separate method only for verifying the valid state of the transaction and marking it as started, but skipping the creation of the underlying transaction. I've changed it to that.

state = UnitOfWorkState.COMMITTING;
commitResponseFuture = SettableApiFuture.create();
ApiFuture<Void> res;
if (retryAbortsInternally) {
// Check if this transaction actually needs to commit anything.
if (txContextFuture == null) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just double checking my understanding: checkValidTransaction(COMMIT_STATEMENT) will initialize the txContextFuture here, so it won't be null for blind writes, correct?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct, although it will only be initialized for blind writes if the transaction actually has any mutations. So a transaction that does not do anything except calling Commit will be a no-op (it already was).

@@ -114,6 +114,8 @@ public abstract class AbstractMockServerTest {
Statement.of("INSERT INTO TEST (ID, NAME) VALUES (1, 'test aborted')");
public static final Statement INSERT_RETURNING_STATEMENT =
Statement.of("INSERT INTO TEST (ID, NAME) VALUES (1, 'test aborted') THEN RETURN *");
public static final Statement PG_INSERT_RETURNING_STATEMENT =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unrelated to delayed transactions?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added this so we could include tests that execute DML with a RETURNING clause in both dialects. The reason that it is relevant is that these statements are executed using the executeQuery method, and we should ensure that these statements trigger the start of the underlying transaction, even though they are executed through the executeQuery method.

So this statement is referenced in the DelayTransactionStartUntilFirstWriteMockServerTest

}

@Test
public void testSingleQuery() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: rename to something a bit more clear. It seems we test that we never commit nor rollback, because there are no writes (thus we are using a singleUse read only transaction?)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I renamed the tests in this class to more clearly reflect that they test transactions with a single query, ... etc.


assertEquals(2, mockSpanner.countRequestsOfType(ExecuteSqlRequest.class));
assertFalse(mockSpanner.getRequestsOfType(ExecuteSqlRequest.class).get(0).hasTransaction());
assertFalse(mockSpanner.getRequestsOfType(ExecuteSqlRequest.class).get(1).hasTransaction());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does the second query has a transaction? I would expect both of them to be using a singleUse (because there were no writes)?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you misread the assertion... Or I'm missing something. The line says assertFalse(... hasTransaction()).

So your assumption is right. Both are single-use read-only.

@olavloite olavloite added the kokoro:force-run Add this label to force Kokoro to re-run the tests. label Jun 2, 2023
@yoshi-kokoro yoshi-kokoro removed the kokoro:force-run Add this label to force Kokoro to re-run the tests. label Jun 2, 2023
@olavloite olavloite merged commit f1cbd16 into main Jun 2, 2023
20 of 21 checks passed
@olavloite olavloite deleted the delay-transaction-start branch June 2, 2023 14:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
api: spanner Issues related to the googleapis/java-spanner API. size: xl Pull request size is extra large.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants