This repository was archived by the owner on Mar 31, 2026. It is now read-only.
feat(dbapi): add retry_aborts_internally option to Connection#1538
Closed
waiho-gumloop wants to merge 1 commit intogoogleapis:mainfrom
Closed
feat(dbapi): add retry_aborts_internally option to Connection#1538waiho-gumloop wants to merge 1 commit intogoogleapis:mainfrom
waiho-gumloop wants to merge 1 commit intogoogleapis:mainfrom
Conversation
Add a `retry_aborts_internally` flag (default True) to the DBAPI Connection class and the `connect()` function. When set to False, aborted transactions raise `RetryAborted` directly from `commit()` instead of entering the internal statement-replay retry loop. This allows applications that implement their own transaction retry logic (e.g. re-invoking a callable with a fresh session) to avoid nested retry loops and contention amplification under concurrent writes. Equivalent to `RETRY_ABORTS_INTERNALLY` in the Spanner JDBC driver and `ReadWriteStmtBasedTransaction` in the Go client.
Contributor
There was a problem hiding this comment.
Code Review
This pull request introduces the retry_aborts_internally flag to the Spanner DB-API connection, allowing users to disable the automatic internal retry of aborted transactions. This is useful for applications that implement their own retry logic. The changes include updates to the Connection class, the connect factory function, and corresponding unit tests to verify the new behavior. I have no feedback to provide.
Contributor
|
Hi @waiho-gumloop, The code in this repository has moved to https://github.com/googleapis/google-cloud-python/tree/main/packages/google-cloud-spanner. Please could you open a new PR in google-cloud-python? |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Add a
retry_aborts_internallyflag to the DBAPIConnectionclass and theconnect()function. When set toFalse, aborted transactions raiseRetryAborteddirectly fromcommit()instead of entering the internal statement-replay retry loop.Changes
Connection.__init__: Acceptretry_aborts_internallyparameter (defaultTrue)Connection.retry_aborts_internally: Property getter/setter with guard against mid-transaction changesConnection.commit(): Check_retry_aborts_internallybefore entering the replay loop; when disabled, wrapAbortedinRetryAbortedfor PEP 249 complianceconnect(): Pass-throughretry_aborts_internallytoConnectionRationale
Why the internal retry was added
The DBAPI's statement-replay retry was introduced to support Django and other PEP 249 ORMs (original issue googleapis/python-spanner#34). These frameworks build transactions incrementally through individual
cursor.execute()calls — the DBAPI layer sees a sequence of statements but has no callable representing the full transaction. When Spanner aborts a transaction, the only option is to replay all recorded statements and verify checksums to ensure read consistency.Why applications may not want the internal retry
Applications that implement their own transaction retry logic — wrapping the entire transaction in a callable and re-invoking it with a fresh session on abort (similar to
Session.run_in_transaction) — do not need transparent statement replay. The application already re-reads data and makes fresh decisions on each retry, making checksum validation unnecessary.When both layers retry simultaneously, the result is nested retry loops that cause severe problems under contention:
RetryAborted. The outer application retry then starts fresh, having wasted all that time.In our production workload with 10 concurrent writers, disabling the internal retry reduced abort-to-recovery time from ~18 seconds to ~0.05 seconds (using Spanner's server-suggested retry delay) and improved success rates from ~55% to 100%.
Precedent in other Spanner client libraries
This change aligns the Python DBAPI with existing functionality in other Spanner clients:
com.google.cloud.spanner.jdbc)RETRY_ABORTS_INTERNALLYconnection propertytruecloud.google.com/go/spanner)NewReadWriteStmtBasedTransactionvsReadWriteTransactionretry_aborts_internallyconstructor/connect parameterTrueThe JDBC driver's
RETRY_ABORTS_INTERNALLYwas added specifically for the same use case: applications with their own retry wrappers that need to opt out of the driver's internal retry to avoid interference.Usage
When
retry_aborts_internally=False,commit()raisesRetryAborted(a subclass ofOperationalError) on transaction abort, allowing the application's retry logic to handle it directly.Test plan
test_retry_aborts_internally_defaults_true— constructor defaults to Truetest_retry_aborts_internally_set_false— constructor accepts Falsetest_retry_aborts_internally_setter— property setter workstest_retry_aborts_internally_setter_while_transaction_active— setter rejects changes during active transactiontest_commit_retries_internally_when_enabled— commit calls retry_transaction when flag is Truetest_commit_raises_retry_aborted_when_internal_retry_disabled— commit raises RetryAborted when flag is Falsetest_connect_retry_aborts_internally_default— connect() defaults to Truetest_connect_retry_aborts_internally_false— connect() passes False through to ConnectionRelated Issue
Closes googleapis/google-cloud-python#16491