Skip to content

sql: add a retry loop for stmts in READ COMMITTED txns#107044

Merged
craig[bot] merged 3 commits intocockroachdb:masterfrom
rafiss:retry-read-committed
Aug 17, 2023
Merged

sql: add a retry loop for stmts in READ COMMITTED txns#107044
craig[bot] merged 3 commits intocockroachdb:masterfrom
rafiss:retry-read-committed

Conversation

@rafiss
Copy link
Copy Markdown
Collaborator

@rafiss rafiss commented Jul 18, 2023

fixes #100145

This new loop retries individual statements inside an explicit READ
COMMITTED transaction. This is possible because each statement in a READ
COMMITTED transaction has a different read timestamp. The conn executor
already has retry logic for all implicit transactions, and we continue to
use those when possible.

A session setting controls how many retries will be performed for a statement
inside of an explicit READ COMMITTED transaction.

Release note (sql change): Added the
max_retries_for_read_committed session variable. It
defaults to 10, and determines the number of times an individual
statement in an explicit READ COMMITTED transaction will be retried
if it encounters a retriable transaction error.

@cockroach-teamcity
Copy link
Copy Markdown
Member

This change is Reviewable

@rafiss rafiss force-pushed the retry-read-committed branch from 1d1d2d9 to 0b0b287 Compare July 18, 2023 16:21
@rafiss rafiss marked this pull request as ready for review July 18, 2023 16:21
@rafiss rafiss requested review from a team as code owners July 18, 2023 16:21
@rafiss rafiss requested review from michae2 and nvb July 18, 2023 16:21
@rafiss rafiss force-pushed the retry-read-committed branch from 0b0b287 to 633add0 Compare July 18, 2023 17:12
Copy link
Copy Markdown
Collaborator

@fqazi fqazi left a comment

Choose a reason for hiding this comment

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

:LGTM: minus CI not being happy

Reviewed 1 of 1 files at r1, 3 of 3 files at r2, 5 of 5 files at r3, all commit messages.
Reviewable status: :shipit: complete! 1 of 0 LGTMs obtained (waiting on @michae2 and @nvanbenschoten)

@rafiss rafiss force-pushed the retry-read-committed branch from 633add0 to 9c30984 Compare July 18, 2023 21:27
pkg/sql/vars.go Outdated
// CockroachDB extension. Configures the maximum number of automatic retries
// to perform for statements in explicit READ COMMITTED transactions that
// see a transaction retry error.
`max_retries_for_read_committed_transactions`: {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

how about a cluster setting for this purpose?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

i lean towards session variable, since cluster settings are generally intended for usage by operators, and require higher permissions on the cluster. also, in this case, it is reasonable that different workloads may want different values here, since it depends on how much contention they see.

Copy link
Copy Markdown
Contributor

@Xiang-Gu Xiang-Gu left a comment

Choose a reason for hiding this comment

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

Good work!

Copy link
Copy Markdown
Collaborator

@michae2 michae2 left a comment

Choose a reason for hiding this comment

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

Nice work!

Reviewed 1 of 1 files at r1, 3 of 3 files at r2.
Reviewable status: :shipit: complete! 0 of 0 LGTMs obtained (and 1 stale) (waiting on @chengxiong-ruan, @fqazi, @nvanbenschoten, and @rafiss)


pkg/sql/conn_executor_exec.go line 963 at r2 (raw file):

	if ex.state.mu.txn.IsOpen() &&
		ex.state.mu.txn.IsoLevel() == isolation.ReadCommitted &&
		!ex.implicitTxn() &&

Do we also want to defer to the state machine retry for the first statement of explicit transactions?


pkg/sql/conn_executor_exec.go line 980 at r2 (raw file):

	}

	maxExecCount := 1

