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: perf improvements for large queries #85100

Merged
merged 3 commits into from
Jul 29, 2022
Merged

Conversation

DrewKimball
Copy link
Collaborator

opt: add bench test for slow queries

This commit adds two slow-planning queries pulled from #64793 to be used
in benchmarking the optimizer. In addition, the ReorderJoinsLimit has been
set to the default 8 for benchmarking tests.

opt: add struct for tracking column equivalence sets

Previously, the JoinOrderBuilder would construct a FuncDepSet from
scratch on each call to addJoins in order to eliminate redundant join
filters. This led to unnecessary large allocations because addJoins is
called an exponential number of times in query size.

This commit adds a struct EquivSet that efficiently stores equivalence
relations as ColSets in a slice. Rather than being constructed on each
call to addJoins, a Reset method is called that maintains slice memory.

In the future, EquivSet can be used to handle equivalencies within FuncDepSet
structs as well. This well avoid a significant number of allocations in cases with
many equivalent columns, as outlined in #83963.

opt: avoid usage of FastIntMap in optimizer hot paths

Previously, computeHashJoinCost would use a FastIntMap to represent join
equality filters to pass to computeFiltersCost. In addition,
GenerateMergeJoins used a FastIntMap to look up columns among its join
equality columns. This lead to unnecessary allocations since column IDs are
often large enough to exceed the small field of FastIntMap.

This commit modifies computeFiltersCost to take an anonymous function
that is used to decide whether to skip an equality condition, removing the
need for a mapping between columns.

This commit also refactors GenerateMergeJoins to simply perform a linear
scan of its equality columns; this avoids the allocation issue, and should be
fast in practice because the number of equalities will not generally be large.

Release note: None

@DrewKimball DrewKimball requested a review from a team as a code owner July 26, 2022 23:15
@cockroach-teamcity
Copy link
Member

This change is Reviewable

Copy link
Collaborator

@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.

💯 This looks great! Can you share some of the benchmark results?

Reviewed 2 of 2 files at r1, 4 of 4 files at r2, 2 of 2 files at r3, all commit messages.
Reviewable status: :shipit: complete! 0 of 0 LGTMs obtained (waiting on @DrewKimball)


-- commits line 16 at r2:
By "query size", do you mean number of joins in the query?


pkg/sql/opt/xform/coster.go line 1103 at r3 (raw file):

//
// shouldSkipEq is a function that is used to skip equality filters on some
// joins in the case when they do not cost anything.

nit: explain why they don't cost anything, like "... when they do not cost anything because they exist in filters even though the join operation performs the filtering".


pkg/sql/opt/xform/join_funcs.go line 122 at r3 (raw file):

		addCol := func(col opt.ColumnID, descending bool) {
			eqIdx := getEqIdx(col)

nit: We only really need to equality columns here, not the index, so instead of returning the index you could make the function getEqCols := func(col opt.ColumnID) (left, right opt.ColumnID) { ... }


pkg/sql/opt/bench/bench_test.go line 803 at r1 (raw file):

	// 1. Table with small number of columns.
	// 2. Table with no indexes.
	// 3. Very simple query that returns single row based on key filter.

nit: these comments don't look correct


pkg/sql/opt/bench/bench_test.go line 816 at r1 (raw file):

																		 ORDER BY tab_455284.col1_2 DESC, tab_455284.col1_1 DESC
																		 LIMIT 1:::INT8)), (NULL,
																												6736:::INT8)) AS tab_455285 (col_1103773, col_1103774)),

nit: can you make the indentation less aggressive here?


pkg/sql/opt/bench/bench_test.go line 852 at r1 (raw file):

				(SELECT *
				 FROM (
							 VALUES ((-0.19099748134613037):::FLOAT8), (0.9743397235870361:::FLOAT8), ((-1.6944892406463623):::FLOAT8)) AS tab_297691 (col_692430))

nit: indentation


pkg/sql/opt/props/equiv_set_test.go line 27 at r2 (raw file):

	const numIterations = 8
	const chanceUseExisting = 0.5
	const removeChance = 0.05

nit: removeChance is unused


pkg/sql/opt/props/equiv_set_test.go line 56 at r2 (raw file):

		rightGroup := testOracle[right]
		rightContains := rightGroup.Contains(left)
		_ = rightContains

nit: remove all the lines related to rightGroup - these are unnecessary, correct? You could simplify this all to return testOracle[left].Contains(right).


