-
Notifications
You must be signed in to change notification settings - Fork 27
chore: delay transaction activation until actual use #552
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
base: main
Are you sure you want to change the base?
Conversation
Store temporary TransactionOptions in the connection state as local options. Local options only apply to the current transaction. This simplifies the internal state handling of the driver, as all transaction state should only be read from the connection state, and not also from a temporary variable. This also enables the use of a combination of temporary transaction options and using SQL statements to set further options. The shared library always includes temporary transaction options, as the BeginTransaction function accepts TransactionOptions as an input argument. This meant that using SQL statements to set further transaction options was not supported through the shared library.
Delay the actual transaction activation until the first actual usage of the transaction. That is; the first time that a statement is being sent to Spanner. This allows the application to amend the transaction options after calling BeginTx or executing `BEGIN TRANSACTION`. The transaction options can be amended by executing a statement like `SET TRANSACTION READ ONLY`.
d046e5a
to
a4f182b
Compare
resp := &spannerpb.CommitResponse{} | ||
if err := proto.Unmarshal(commitMsg.Res, resp); err != nil { | ||
t.Fatalf("Failed to unmarshal commit response: %v", err) | ||
if commitMsg.Length() != 0 { |
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 change optimizes the following transaction shape:
begin;
commit;
This would previously cause a two round-trips to Spanner: One to start the transaction, and one to commit the empty transaction. This is now converted into a no-op on Spanner.
connection.beginTransaction(TransactionOptions.getDefaultInstance()); | ||
connection.commit(); | ||
|
||
// TODO: The library should take a shortcut and just skip committing empty 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.
This is the shortcut mentioned above
if c.batch != nil { | ||
return nil, spanner.ToSpannerError(status.Errorf(codes.FailedPrecondition, "This connection already has an active batch.")) | ||
} | ||
if c.inReadOnlyTransaction() { |
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 check was not used, as the if c.inTransaction
check above already tries to start the batch on the read-only transaction. That will fail, as the read-only transaction returns an error.
c.tempTransactionCloseFunc = options.close | ||
// Start a transaction for the connection state, so we can set the transaction options | ||
// as local options in the current transaction. | ||
_ = c.state.Begin() |
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 has been moved into the BeginTx
function. The flow is now:
- BeginTx -> This starts the transaction for the connection state and creates an 'empty' transaction that can become both a read-only or a read/write transaction at a later moment. The
close
function is an argument for theBeginTx
function. - withTempTransactionOptions is called after the call to
BeginTx
. The transaction options are then stored in the local session state of that transaction. - The application can set further transaction options by executing for example
set transaction read only
- When the first query or DML statement is executed on the transaction, the actual transaction is started. From this point on, it is no longer possible to set further transaction options.
_ = propertyRetryAbortsInternally.SetLocalValue(c.state, false) | ||
} | ||
|
||
c.tx = &delegatingTransaction{ |
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.
A delegatingTransaction
delegates all calls to an underlying read/write or read-only transaction. That underlying transaction is not created yet. Instead, it will automatically be created when the first query or DML statement is executed.
return c.tx, nil | ||
} | ||
|
||
func (c *conn) activateTransaction() (contextTransaction, error) { |
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 function is called when the first query or DML statement is executed on the current transaction. This 'activates' the transaction, and creates the actual read/write or read-only transaction on Spanner.
if result == txResultCommit { | ||
_ = c.state.Commit() | ||
} else { | ||
_ = c.state.Rollback() | ||
} |
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.
Committing or rolling back the connection state is now part of the generic close function for delegatingTransaction
, so we don't need to repeat this for each other transaction.
b5665f2
to
2e15c1b
Compare
Delay the actual transaction activation until the first actual usage of the transaction. That is; the first time that a statement is being sent to Spanner. This allows the application to amend the transaction options after calling BeginTx or executing
BEGIN TRANSACTION
. The transaction options can be amended by executing a statement likeSET TRANSACTION READ ONLY
.