Rather than going through this loop codepath in every case, I think it would be cleaner to put the retry loop in a separate conn executor function that wraps dispatchToExecutionEngine and is only called for read committed transactions. That will do two things: (a) get rid of some of the conditional logic, and (b) add a function to the stack that will be visible when debugging which could help us know we're in a read committed transaction.


pkg/sql/conn_executor_exec.go line 987 at r2 (raw file):

	}

	for attemptNum := 0; attemptNum < maxExecCount; attemptNum++ {

Does this loop need some kind of (randomized) backoff? For example, I think two update statements could both conflict with each other and both need to retry repeatedly.


pkg/sql/conn_executor_exec.go line 1005 at r2 (raw file):

				break
			}
			if !errIsRetriable(maybeRetriableErr) || attemptNum == maxRetries {

Does errIsRetriable return true for any errors that need a full transaction retry (rather than just a statement retry)? If so, we might be performing extra useless statement retries in those cases, when we should go straight to a transaction-level retry.


pkg/sql/vars.go line 2034 at r4 (raw file):

Previously, rafiss (Rafi Shamim) wrote…

i lean towards session variable, since cluster settings are generally intended for usage by operators, and require higher permissions on the cluster. also, in this case, it is reasonable that different workloads may want different values here, since it depends on how much contention they see.

bikeshedding: I understand that "_transactions" refers to the fact that the whole transaction is at read-committed isolation, but find it a little confusing for this setting. I think it would be clearer to leave it off or change it to "_statements".

Copy link
Copy Markdown
Collaborator Author

@rafiss rafiss left a comment

Choose a reason for hiding this comment

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

Reviewable status: :shipit: complete! 0 of 0 LGTMs obtained (and 1 stale) (waiting on @chengxiong-ruan, @fqazi, and @nvanbenschoten)


pkg/sql/conn_executor_exec.go line 1019 at r4 (raw file):

			}
			res.SetError(nil)
			ex.state.mu.txn.PrepareForRetry(ctx)

we should wait for kv: remove TxnCoordSender.PrepareRetryableError, rationalize ManualRestart to complete.

@rafiss
Copy link
Copy Markdown
Collaborator Author

rafiss commented Aug 10, 2023

For some reason I can't type into Reviewable, but replying here:

Do we also want to defer to the state machine retry for the first statement of explicit transactions?

I don't think so - the state machine doesn't do retries if the BEGIN; and first statement are sent in two different commands. But in this case, we would want to.

Rather than going through this loop codepath in every case, I think it would be cleaner to put the retry loop in a separate conn executor function that wraps dispatchToExecutionEngine and is only called for read committed transactions. That will do two things: (a) get rid of some of the conditional logic, and (b) add a function to the stack that will be visible when debugging which could help us know we're in a read committed transaction.

Very great idea. Done, and hopefully it's simpler to read now.

Does errIsRetriable return true for any errors that need a full transaction retry (rather than just a statement retry)? If so, we might be performing extra useless statement retries in those cases, when we should go straight to a transaction-level retry.

Great catch. I've now rebased on top of #105161 so that I can check with TxnMustRestartFromBeginning.

bikeshedding: I understand that "_transactions" refers to the fact that the whole transaction is at read-committed isolation, but find it a little confusing for this setting. I think it would be clearer to leave it off or change it to "_statements".

I just removed "_transactions" from the end now.

@rafiss rafiss force-pushed the retry-read-committed branch from 0c74392 to f4f17c6 Compare August 10, 2023 07:20
@rafiss rafiss force-pushed the retry-read-committed branch from f4f17c6 to 2e3268d Compare August 11, 2023 00:39
@rafiss rafiss requested review from a team as code owners August 11, 2023 00:39
@rafiss rafiss requested review from rhu713 and removed request for a team August 11, 2023 00:39
@rafiss rafiss force-pushed the retry-read-committed branch 4 times, most recently from f81d19e to 7d1895f Compare August 14, 2023 15:04
@rafiss rafiss force-pushed the retry-read-committed branch from 4e7f87e to 6be2709 Compare August 17, 2023 03:15
@rafiss
Copy link
Copy Markdown
Collaborator Author