pkg/sql/opt/props/equiv_set_test.go line 87 at r2 (raw file):

		var colsUsed opt.ColSet
		var fds FuncDepSet
		t.Run(fmt.Sprintf("iteration %d", i), func(t *testing.T) {

nit: it might be good to test with different numbers of columns like:

const maxCols = 256
// ...
for i := 2; i <= maxCols; i = i << 1 {
    for j := 0; i < numIterations; j++ {
        t.Run(fmt.Sprintf("cols=%d;iteration=%d", i, j), func(t *testing.T) {
        // ...

Copy link
Collaborator Author

@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.

Can you share some of the benchmark results?

Oops, forgot about that. Here are the two queries vs master:

name                         old time/op    new time/op    delta
SlowQueries/slow-query-1-10    34.1ms ± 0%    22.1ms ± 0%  -35.25%  (p=0.000 n=10+9)
SlowQueries/slow-query-2-10     342ms ± 0%     303ms ± 1%  -11.46%  (p=0.000 n=9+9)

name                         old alloc/op   new alloc/op   delta
SlowQueries/slow-query-1-10    26.6MB ± 0%    12.4MB ± 0%  -53.24%  (p=0.000 n=10+10)
SlowQueries/slow-query-2-10     228MB ± 0%     180MB ± 0%  -21.15%  (p=0.000 n=10+10)

name                         old allocs/op  new allocs/op  delta
SlowQueries/slow-query-1-10      220k ± 0%      126k ± 0%  -42.53%  (p=0.000 n=10+9)
SlowQueries/slow-query-2-10     1.74M ± 0%     1.60M ± 0%   -8.34%  (p=0.000 n=10+10)

Reviewable status: :shipit: complete! 0 of 0 LGTMs obtained (waiting on @mgartner)


-- commits line 16 at r2:

Previously, mgartner (Marcus Gartner) wrote…

By "query size", do you mean number of joins in the query?

Yes, changed the comment. Done.


pkg/sql/opt/xform/coster.go line 1103 at r3 (raw file):

Previously, mgartner (Marcus Gartner) wrote…

nit: explain why they don't cost anything, like "... when they do not cost anything because they exist in filters even though the join operation performs the filtering".

Done.


pkg/sql/opt/xform/join_funcs.go line 122 at r3 (raw file):

Previously, mgartner (Marcus Gartner) wrote…

nit: We only really need to equality columns here, not the index, so instead of returning the index you could make the function getEqCols := func(col opt.ColumnID) (left, right opt.ColumnID) { ... }

Done.


pkg/sql/opt/bench/bench_test.go line 803 at r1 (raw file):

Previously, mgartner (Marcus Gartner) wrote…

nit: these comments don't look correct

Oops, copy-paste error. Done.


pkg/sql/opt/bench/bench_test.go line 816 at r1 (raw file):

Previously, mgartner (Marcus Gartner) wrote…

nit: can you make the indentation less aggressive here?

Done.


pkg/sql/opt/bench/bench_test.go line 852 at r1 (raw file):

Previously, mgartner (Marcus Gartner) wrote…

nit: indentation

Done.


pkg/sql/opt/props/equiv_set_test.go line 27 at r2 (raw file):

Previously, mgartner (Marcus Gartner) wrote…

nit: removeChance is unused

Done.


pkg/sql/opt/props/equiv_set_test.go line 56 at r2 (raw file):

Previously, mgartner (Marcus Gartner) wrote…

nit: remove all the lines related to rightGroup - these are unnecessary, correct? You could simplify this all to return testOracle[left].Contains(right).

Done.


pkg/sql/opt/props/equiv_set_test.go line 87 at r2 (raw file):

Previously, mgartner (Marcus Gartner) wrote…

nit: it might be good to test with different numbers of columns like:

const maxCols = 256
// ...
for i := 2; i <= maxCols; i = i << 1 {
    for j := 0; i < numIterations; j++ {
        t.Run(fmt.Sprintf("cols=%d;iteration=%d", i, j), func(t *testing.T) {
        // ...

Good idea. Done.

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: nice work!!

Reviewed 1 of 2 files at r1, 7 of 7 files at r4, 4 of 4 files at r5, 2 of 2 files at r6, all commit messages.
Reviewable status: :shipit: complete! 1 of 0 LGTMs obtained (waiting on @DrewKimball and @mgartner)


-- commits line 22 at r6:
nit: add a release note


-- commits line 41 at r6:
nit: add a release note

Copy link
Collaborator

@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.

:lgtm: Those results look great!

Reviewed 7 of 7 files at r4, 4 of 4 files at r5, 2 of 2 files at r6, all commit messages.
Reviewable status: :shipit: complete! 2 of 0 LGTMs obtained (waiting on @DrewKimball)


pkg/sql/opt/props/equiv_set_test.go line 83 at r5 (raw file):

			var colsUsed opt.ColSet
			var fds FuncDepSet
			t.Run(fmt.Sprintf("cols=%d;iteration=%d", numCols, i), func(t *testing.T) {

nit: use / instead of ; - sorry I mislead you there

Copy link
Collaborator Author

@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.

TFTRs!

Fixed the lint failure, but I think the test failures are flakes.

Reviewable status: :shipit: complete! 0 of 0 LGTMs obtained (and 2 stale) (waiting on @mgartner and @rytaft)


-- commits line 22 at r6:

Previously, rytaft (Rebecca Taft) wrote…

nit: add a release note

Done.


-- commits line 41 at r6:

Previously, rytaft (Rebecca Taft) wrote…

nit: add a release note

Done.


pkg/sql/opt/props/equiv_set_test.go line 83 at r5 (raw file):

Previously, mgartner (Marcus Gartner) wrote…

nit: use / instead of ; - sorry I mislead you there

Oh, didn't notice that. Done.

@DrewKimball
Copy link
Collaborator Author

Turned out those tests weren't flakes - ended up having to only set the ReorderJoinsLimit for the slow query benchmark (instead of all testing EvalCtx instances) and to use ExtractJoinEqualityColumns for the equality filter skipping in computeHashJoinCost to recover the deduplication behavior.

Copy link
Collaborator

@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.

:lgtm: once you squash those last two commits into the original three

Reviewed 6 of 6 files at r7, 4 of 4 files at r8, 2 of 2 files at r9, 1 of 1 files at r10, 2 of 2 files at r11, all commit messages.
Reviewable status: :shipit: complete! 1 of 0 LGTMs obtained (and 1 stale) (waiting on @DrewKimball)

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 r7, 4 of 4 files at r8, 2 of 2 files at r9, 1 of 1 files at r10, 2 of 2 files at r11, all commit messages.
Reviewable status: :shipit: complete! 1 of 0 LGTMs obtained (and 1 stale) (waiting on @DrewKimball)

DrewKimball and others added 3 commits July 29, 2022 11:43
This commit adds two slow-planning queries pulled from cockroachdb#64793 to be used
in benchmarking the optimizer. In addition, the `ReorderJoinsLimit` has been
set to the default 8 for benchmarking tests.

Release note: None
Previously, the `JoinOrderBuilder` would construct a `FuncDepSet` from
scratch on each call to `addJoins` in order to eliminate redundant join
filters. This led to unnecessary large allocations because `addJoins` is
called an exponential number of times in the number of joins in the query.

This commit adds a struct `EquivSet` that efficiently stores equivalence
relations as `ColSets` in a slice. Rather than being constructed on each
call to `addJoins`, a `Reset` method is called that maintains slice memory.

Release note (performance improvement): The optimizer is now less likely to
take an excessive amount of time to plan queries with many joins.
Previously, `computeHashJoinCost` would use a `FastIntMap` to represent join
equality filters to pass to `computeFiltersCost`. In addition,
`GenerateMergeJoins` used a `FastIntMap` to look up columns among its join
equality columns. This lead to unnecessary allocations since column IDs are
often large enough to exceed the small field of `FastIntMap`.

This commit modifies `computeFiltersCost` to take an anonymous function
that is used to decide whether to skip an equality condition, removing the
need for a mapping between columns.

This commit also refactors `GenerateMergeJoins` to simply perform a linear
scan of its equality columns; this avoids the allocation issue, and should be
fast in practice because the number of equalities will not generally be large.

Release note (performance improvement): The optimizer is now less likely to
take an excessive amount of time to plan queries with many joins.
Copy link
Collaborator Author

@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.

once you squash those last two commits into the original three

Whoops. Done.

Reviewable status: :shipit: complete! 1 of 0 LGTMs obtained (and 1 stale) (waiting on @DrewKimball)

Copy link
Collaborator Author

@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.

TFTRs!

Reviewable status: :shipit: complete! 0 of 0 LGTMs obtained (and 2 stale) (waiting on @mgartner and @rytaft)

@DrewKimball
Copy link
Collaborator Author

I think the test failures are actually flakes this time - the xform/cycle stack overflow, and the backupccl timeout.

@DrewKimball
Copy link
Collaborator Author

bors r+

@craig craig bot merged commit 7e2df69 into cockroachdb:master Jul 29, 2022
@craig
Copy link
Contributor

craig bot commented Jul 29, 2022

Build succeeded:

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