-
Notifications
You must be signed in to change notification settings - Fork 102
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: inline BeginTransaction with first statement #1692
Conversation
@@ -573,6 +563,8 @@ export class Snapshot extends EventEmitter { | |||
|
|||
if (this.id) { | |||
transaction.id = this.id as Uint8Array; | |||
} else if (typeof this._options.readWrite !== 'undefined') { | |||
transaction.begin = this._options; |
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.
Would you mind adding a test that verifies the following:
- A query is the first statement in a read/write transaction. A transaction ID is successfully returned by initial request.
- One or more
PartialResultSet
s are returned by the stream, with (at least) one of them returning a resume token. - The stream fails halfway with an
UNAVAILABLE
error and the stream is restarted with a resume token.
Step 3 should use the transaction ID that was returned by step 1, and not include a BeginTransaction
option.
(https://github.com/googleapis/nodejs-spanner/pull/1253/files is a very old implementation for the same. Some of the tests there might be re-usable.)
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.
Added. thanks a lot for this comment: it helped to find a bug in the code
@@ -1034,6 +1029,8 @@ export class Snapshot extends EventEmitter { | |||
const transaction: spannerClient.spanner.v1.ITransactionSelector = {}; | |||
if (this.id) { |
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.
This does not take into account that a transaction could in theory have multiple requests in flight at the same time. If a transaction starts out with sending two SELECT
statements to the backend, it might very well be that the first that is sent has not yet returned a transaction id before the second is being sent. That will cause both requests to include a BeginTransaction
option.
Consider the following test case (the getRowCountFromStreamingSql
function is defined in test/Spanner.ts):
it('should only inline one begin transaction', async () => {
const database = newTestDatabase();
await database.runTransactionAsync(async tx => {
const rowCount1 = getRowCountFromStreamingSql(tx, {sql: selectSql});
const rowCount2 = getRowCountFromStreamingSql(tx, {sql: selectSql});
await Promise.all([rowCount1, rowCount2]);
await tx.commit();
});
await database.close();
let request = spannerMock.getRequests().find(val => {
return (val as v1.ExecuteSqlRequest).sql;
}) as v1.ExecuteSqlRequest;
assert.ok(request, 'no ExecuteSqlRequest found');
assert.deepStrictEqual(request.transaction!.begin!.readWrite, {});
assert.strictEqual(request.sql, selectSql);
request = spannerMock
.getRequests()
.slice()
.reverse()
.find(val => {
return (val as v1.ExecuteSqlRequest).sql;
}) as v1.ExecuteSqlRequest;
assert.ok(request, 'no ExecuteSqlRequest found');
assert.strictEqual(request.sql, selectSql);
assert.ok(request.transaction!.id, 'TransactionID is not set.');
const beginTxnRequest = spannerMock.getRequests().find(val => {
return (val as v1.BeginTransactionRequest).options?.readWrite;
}) as v1.BeginTransactionRequest;
assert.ok(!beginTxnRequest, 'beginTransaction was called');
});
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.
You're absolutely right. Added a lock that prevents a multiple inline begin transaction at the same time. Thanks!
…on happens; make sure stream requests uses a transaction id from the first response even if the stream fails halfway with an UNAVAILABLE error
src/transaction.ts
Outdated
json, | ||
jsonOptions, | ||
maxResumeRetries, | ||
}).on('response', response => { |
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.
What would happen if the call fails?
As per the design we should make an explicit begin transaction call if the call fails.
cc: @olavloite
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.
if the call fails, the transaction is created explicitly with "BeginTransaction" rpc call.
It's covered in the test: "should use beginTransaction on retry".
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.
Is this done at the backend or do we explicitly call begin transaction from the client side?
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.
from the client side
src/transaction.ts
Outdated
@@ -573,6 +570,8 @@ export class Snapshot extends EventEmitter { | |||
|
|||
if (this.id) { | |||
transaction.id = this.id as Uint8Array; | |||
} else if (typeof this._options.readWrite !== 'undefined') { |
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.
Should this rather be else if(this._options.readWrite)
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.
yes, fixed, thanks
@ko3a4ok Can you please fix the failing test cases ? |
@surbhigarg92, unfortunately, if we just remove it, the explicit beginTransaction call won't be called on the retry. I updated to call it only after the first attempt. Please let me know if it makes sense, or is it better to move the call |
This looks fine. Only thing I am wondering is that with this implementation, we are calling |
Regarding 1.: in this case, we should not create any transaction at all because Regarding 2.: I completely forgot about that, thanks for bringing this up. Added in 2caaa0f |
Merge-on-green attempted to merge your PR for 6 hours, but it was not mergeable because either one of your required status checks failed, one of your required reviews was not approved, or there is a do not merge label. Learn more about your required status checks here: https://help.github.com/en/github/administering-a-repository/enabling-required-status-checks. You can remove and reapply the label to re-run the bot. |
Add inline BeginTransaction for the first read or executeSql request in a transaction.
Each transaction doesn't call
beginTransanction
before its start. Instead, all creation transaction parameters are passed within the first read or executeSql request.In case of a transaction restart,
beginTransaction
is called explicitly.