-
Notifications
You must be signed in to change notification settings - Fork 2.1k
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
Gen4 Planner: AxB vs BxA #7274
Gen4 Planner: AxB vs BxA #7274
Conversation
Signed-off-by: Andres Taylor <andres@planetscale.com>
Signed-off-by: Andres Taylor <andres@planetscale.com>
Signed-off-by: Andres Taylor <andres@planetscale.com>
Signed-off-by: Andres Taylor <andres@planetscale.com>
Signed-off-by: Andres Taylor <andres@planetscale.com>
Signed-off-by: Andres Taylor <andres@planetscale.com>
Signed-off-by: Andres Taylor <andres@planetscale.com>
Signed-off-by: Andres Taylor <andres@planetscale.com>
Signed-off-by: Andres Taylor <andres@planetscale.com>
When picking the vindex to use, we must also check that the value it is reading from can be used by vtgate for this purpose - the PlanValue must support the expression type. Signed-off-by: Andres Taylor <andres@planetscale.com>
Signed-off-by: Andres Taylor <andres@planetscale.com>
go/vt/sqlparser/ast_funcs.go
Outdated
if node == nil { | ||
return nil | ||
} | ||
panic(1) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: can be changed to "subquery clone not supported"
go/vt/sqlparser/ast_funcs.go
Outdated
if node == nil { | ||
return nil | ||
} | ||
panic(1) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same as above
go/vt/sqlparser/ast_funcs.go
Outdated
if node == nil { | ||
return nil | ||
} | ||
panic(1) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same as above
go/vt/sqlparser/ast_funcs.go
Outdated
if node == nil { | ||
return nil | ||
} | ||
panic(1) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same as above
go/vt/sqlparser/ast_funcs.go
Outdated
if node == nil { | ||
return nil | ||
} | ||
panic(1) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same as above
go/vt/sqlparser/ast_funcs.go
Outdated
return nil | ||
} | ||
|
||
panic(1) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same as above
"Sharded": true | ||
}, | ||
"FieldQuery": "select id from music where 1 != 1", | ||
"Query": "select id from music", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Either we add the query to the plan with where clause that shows it will select 0 results or we do not add query to the plan to avoid confusion.
"QueryType": "SELECT", | ||
"Original": "select unsharded.id from user join unsharded where unsharded.id = user.id", | ||
"Instructions": { | ||
"OperatorType": "Join", | ||
"Variant": "Join", | ||
"JoinColumnIndexes": "-2", | ||
"TableName": "unsharded_user", | ||
"Inputs": [ | ||
{ | ||
"OperatorType": "Route", | ||
"Variant": "SelectUnsharded", | ||
"Keyspace": { | ||
"Name": "main", | ||
"Sharded": false | ||
}, | ||
"FieldQuery": "select unsharded.id, unsharded.id from unsharded where 1 != 1", | ||
"Query": "select unsharded.id, unsharded.id from unsharded", | ||
"Table": "unsharded" | ||
}, | ||
{ | ||
"OperatorType": "Route", | ||
"Variant": "SelectEqualUnique", | ||
"Keyspace": { | ||
"Name": "user", | ||
"Sharded": true | ||
}, | ||
"FieldQuery": "select 1 from user where 1 != 1", | ||
"Query": "select 1 from user where user.id = :unsharded_id", | ||
"Table": "user", | ||
"Values": [ | ||
":unsharded_id" | ||
], | ||
"Vindex": "user_index" | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
for this query the better plan would be select scatter followed by query on selectunsharded.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe. would you mind if we look deeper into this later, when a larger percentage of the gen4 planner is done, and we can get a better overview of the options available?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
code looks good to be. I think there is need to document the logic for the important methods involved in the planning.
@@ -104,3 +104,23 @@ func TestMergeJoins(t *testing.T) { | |||
}) | |||
} | |||
} | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I checked this file and I see the default planner is greedy. Is it intentional to miss the V4Left2Right planner test?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should probably add to it. it was not a conscious decision, no
engine.SelectEqualUnique, | ||
engine.SelectNext, | ||
engine.SelectNone, | ||
engine.SelectReference, | ||
engine.SelectUnsharded: | ||
return 1 | ||
case engine.SelectEqual: | ||
return 5 | ||
case engine.SelectIN: | ||
return 10 | ||
case engine.SelectMultiEqual: | ||
return 10 | ||
case engine.SelectScatter: | ||
return 20 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
add a todo to revisit these cost.
if newVindexFound { | ||
rp.pickBestAvailableVindex() | ||
} | ||
|
||
// any predicates that cover more than a single table need to be added here | ||
rp.predicates = append(rp.predicates, predicates...) | ||
|
||
return nil | ||
} | ||
|
||
// pickBestAvailableVindex goes over the available vindexes for this route and picks the best one available. | ||
func (rp *routePlan) pickBestAvailableVindex() { | ||
//TODO (Manan,Andres): Improve cost metric for vindexes | ||
for _, v := range vindexPreds { | ||
for _, v := range rp.vindexPreds { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we do not need to loop over on rp.vindexPreds.
On a given routePlan there will only we one vindex used so we can just check the cost of the best selected vindex with the current matched vindex.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the idea is that when we add a table to a routePlan, we also add all possible vindexes. later, when predicates are added, new vindexes can become available (in other words, covered
as the struct field).
I tried removing vindexes that had already lost and did not need rechecking, but that didn't make any diff in the benchmarks, so I didn't keep that complexity in here
} | ||
|
||
// pickBestAvailableVindex goes over the available vindexes for this route and picks the best one available. | ||
func (rp *routePlan) pickBestAvailableVindex() { | ||
//TODO (Manan,Andres): Improve cost metric for vindexes |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
move this TODO to the place where cost metric is present :)
Signed-off-by: Andres Taylor <andres@planetscale.com>
Signed-off-by: Andres Taylor <andres@planetscale.com>
Signed-off-by: Andres Taylor <andres@planetscale.com>
Before optimisation: BenchmarkPlanner/large_cases.txt-v4-16 70786 167749 ns/op 74315 B/op 1618 allocs/op BenchmarkPlanner/large_cases.txt-v4-16 70998 168734 ns/op 74303 B/op 1618 allocs/op After: BenchmarkPlanner/large_cases.txt-v4-16 76375 156411 ns/op 67752 B/op 1376 allocs/op BenchmarkPlanner/large_cases.txt-v4-16 76207 157553 ns/op 67816 B/op 1376 allocs/op Signed-off-by: Andres Taylor <andres@planetscale.com>
Signed-off-by: Andres Taylor <andres@planetscale.com>
This PR teaches the Gen4 planner how to re-evaluate routes after a predicate has been pushed during the join tree comparisons.
Better plans
I'll use an example to show what the planner does:
There is a primary vindex on
user.id
, but foruser_extra
, there are no applicable vindexes.This means first of all that we can't merge the two tables into a single route - for certain
user.id
values, the matchinguser_extra.col
might well be placed in a different shard.So, we are forced to do the join on the vtgate - we can't delegate this to mysql. Now the question is: should we first query the
user
table and push down theuser.id
to theuser_extra
route, or the other way around?Given the available vindexes, we have to evaluate
user_extra
using a scatter query no matter if we haveuser.id
available or not. For theuser
table on the other hand, if we haveuser_extra.col
available, we could use aSelectEqualUnique
which is way better than a scatter query.Implementation comments
Because the planner now takes the same predicate (
user.id = user_extra.col
) and rewrites it in two different ways depending on which route is on the LHS, I needed to make it possible to clone expressions. I have hand-written the clone method for expressions, but I think it makes sense to use the visitorgen to generate this code, and do it for all AST structs and not justExpr
.I also had to move the pushing of predicates to the joinTree phase instead of waiting until we have logical plans.
Planner performance
I was afraid that all this new logic that is being done during joinTree selection would lead to much slower planning, but the benchmarks don't look too scary. 😅