rafiss commented Aug 17, 2023

tftrs!

bors r+

@craig
Copy link
Copy Markdown
Contributor

craig bot commented Aug 17, 2023

Build failed:

This test ensures that we do not do per-statement retries for READ
COMMITTED transactions if results were already sent to the client.

Release note: None
@rafiss rafiss force-pushed the retry-read-committed branch from 6be2709 to 0e57591 Compare August 17, 2023 04:00
@rafiss
Copy link
Copy Markdown
Collaborator Author

rafiss commented Aug 17, 2023

bors r+

@craig
Copy link
Copy Markdown
Contributor

craig bot commented Aug 17, 2023

Build failed:

@rafiss
Copy link
Copy Markdown
Collaborator Author

rafiss commented Aug 17, 2023

flake was:

   > [internal] load metadata for registry.access.redhat.com/ubi8/ubi-minimal:latest:
  ------
  Dockerfile:1
  --------------------
     1 | >>> FROM registry.access.redhat.com/ubi8/ubi-minimal
     2 |     ARG fips_enabled
     3 |
  --------------------
  ERROR: failed to solve: registry.access.redhat.com/ubi8/ubi-minimal: failed to do request: Head "https://registry.access.redhat.com/v2/ubi8/ubi-minimal/manifests/latest": read tcp 10.142.0.162:46278->23.47.144.132:443: read: connection reset by peer
  + remove_files_on_exit

bors r+

@craig
Copy link
Copy Markdown
Contributor

craig bot commented Aug 17, 2023

Build failed:

@rafiss
Copy link
Copy Markdown
Collaborator Author

rafiss commented Aug 17, 2023

bors r+

@craig
Copy link
Copy Markdown
Contributor

craig bot commented Aug 17, 2023

Build succeeded:

@craig craig bot merged commit 0333b78 into cockroachdb:master Aug 17, 2023
@rafiss rafiss deleted the retry-read-committed branch August 18, 2023 05:32
craig bot pushed a commit that referenced this pull request Jun 5, 2025
147682: sql: increase observability of automatic statement retries r=yuzefovich a=michae2

In #107044 we added statement-level automatic retries to the conn executor. These retries are used under Read Committed isolation to try to prevent retryable errors from escaping to the application.

This PR adds more observability to statement-level retries, in some cases bringing it up to par with transaction-level retries. See individual commits for details.

Informs: #145377

Release note: None

Co-authored-by: Michael Erickson <michae2@cockroachlabs.com>
michae2 added a commit to michae2/cockroach that referenced this pull request Jun 6, 2025
In cockroachdb#107044 we added a retry loop for individual statements executing
under Read Committed isolation. We chose 10 as the default retry limit,
but testing has shown that this is too low for high-contention
workloads. (By comparison, automatic transaction retries in conn_fsm are
unlimited.) Increase the default limit to 100.

Informs: cockroachdb#145377

Release note (sql change): This change increases the default value for
the `max_retries_for_read_committed` session variable from 10 to 100.
Testing has shown that some high-contention workloads running under Read
Committed isolation benefit from more statement retries.
michae2 added a commit to michae2/cockroach that referenced this pull request Jun 11, 2025
In cockroachdb#107044 we added a retry loop for individual statements executing
under Read Committed isolation. We chose 10 as the default retry limit,
but testing has shown that this is too low for high-contention
workloads. (By comparison, automatic transaction retries in conn_fsm are
unlimited.) Increase the default limit to 100.

Informs: cockroachdb#145377

Release note (sql change): This change increases the default value for
the `max_retries_for_read_committed` session variable from 10 to 100.
Testing has shown that some high-contention workloads running under Read
Committed isolation benefit from more statement retries.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

sql: add per-statement retry loop for read committed transactions

7 participants