-
Notifications
You must be signed in to change notification settings - Fork 123
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
Conversation
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.
@@ -225,13 +237,22 @@ public boolean isReadOnly() { | |||
} | |||
|
|||
@Override | |||
void checkValidTransaction() { | |||
void checkValidTransaction(ParsedStatement statement) { |
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unrelated to delayed transactions?
There was a problem hiding this comment.
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() { |
There was a problem hiding this comment.
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?)
There was a problem hiding this comment.
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()); |
There was a problem hiding this comment.
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)?
There was a problem hiding this comment.
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.
🤖 I have created a release *beep* *boop* --- ## [6.43.0](https://togithub.com/googleapis/java-spanner/compare/v6.42.3...v6.43.0) (2023-06-07) ### Features * Delay transaction start option ([#2462](https://togithub.com/googleapis/java-spanner/issues/2462)) ([f1cbd16](https://togithub.com/googleapis/java-spanner/commit/f1cbd168a7e5f48206cdfc2d782835cf7ccb8b0d)) * Make administrative request retries optional ([#2476](https://togithub.com/googleapis/java-spanner/issues/2476)) ([ee6548c](https://togithub.com/googleapis/java-spanner/commit/ee6548cfa511d6efc99f508290ed0b1ce025a4cc)) ### Dependencies * Update dependency com.google.cloud:google-cloud-shared-dependencies to v3.11.0 ([#2486](https://togithub.com/googleapis/java-spanner/issues/2486)) ([82400d5](https://togithub.com/googleapis/java-spanner/commit/82400d5576c3ffe08ff6bb94d8b1a307e2f41662)) --- This PR was generated with [Release Please](https://togithub.com/googleapis/release-please). See [documentation](https://togithub.com/googleapis/release-please#release-please).
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.