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

opt: prevent cycle in memo created by SplitDisjunction #58434

Merged

Conversation

mgartner
Copy link
Collaborator

@mgartner mgartner commented Jan 4, 2021

opt: prevent cycle in memo created by SplitDisjunction

Previously, the SplitDisjunction rule would reuse table and column IDs
from the rule's input expressions in the left side of the generated
UnionAll expression. This was incorrectly believed to be safe. The
addition of partial indexes has provided an example that highlights the
flaw: this can create cycles in the memo.

Consider the table and query below:

CREATE TABLE t (
  k INT PRIMARY KEY,
  a INT,
  b INT,
  c INT,
  INDEX tab_c_idx (c) WHERE a > 1 OR b > 1
)

SELECT * FROM t WHERE a > 1 OR b > 1

This leads to a cycle in the memo because tab_c_idx can be scanned to
retrieve rows where a > 1 and where a > 1 OR b > 1. Notice the cycle
in the memo below: G2 -> G6 -> G10 -> G2. G2 includes both an
unconstrained partial index scan a DistinctOn created by
SplitDisjunction. The child of the DistinctOn is a UnionAll (G7)
with a left child of G10. G10 has two expressions, one of which is a
Select with an input of G2.

memo
 ├── G1: (scalar-group-by G2 G3)
 ├── G2: (select G4 G5) (scan t@tab_c_idx) (distinct-on G6 G7)
 ├── G3: (aggregations G8)
 ├── G4: (scan t)
 ├── G5: (filters G9)
 ├── G6: (union-all G10 G11)
 ├── G7: (aggregations G12)
 ├── G8: (count-rows)
 ├── G9: (or G13 G14)
 ├── G10: (select G4 G15) (select G2 G15)
 ├── G11: (select G16 G17)
 ├── G12: (const-agg G18)
 ├── G13: (eq G19 G20)
 ├── G14: (eq G18 G20)
 ├── G15: (filters G13)
 ├── G16: (scan t)
 ├── G17: (filters G21)
 ├── G18: (variable b)
 ├── G19: (variable a)
 ├── G20: (const 1)
 ├── G21: (eq G22 G20)
 └── G22: (variable b)

In order to prevent this cycle, SplitDisjunction now creates new table
and column IDs for both its left and right inputs. I've been unable to
find an example where table and column ID reuse in
SplitDisjuctionAddKey causes problems, but I've updated that rule to
err on the side of caution.

Fixes #58390

Release justification: This is a critical fix for a bug that causes
errors querying tables with disjunctive filters that are the same or
similar to the predicate of one of the table's partial indexes.

Release note (bug fix): A bug has been fixed which caused errors when
querying a table with a disjunctive filter (an OR expression) that is
the same or similar to the predicate of one of the table's partial
indexes.

@mgartner mgartner requested a review from a team as a code owner January 4, 2021 21:53
@cockroach-teamcity
Copy link
Member

This change is Reviewable

Copy link
Collaborator

@rytaft rytaft left a comment

Choose a reason for hiding this comment

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

:lgtm_strong: nice find!

Reviewed 4 of 4 files at r1.
Reviewable status: :shipit: complete! 1 of 0 LGTMs obtained (waiting on @andy-kimball, @mgartner, and @RaduBerinde)


pkg/sql/opt/xform/select_funcs.go, line 1731 at r1 (raw file):

