Skip to content

feat: support begin with AbortedException for manager interface #3835

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

Open
wants to merge 14 commits into
base: main
Choose a base branch
from

Conversation

harshachinta
Copy link
Contributor

@harshachinta harshachinta commented Apr 24, 2025

Aborted transactions should ideally be retried in the same transaction manager instance because the client library will ensure to populate the "Prev txn attempt's txn id" on retry (for mux sessions) which helps preserve the lock order(priority) of the transaction in scenarios of high contention thats causing txn wounding. This is achieved by using resetForRetry() that automatically takes care of preserving the lock order.

But if the customer application retries aborted transactions on a new TransactionManager instance with out using the resetForRetry(), we lose the lock order of the previous attempt.

To address this scenario of preserving the lockorder across transaction manger instance for a retry of the same transaction
Current proposal as per client lib team recommendation:

  1. Overload the Begin API in the TransactionManager to accept an AbortedException argument.
  2. Client library will populate the aborted transaction's transaction ID in the AbortedException object which is propagated up the customer's application stack.
  3. On the customer end, they will just need to pass in this AbortedException object to the "Begin" API when they retry the transaction on the new transaction manager instance.

Note: This will be a no-op for regular sessions.
b/407037309

@product-auto-label product-auto-label bot added size: m Pull request size is medium. api: spanner Issues related to the googleapis/java-spanner API. labels Apr 24, 2025
@product-auto-label product-auto-label bot added size: l Pull request size is large. and removed size: m Pull request size is medium. labels Apr 24, 2025
@harshachinta harshachinta changed the title feat(spanner): support begin with aborted exception for manager feat: support begin with AbortedException for manager interface Apr 24, 2025
@harshachinta harshachinta marked this pull request as ready for review April 24, 2025 10:21
@harshachinta harshachinta requested review from a team as code owners April 24, 2025 10:21
@harshachinta harshachinta requested a review from olavloite April 24, 2025 10:21
@@ -793,6 +793,7 @@ public SpannerException onError(SpannerException e, boolean withBeginTransaction
long delay = -1L;
if (exceptionToThrow instanceof AbortedException) {
delay = exceptionToThrow.getRetryDelayInMillis();
((AbortedException) exceptionToThrow).setTransactionID(this.transactionId);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Should this be
this.transactionId or
this.transactionId != null ? this.transactionId : this.getPreviousTransactionId()

Copy link
Collaborator

Choose a reason for hiding this comment

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

I guess this.transactionId != null ? this.transactionId : this.getPreviousTransactionId() would cause the transaction priority to be preserved also when the begin call of a transaction fails, right? In that case, I think we should do that.

Comment on lines +174 to +175
* Initializes a new read-write transaction. This method must be called before performing any
* operations, and it can only be invoked once per transaction lifecycle.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
* Initializes a new read-write transaction. This method must be called before performing any
* operations, and it can only be invoked once per transaction lifecycle.
* Initializes a new read-write transaction that is a retry of a previously aborted transaction.
* This method must be called before performing any
* operations, and it can only be invoked once per transaction lifecycle.

Comment on lines +177 to +180
* <p>This is especially useful in scenarios involving multiplexed sessions and when creating a
* new transaction for retry attempts. If {@link #resetForRetryAsync()} is not used, you can pass
* the {@link AbortedException} from a previous attempt here to preserve the transaction's
* priority.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
* <p>This is especially useful in scenarios involving multiplexed sessions and when creating a
* new transaction for retry attempts. If {@link #resetForRetryAsync()} is not used, you can pass
* the {@link AbortedException} from a previous attempt here to preserve the transaction's
* priority.
* <p>This method should only be used when multiplexed sessions are enabled to create a retry
* for a previously aborted transaction. This method can be used instead of {@link #resetForRetryAsync()}
* to create a retry. Using this method or {@link #resetForRetryAsync()} will have the same effect.
* You must pass in the {@link AbortedException} from the previous attempt to preserve the transaction's
* priority.

private ApiFuture<TransactionContext> internalBeginAsync(boolean firstAttempt) {
@Override
public TransactionContextFutureImpl beginAsync(AbortedException exception) {
Preconditions.checkState(txn == null, "begin can only be called once");
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
Preconditions.checkState(txn == null, "begin can only be called once");
Preconditions.checkState(txn == null, "begin can only be called once");
Preconditions.checkNotNull(exception, "AbortedException from the previous attempt is required");

@@ -61,6 +61,18 @@ enum TransactionState {
*/
TransactionContext begin();

/**
* Initializes a new read-write transaction. This method must be called before performing any
Copy link
Collaborator

Choose a reason for hiding this comment

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

See my comment on beginAsync. I think that we should be more explicit in when this method should be used, and not describe it as 'useful in scenarios involving multiplexed sessions'. I think that could lead to wrong usage of this method.

@Override
public TransactionContext begin(AbortedException exception) {
Preconditions.checkState(txn == null, "begin can only be called once");
ByteString previousAbortedTransactionID =
Copy link
Collaborator

Choose a reason for hiding this comment

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

Here also: Add a not-null check for the AbortedException.

try (IScope s = tracer.withSpan(span)) {
txn = session.newTransaction(options, /* previousTransactionId = */ ByteString.EMPTY);
txn = session.newTransaction(options, /* previousTransactionId = */ previousTransactionId);
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: you can remove the comment for previousTransactionId, as that is now clear from the variable name

@@ -793,6 +793,7 @@ public SpannerException onError(SpannerException e, boolean withBeginTransaction
long delay = -1L;
if (exceptionToThrow instanceof AbortedException) {
delay = exceptionToThrow.getRetryDelayInMillis();
((AbortedException) exceptionToThrow).setTransactionID(this.transactionId);
Copy link
Collaborator

Choose a reason for hiding this comment

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

I guess this.transactionId != null ? this.transactionId : this.getPreviousTransactionId() would cause the transaction priority to be preserved also when the begin call of a transaction fails, right? In that case, I think we should do that.

Copy link

Warning: This pull request is touching the following templated files:

  • .github/workflows/ci.yaml

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: l Pull request size is large.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants