Skip to content
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

Allow fast path insert into unique indices with implicit partitioning columns under read-committed #126504

Merged
merged 2 commits into from
Jul 3, 2024

Conversation

mw5h
Copy link
Contributor

@mw5h mw5h commented Jul 1, 2024

See commit messages for details.

@mw5h mw5h requested a review from a team as a code owner July 1, 2024 16:32
@mw5h mw5h requested review from DrewKimball and removed request for a team July 1, 2024 16:32
@cockroach-teamcity
Copy link
Member

This change is Reviewable

@michae2 michae2 self-requested a review July 1, 2024 16:45
Copy link
Collaborator

@DrewKimball DrewKimball left a comment

Choose a reason for hiding this comment

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

BTW, @mgartner has a nice way to generate a PR message using commits, described here.

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


-- commits line 14 at r3:
Maybe just a Reviewable artifact, but it looks like a commit was duplicated, or something.


pkg/sql/insert_fast_path.go line 220 at r4 (raw file):

				if c.Locking.Form == tree.LockPredicate {
					if !isInputRow {
						err := row.PutUniqLock(ctx, &r.ti.putter, c.Locking.WaitPolicy, span.Key, r.traceKV)

I believe this will execute the unique checks for the other partitions in the same batch as the insert, rather than with other unique checks / FK checks. Is that intentional? If so, we should call it out in a comment here.

[nit] Along the lines of the above comment, I think it might be helpful to inline the logic from PutUniqueLock here unless there's some dependency issue, so it's clear what request we're sending and in what batch. It's fine if you think it's better this way, though.


pkg/sql/opt/exec/execbuilder/mutation.go line 192 at r4 (raw file):

		// If there is a unique index with a implicit partitioning columns, the fast
		// path can write tombstones to lock the row in all partitions.
		allowPredicateLocks := execFastPathCheck.ReferencedTable.Unique(execFastPathCheck.CheckOrdinal).HasIndexWithImplicitPartitioningColumn()

[nit] Since we have access to the index here, is it necessary to add HasIndexWithImplicitPartitioningColumn? Could we just check if ImplicitPartitioningColumnCount() > 0 instead?


pkg/sql/opt/exec/execbuilder/relational.go line 3141 at r4 (raw file):

}

// TODO: Delete this function once predicate locks are universally supported.

[nit] Is there an issue we can associate the TODO with, like TODO(#foo):?

Copy link
Collaborator

@DrewKimball DrewKimball 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 figuring this out!

Reviewable status: :shipit: complete! 0 of 0 LGTMs obtained (waiting on @michae2 and @mw5h)

@michae2 michae2 requested a review from DrewKimball July 2, 2024 06:46
Copy link
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.

Awesome!! 🔥 This is tricky stuff!

It's very satisfying that the error message for the tombstone CPut works exactly for the whole INSERT statement!

Reviewed 1 of 1 files at r1, 8 of 8 files at r3, 8 of 8 files at r4, all commit messages.
Reviewable status: :shipit: complete! 0 of 0 LGTMs obtained (waiting on @DrewKimball and @mw5h)


-- commits line 47 at r4:
nit: Maybe add "when using the insert fast path" to make it clear when it applies.


pkg/sql/insert_fast_path.go line 220 at r4 (raw file):

Previously, DrewKimball (Drew Kimball) wrote…

I believe this will execute the unique checks for the other partitions in the same batch as the insert, rather than with other unique checks / FK checks. Is that intentional? If so, we should call it out in a comment here.

[nit] Along the lines of the above comment, I think it might be helpful to inline the logic from PutUniqueLock here unless there's some dependency issue, so it's clear what request we're sending and in what batch. It's fine if you think it's better this way, though.

I was wondering about this, too. I'm guessing it's because the logic to handle the response is not the same as in runUniqChecks (because the tombstone CPut error message can simply become the statement error message) so it doesn't make sense to include in the uniqBatch. Agree that a comment would help.


pkg/sql/opt/exec/execbuilder/mutation.go line 190 at r4 (raw file):

		execFastPathCheck.CheckOrdinal = c.CheckOrdinal

		// If there is a unique index with a implicit partitioning columns, the fast

typo: "implicit" instead of "a implicit"


pkg/sql/opt/exec/execbuilder/mutation.go line 192 at r4 (raw file):

Previously, DrewKimball (Drew Kimball) wrote…

[nit] Since we have access to the index here, is it necessary to add HasIndexWithImplicitPartitioningColumn? Could we just check if ImplicitPartitioningColumnCount() > 0 instead?

Is there a reason we can't always use true here? Oh, right, is it because this tombstone technique only works for single-row predicate locks, and unique checks from UNIQUE WITHOUT INDEX might require range predicate locks? 🤔

I wonder if checking for implicit partitioning is enough to always prevent range predicate locks? For example, would it catch something like this? (Which I think will have a unique check for the UNIQUE WITHOUT INDEX (b) that uses the implicitly-partitioned index on (b, c).)

SET experimental_enable_unique_without_index_constraints = true;
SET experimental_enable_implicit_column_partitioning = true;

CREATE TABLE abc (
  a INT NOT NULL CHECK (a IN (4, 5, 6)),
  b INT NOT NULL,
  c INT NOT NULL,
  INDEX (b, c),
  UNIQUE WITHOUT INDEX (b)
) PARTITION ALL BY LIST (a) (
  PARTITION p1 VALUES IN ((4)),
  PARTITION p2 VALUES IN ((5)),
  PARTITION p3 VALUES IN ((6))
);
EXPLAIN INSERT INTO abc VALUES (6, 7, 8);

pkg/sql/opt/exec/execbuilder/relational.go line 3143 at r4 (raw file):

// TODO: Delete this function once predicate locks are universally supported.
func (b *Builder) buildLocking(toLock opt.TableID, locking opt.Locking) (opt.Locking, error) {
	return b.buildLockingImpl(toLock, locking, false)

nit: Usually literal arguments are decorated with a comment like ..., false /* allowPredicateLocks */).


pkg/sql/row/locking.go line 85 at r4 (raw file):

) error {
	if waitPolicy != tree.LockWaitBlock {
		return errors.Errorf("Non-blocking predicate locks are not implemented")

Could this check be moved to buildLockingImpl?


pkg/ccl/logictestccl/testdata/logic_test/regional_by_row_read_committed line 223 at r4 (raw file):


statement async conflict error pgcode 23505 pq: duplicate key value violates unique constraint "university_pkey"
INSERT INTO university VALUES ('CMU', 'Chippewas', '97858');

Haha, I appreciate this!

Copy link
Contributor Author

@mw5h mw5h 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 (waiting on @DrewKimball)


pkg/sql/opt/exec/execbuilder/mutation.go line 192 at r4 (raw file):

Previously, michae2 (Michael Erickson) wrote…

Is there a reason we can't always use true here? Oh, right, is it because this tombstone technique only works for single-row predicate locks, and unique checks from UNIQUE WITHOUT INDEX might require range predicate locks? 🤔

I wonder if checking for implicit partitioning is enough to always prevent range predicate locks? For example, would it catch something like this? (Which I think will have a unique check for the UNIQUE WITHOUT INDEX (b) that uses the implicitly-partitioned index on (b, c).)

SET experimental_enable_unique_without_index_constraints = true;
SET experimental_enable_implicit_column_partitioning = true;

CREATE TABLE abc (
  a INT NOT NULL CHECK (a IN (4, 5, 6)),
  b INT NOT NULL,
  c INT NOT NULL,
  INDEX (b, c),
  UNIQUE WITHOUT INDEX (b)
) PARTITION ALL BY LIST (a) (
  PARTITION p1 VALUES IN ((4)),
  PARTITION p2 VALUES IN ((5)),
  PARTITION p3 VALUES IN ((6))
);
EXPLAIN INSERT INTO abc VALUES (6, 7, 8);
demo@127.0.0.1:26257/demoapp/movr> SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL READ COMMITTED;
SET

Time: 1ms total (execution 1ms / network 0ms)

demo@127.0.0.1:26257/demoapp/movr> EXPLAIN INSERT INTO abc VALUES (6, 7, 8);
ERROR: unimplemented: explicit unique checks are not yet supported under read committed isolation
SQLSTATE: 0A000
HINT: You have attempted to use a feature that is not yet implemented.
See: https://go.crdb.dev/issue-v/110873/v24.2

@mw5h mw5h requested review from a team as code owners July 2, 2024 17:11
@mw5h mw5h requested review from herkolategan, DarrylWong and msbutler and removed request for a team July 2, 2024 17:11
Copy link
Contributor Author

@mw5h mw5h 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 (waiting on @DarrylWong, @DrewKimball, @herkolategan, @michae2, and @msbutler)


-- commits line 14 at r3:

Previously, DrewKimball (Drew Kimball) wrote…

Maybe just a Reviewable artifact, but it looks like a commit was duplicated, or something.

I think it's just Reviewable.


pkg/sql/insert_fast_path.go line 220 at r4 (raw file):

Previously, michae2 (Michael Erickson) wrote…

I was wondering about this, too. I'm guessing it's because the logic to handle the response is not the same as in runUniqChecks (because the tombstone CPut error message can simply become the statement error message) so it doesn't make sense to include in the uniqBatch. Agree that a comment would help.

Done.


pkg/sql/opt/exec/execbuilder/relational.go line 3141 at r4 (raw file):

Previously, DrewKimball (Drew Kimball) wrote…

[nit] Is there an issue we can associate the TODO with, like TODO(#foo):?

There isn't yet an issue for predicate locks, though maybe I should file one.

Copy link
Collaborator

@DrewKimball DrewKimball left a comment

Choose a reason for hiding this comment

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

:LGTM:

Reviewed 1 of 1 files at r8, 4 of 8 files at r9, 10 of 10 files at r11, all commit messages.
Reviewable status: :shipit: complete! 1 of 0 LGTMs obtained (waiting on @DarrylWong, @herkolategan, @msbutler, and @mw5h)


pkg/sql/insert_fast_path.go line 226 at r11 (raw file):

				if c.Locking.Form == tree.LockPredicate {
					if !isInputRow {
						r.ti.putter.CPut(span.Key, nil, nil)

Did you mean to remove the tracing messages from before? Did it turn out that we already write to logs for CPut?

@mw5h
Copy link
Contributor Author

mw5h commented Jul 2, 2024

Did you mean to remove the tracing messages from before? Did it turn out that we already write to logs for CPut?

I did because I thought there was already tracing around the CPut, but I was mistaken.

mw5h added 2 commits July 2, 2024 16:03
Previously, specifying a statement or query as asynchronous would cause
logictest to not parse any associated error or notice conditions
properly, so such statements were required to always return without
error. In order to test predicate locks, we need to be able to
concurrently write conflicting rows. To fix this, I adjusted the regular
expressions to allow the "async" keyword.

Informs: cockroachdb#110873

Release note: None
Previously, insertion into tables with unique keys where the keys in
question had implicit partition columns was disabled under read
commmitted isolation because we couldn't lock the inserted row in all
partitions.

This patch introduces a limited form of predicate lock that operates by
CPuting a tombstone into all other partitions aside from the one being
inserted into. This effectively locks exclusive, instead of shared,
which the best we can do without explicit predicate locks. This locking
is used on the insert fast path only (slow path is still disabled).

This patch also disables the guardrails around predicate locks under
read-committed when we mark a uniqueness constraint as
"uniqueWithoutIndex" due to the presence of implicit partition columns.

Part of: cockroachdb#110873

Release note (bug fix): Fast path inserts into regional by row tables
with uniqueness constraints are now possible under read-commited
isolation.
Copy link
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.

:lgtm: Very nice!

Reviewed 1 of 1 files at r8, 4 of 8 files at r9, 6 of 10 files at r11, 4 of 4 files at r12, 4 of 4 files at r13, all commit messages.
Reviewable status: :shipit: complete! 1 of 0 LGTMs obtained (and 1 stale) (waiting on @DarrylWong, @DrewKimball, @herkolategan, @msbutler, and @mw5h)

@mw5h
Copy link
Contributor Author

mw5h commented Jul 3, 2024

bors r+

@craig craig bot merged commit bb72988 into cockroachdb:master Jul 3, 2024
22 checks passed
@mw5h mw5h deleted the rc_unique_wo_index branch July 12, 2024 18:39
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.

4 participants