// ScanPrivateTable returns the table ID of the scan private.
func (c *CustomFuncs) ScanPrivateTable(sp *memo.ScanPrivate) opt.TableID {

[nit] I'd call this something like TableIDFromScanPrivate or GetTableIDFromScanPrivate.


pkg/sql/opt/xform/testdata/rules/select, line 4861 at r1 (raw file):

      ├── union-all
      │    ├── columns: k:1!null u:2 v:3
      │    ├── left columns: k:1!null u:2 v:3

Interesting that we're losing this info about not-null columns. Not necessary for this PR, but maybe worth trying to add it back at some point?


pkg/sql/opt/xform/testdata/rules/select, line 5804 at r1 (raw file):

      ├── columns: k:1!null u:2!null v:3
      ├── grouping columns: k:1!null
      ├── internal-ordering: +1 opt(2)

why did we lose the ordering info here? (Maybe open an issue for this too)

Copy link
Collaborator Author

@mgartner mgartner 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! 1 of 0 LGTMs obtained (waiting on @andy-kimball, @RaduBerinde, and @rytaft)


pkg/sql/opt/xform/select_funcs.go, line 1731 at r1 (raw file):

Previously, rytaft (Rebecca Taft) wrote…

[nit] I'd call this something like TableIDFromScanPrivate or GetTableIDFromScanPrivate.

Done.


pkg/sql/opt/xform/testdata/rules/select, line 4861 at r1 (raw file):

Previously, rytaft (Rebecca Taft) wrote…

Interesting that we're losing this info about not-null columns. Not necessary for this PR, but maybe worth trying to add it back at some point?

Only set operators' output columns are added to the rel prop NotNullCols. You can see the that right column k:6 (now k:11) was not marked as null. I'm not sure what the implications are if NotNullCols contains columns not in the output of the set operation.


pkg/sql/opt/xform/testdata/rules/select, line 5804 at r1 (raw file):

Previously, rytaft (Rebecca Taft) wrote…

why did we lose the ordering info here? (Maybe open an issue for this too)

I'm guessing that ordering is no longer propagating due to the scans being duplicated with new column IDs. We'd need some way to translate the ordering across the projection. I've created an issue: #58435

Previously, the `SplitDisjunction` rule would reuse table and column IDs
from the rule's input expressions in the left side of the generated
`UnionAll` expression. This was incorrectly believed to be safe. The
addition of partial indexes has provided an example that highlights the
flaw: this can create cycles in the memo.

Consider the table and query below:

    CREATE TABLE t (
      k INT PRIMARY KEY,
      a INT,
      b INT,
      c INT,
      INDEX tab_c_idx (c) WHERE a > 1 OR b > 1
    )

    SELECT * FROM t WHERE a > 1 OR b > 1

This leads to a cycle in the memo because `tab_c_idx` can be scanned to
retrieve rows where `a > 1` and where `a > 1 OR b > 1`. Notice the cycle
in the memo below: `G2 -> G6 -> G10 -> G2`. `G2` includes both an
unconstrained partial index scan a `DistinctOn` created by
`SplitDisjunction`. The child of the `DistinctOn` is a `UnionAll` (`G7`)
with a left child of `G10`. `G10` has two expressions, one of which is a
`Select` with an input of `G2`.

    memo
     ├── G1: (scalar-group-by G2 G3)
     ├── G2: (select G4 G5) (scan t@tab_c_idx) (distinct-on G6 G7)
     ├── G3: (aggregations G8)
     ├── G4: (scan t)
     ├── G5: (filters G9)
     ├── G6: (union-all G10 G11)
     ├── G7: (aggregations G12)
     ├── G8: (count-rows)
     ├── G9: (or G13 G14)
     ├── G10: (select G4 G15) (select G2 G15)
     ├── G11: (select G16 G17)
     ├── G12: (const-agg G18)
     ├── G13: (eq G19 G20)
     ├── G14: (eq G18 G20)
     ├── G15: (filters G13)
     ├── G16: (scan t)
     ├── G17: (filters G21)
     ├── G18: (variable b)
     ├── G19: (variable a)
     ├── G20: (const 1)
     ├── G21: (eq G22 G20)
     └── G22: (variable b)

In order to prevent this cycle, `SplitDisjunction` now creates new table
and column IDs for both its left and right inputs. I've been unable to
find an example where table and column ID reuse in
`SplitDisjuctionAddKey` causes problems, but I've updated that rule to
err on the side of caution.

Release justification: This is a critical fix for a bug that causes
errors querying tables with disjunctive filters that are the same or
similar to the predicate of one of the table's partial indexes.

Release note (bug fix): A bug has been fixed which caused errors when
querying a table with a disjunctive filter (an `OR` expression) that is
the same or similar to the predicate of one of the table's partial
indexes.
@mgartner mgartner force-pushed the fix-split-disjunction-memo-cycle branch from 165d7a0 to 07f54de Compare January 4, 2021 23:30
Copy link
Collaborator

@rytaft rytaft left a comment

Choose a reason for hiding this comment

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

Reviewed 6 of 6 files at r2.
Reviewable status: :shipit: complete! 1 of 0 LGTMs obtained (waiting on @andy-kimball and @RaduBerinde)


pkg/sql/opt/xform/testdata/rules/select, line 4861 at r1 (raw file):

Previously, mgartner (Marcus Gartner) wrote…

Only set operators' output columns are added to the rel prop NotNullCols. You can see the that right column k:6 (now k:11) was not marked as null. I'm not sure what the implications are if NotNullCols contains columns not in the output of the set operation.

Ah I see -- makes sense. Thanks!


pkg/sql/opt/xform/testdata/rules/select, line 5804 at r1 (raw file):

Previously, mgartner (Marcus Gartner) wrote…

I'm guessing that ordering is no longer propagating due to the scans being duplicated with new column IDs. We'd need some way to translate the ordering across the projection. I've created an issue: #58435

Thanks!

@mgartner
Copy link
Collaborator Author

mgartner commented Jan 5, 2021

TFTR!

bors r=rytaft

@craig
Copy link
Contributor

craig bot commented Jan 5, 2021

Build failed:

mgartner added a commit to mgartner/cockroach that referenced this pull request Jan 5, 2021
A test backported from cockroachdb#58434 that failed before the associated fix on
master (post-20.2) did not fail before the backported fix in 20.2. This
is because the test relied on new, post-20.2 behavior of
`SplitDisjunction` added in cockroachdb#55915. This commit updates the test so that
it actually tests the fix; notice the `UnionAll` expression, added by
`SplitDisjunction`, that is now included in the memo.

Release note: None
@mgartner
Copy link
Collaborator Author

mgartner commented Jan 5, 2021

bors r=rytaft

@craig
Copy link
Contributor

craig bot commented Jan 5, 2021

Build succeeded:

@craig craig bot merged commit 6de6fca into cockroachdb:master Jan 5, 2021
@mgartner mgartner deleted the fix-split-disjunction-memo-cycle branch January 5, 2021 17:50
mgartner added a commit to mgartner/cockroach that referenced this pull request Jan 6, 2021
Unions generated in SplitScanIntoUnionScans no longer reuse column IDs
from their left children as output column IDs. Reusing column IDs in
this way has shown to be dangerous (see cockroachdb#58434).

Release note: None
mgartner added a commit to mgartner/cockroach that referenced this pull request Jan 6, 2021
A check has been added to `CheckExpr` that asserts that the output
columns of `Union`s and `UnionAll`s are not reused from the left or
right inputs of the union. Reusing columns in this way is dangerous
(see cockroachdb#58434).

Release note: None
mgartner added a commit to mgartner/cockroach that referenced this pull request Jan 6, 2021
Unions generated in SplitScanIntoUnionScans no longer reuse column IDs
from their left children as output column IDs. Reusing column IDs in
this way has shown to be dangerous (see cockroachdb#58434).

Release note: None
mgartner added a commit to mgartner/cockroach that referenced this pull request Jan 6, 2021
A check has been added to `CheckExpr` that asserts that the output
columns of `Union`s and `UnionAll`s are not reused from the left or
right inputs of the union. Reusing columns in this way is dangerous
(see cockroachdb#58434).

Release note: None
mgartner added a commit to mgartner/cockroach that referenced this pull request Jan 7, 2021
Unions generated in SplitScanIntoUnionScans no longer reuse column IDs
from their left children as output column IDs. Reusing column IDs in
this way has shown to be dangerous (see cockroachdb#58434).

Release note: None
mgartner added a commit to mgartner/cockroach that referenced this pull request Jan 7, 2021
A check has been added to `CheckExpr` that asserts that the output
columns of `Union`s and `UnionAll`s are not reused from the left or
right inputs of the union. Reusing columns in this way is dangerous
(see cockroachdb#58434).

Release note: None
craig bot pushed a commit that referenced this pull request Jan 7, 2021
58358: opt: prune partial index columns and simplify partial index projections r=rytaft a=mgartner

Fixes #51623

#### opt: do not derive prune columns for Upsert, Update, Delete

We no longer derive output prune columns for Upsert, Update, and Delete
ops in `DerivePruneCols`. There are no PruneCols rules for these
operators, so deriving their prune columns was only performing
unnecessary work. There are other rules that prune the fetch and return
columns for these operators. These rules do not rely on
`DerivePruneCols`.

Release note: None

#### sql: remove logic to determine fetch cols in row updater

Previously, the `row.MakeUpdater` function had logic to determine the
fetch columns required for an update operation. This is not necessary
because the cost based optimizer already determines the necessary fetch
columns and plumbs them to `MakeUpdater` as the `requestedCols`
argument.

Release note: None

#### opt: safer access to partial index predicates in TableMeta

Previously, partial index predicate expressions in TableMeta were the
source-of-truth used within the optimizer to determine if an index is a
partial index. However, partial index predicates are not added to
TableMeta for all types of statements in optbuilder. Therefore, it was
not safe to assume this was a source-of-truth.

This commit unexports the map of partial index predicates in TableMeta.
Access to partial index predicates must now be done via
`TableMeta.PartialIndexPredicate`. This function checks the catalog to
determine if an index is a partial index, and panics if there is not a
corresponding predicate expression in the partial index predicate map.
This makes the function an actual a source-of-truth.

Release note: None

#### opt: move addPartialIndexPredicatesForTable to optbuilder/partial_index.go

Release note: None

#### opt: prune update/upsert fetch columns not needed for partial indexes

Indexed columns of partial indexes are now only fetched for UPDATE and
UPSERT operations when needed. They are pruned in cases where it is
guaranteed that they are not needed to build old or new index entries.
For example, consider the table and UPDATE:

    CREATE TABLE t (
      a INT PRIMARY KEY,
      b INT,
      c INT,
      d INT,
      INDEX (b) WHERE c > 0,
      FAMILY (a), FAMILY (b), FAMILY (c), FAMILY (d)
    )

    UPDATE t SET d = d + 1 WHERE a = 1

The partial index is guaranteed not to change with this UPDATE because
neither its indexed columns not the columns referenced in its predicate
are mutating. Therefore, the existing values of b do not need to be
fetched to maintain the state of the partial index. Furthermore, the
primary index does require the existing values of b because no columns
in b's family are mutating. So, b can be pruned from the UPDATE's fetch
columns.

Release note (performance improvement): Previously, indexed columns of
partial indexes were always fetched for UPDATEs and UPSERTs. Now they
are only fetched if they are required for maintaining the state of the
index. If an UPDATE or UPSERT mutates columns that are neither indexed by a
partial index nor referenced in a partial index predicate, they will no
longer be fetched (assuming that they are not needed to maintain the
state of other indexes, including the primary index).

#### opt: normalize partial index PUT/DEL projections to false

The `SimplifyPartialIndexProjections` normalization rule has been added
that normalizes synthesized partial index PUT and DEL columns to False
when it is guaranteed that a mutation will not require changed to the
associated partial index. This normalization can lead to further
normalizations, such as pruning columns that the synthesized projections
relied on.

The motivation for this change is to allow fully disjoint updates to
different columns in the same row, when the columns are split across
different families. By pruning columns not needed to maintain a partial
index, we're not forced to scan all column families. This can ultimately
reduce contention during updates.

Release note (performance improvement): UPDATE and UPSERT operations on
tables with partial indexes no longer evaluate partial index predicate
expressions when it is guaranteed that the operation will not alter the
state of the partial index. In some cases, this can eliminate fetching
the existing value of columns that are referenced in partial index
predicates.


58373: streamingccl: add ingestion job framework r=pbardea a=adityamaru

This change introduces a new StreamIngestionJob. It does not do much
more than laying out the general outline of the job, which is very
similar to other bulk jobs such as changefeed, backup etc.

More precisely:
- Introduces StreamIngestionDetails job details proto
- Hooks up the dependency to a mock stream client
- Introduces a StreamIngestionProcessorSpec
- Sets up a simple DistSQL flow which round-robin assigns the partitions
  to the processors.

Most notable TODOs in job land which will be addressed in follow up PRs:
- StreamIngestionPlanHook to create this job. It Will involve figuring out
  SQL syntax.
- Introducing a ts watermark in both the job and processors. This watermark will represent the lowest resolved ts which all processors
have ingested till. Iron out semantics on job start and resumption.
- Introducing a StreamIngestionFrontier processor which will slurp the
  results from the StreamIngestionProcessors, and use them to keep track
of the minimum resolved ts across all processors.

Fixes: #57399

Release note: None

58477: opt: prevent columns reuse in Union and UnionAll r=rytaft a=mgartner

#### opt: fix columns in SplitScanIntoUnionScans constraint

This commit fixes a minor bug in `SplitScanIntoUnionScans` that resulted
in a scan's constraint containing columns not associated with the scan.
This did not affect the correctness of results. However it appears that
it did cause inaccurate stats calculations; I had to add histogram
buckets to the tests to coerce the optimizer into choosing the same
plan for the corresponding test.

Release note: None

#### opt: do not reuse columns for Unions in SplitScanIntoUnionScans

Unions generated in SplitScanIntoUnionScans no longer reuse column IDs
from their left children as output column IDs. Reusing column IDs in
this way has shown to be dangerous (see #58434).

Release note: None

#### opt: add Union column ID check to CheckExpr

A check has been added to `CheckExpr` that asserts that the output
columns of `Union`s and `UnionAll`s are not reused from the left or
right inputs of the union. Reusing columns in this way is dangerous
(see #58434).

Release note: None


Co-authored-by: Marcus Gartner <[email protected]>
Co-authored-by: Aditya Maru <[email protected]>
@rafiss rafiss added this to the 20.2 milestone Apr 22, 2021
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.

roachtest: sqlsmith/setup=rand-tables/setting=default failed
4 participants