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

Gen4 fallback planning #7370

Merged
merged 5 commits into from
Jan 28, 2021
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
438 changes: 221 additions & 217 deletions go/vt/proto/query/query.pb.go

Large diffs are not rendered by default.

46 changes: 36 additions & 10 deletions go/vt/vtgate/planbuilder/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,14 @@ type PlannerVersion = querypb.ExecuteOptions_PlannerVersion
const (
// V3 is also the default planner
V3 = querypb.ExecuteOptions_V3
// V4 uses the default V4 planner, which is the greedy planner
V4 = querypb.ExecuteOptions_V4
// V4GreedyOnly uses only the faster greedy planner
V4GreedyOnly = querypb.ExecuteOptions_V4Greedy
// V4Left2Right tries to emulate the V3 planner by only joining plans in the order they are listed in the FROM-clause
V4Left2Right = querypb.ExecuteOptions_V4Left2Right
// Gen4 uses the default Gen4 planner, which is the greedy planner
Gen4 = querypb.ExecuteOptions_Gen4
// Gen4GreedyOnly uses only the faster greedy planner
Gen4GreedyOnly = querypb.ExecuteOptions_Gen4Greedy
// Gen4Left2Right tries to emulate the V3 planner by only joining plans in the order they are listed in the FROM-clause
Gen4Left2Right = querypb.ExecuteOptions_Gen4Left2Right
// Gen4WithFallback first attempts to use the Gen4 planner, and if that fails, uses the V3 planner instead
Gen4WithFallback = querypb.ExecuteOptions_Gen4WithFallback
)

type truncater interface {
Expand All @@ -92,7 +94,7 @@ var ErrPlanNotSupported = errors.New("plan building not supported")

// BuildFromStmt builds a plan based on the AST provided.
func BuildFromStmt(query string, stmt sqlparser.Statement, vschema ContextVSchema, bindVarNeeds *sqlparser.BindVarNeeds) (*engine.Plan, error) {
instruction, err := createInstructionFor(query, stmt, vschema)
instruction, err := createInstructionFor(query, stmt, vschema, getConfiguredPlanner(vschema))
if err != nil {
return nil, err
}
Expand All @@ -105,17 +107,41 @@ func BuildFromStmt(query string, stmt sqlparser.Statement, vschema ContextVSchem
return plan, nil
}

func getConfiguredPlanner(vschema ContextVSchema) selectPlanner {
switch vschema.Planner() {
case V3:
return buildSelectPlan
case Gen4, Gen4Left2Right, Gen4GreedyOnly:
return gen4Planner
case Gen4WithFallback:
fp := &fallbackPlanner{
primary: buildSelectPlan,
fallback: gen4Planner,
}
return fp.plan
Copy link
Member

Choose a reason for hiding this comment

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

primary should be gen4, right?

default:
// fail - unknown planner
return func(query string) func(sqlparser.Statement, ContextVSchema) (engine.Primitive, error) {
return func(sqlparser.Statement, ContextVSchema) (engine.Primitive, error) {
return nil, vterrors.Errorf(vtrpcpb.Code_INTERNAL, "unknown planner selected %d", vschema.Planner())
}
}
Copy link
Member

Choose a reason for hiding this comment

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

nit: error can be added as return type to avoid creating this method.

}
}

func buildRoutePlan(stmt sqlparser.Statement, vschema ContextVSchema, f func(statement sqlparser.Statement, schema ContextVSchema) (engine.Primitive, error)) (engine.Primitive, error) {
if vschema.Destination() != nil {
return buildPlanForBypass(stmt, vschema)
}
return f(stmt, vschema)
}

func createInstructionFor(query string, stmt sqlparser.Statement, vschema ContextVSchema) (engine.Primitive, error) {
type selectPlanner func(query string) func(sqlparser.Statement, ContextVSchema) (engine.Primitive, error)

func createInstructionFor(query string, stmt sqlparser.Statement, vschema ContextVSchema, sel selectPlanner) (engine.Primitive, error) {
Copy link
Member

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 pass selectPlanner here and getConfiguredPlanner(vschema) can be used directly.

switch stmt := stmt.(type) {
case *sqlparser.Select:
return buildRoutePlan(stmt, vschema, buildSelectPlan(query))
return buildRoutePlan(stmt, vschema, sel(query))
case *sqlparser.Insert:
return buildRoutePlan(stmt, vschema, buildInsertPlan)
case *sqlparser.Update:
Expand All @@ -132,7 +158,7 @@ func createInstructionFor(query string, stmt sqlparser.Statement, vschema Contex
return buildUsePlan(stmt, vschema)
case *sqlparser.Explain:
if stmt.Type == sqlparser.VitessType {
innerInstruction, err := createInstructionFor(query, stmt.Statement, vschema)
innerInstruction, err := createInstructionFor(query, stmt.Statement, vschema, sel)
if err != nil {
return nil, err
}
Expand Down
4 changes: 2 additions & 2 deletions go/vt/vtgate/planbuilder/ddl.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ func buildAlterView(vschema ContextVSchema, ddl *sqlparser.AlterView) (key.Desti
}

var selectPlan engine.Primitive
selectPlan, err = createInstructionFor(sqlparser.String(ddl.Select), ddl.Select, vschema)
selectPlan, err = createInstructionFor(sqlparser.String(ddl.Select), ddl.Select, vschema, buildSelectPlan)
if err != nil {
return nil, nil, err
}
Expand Down Expand Up @@ -178,7 +178,7 @@ func buildCreateView(vschema ContextVSchema, ddl *sqlparser.CreateView) (key.Des
ddl.ViewName.Qualifier = sqlparser.NewTableIdent("")

var selectPlan engine.Primitive
selectPlan, err = createInstructionFor(sqlparser.String(ddl.Select), ddl.Select, vschema)
selectPlan, err = createInstructionFor(sqlparser.String(ddl.Select), ddl.Select, vschema, buildSelectPlan)
if err != nil {
return nil, nil, err
}
Expand Down
57 changes: 57 additions & 0 deletions go/vt/vtgate/planbuilder/fallback_planner.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
Copyright 2021 The Vitess Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package planbuilder

import (
"fmt"

"vitess.io/vitess/go/vt/sqlparser"
"vitess.io/vitess/go/vt/vtgate/engine"
)

type fallbackPlanner struct {
primary, fallback selectPlanner
}

var _ selectPlanner = (*fallbackPlanner)(nil).plan

func (fp *fallbackPlanner) safePrimary(query string) func(sqlparser.Statement, ContextVSchema) (engine.Primitive, error) {
primaryF := fp.primary(query)
return func(stmt sqlparser.Statement, vschema ContextVSchema) (res engine.Primitive, err error) {
defer func() {
// if the primary planner panics, we want to catch it here so we can fall back
if r := recover(); r != nil {
err = fmt.Errorf("%v", r) // not using vterror since this will only be used for logging
}
}()
res, err = primaryF(stmt, vschema)
return
}
}

func (fp *fallbackPlanner) plan(query string) func(sqlparser.Statement, ContextVSchema) (engine.Primitive, error) {
primaryF := fp.safePrimary(query)
backupF := fp.fallback(query)

return func(stmt sqlparser.Statement, vschema ContextVSchema) (engine.Primitive, error) {
res, err := primaryF(stmt, vschema)
if err != nil {
return backupF(stmt, vschema)
}
return res, nil
}
}
80 changes: 80 additions & 0 deletions go/vt/vtgate/planbuilder/fallback_planner_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
Copyright 2021 The Vitess Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package planbuilder

import (
"fmt"
"testing"

"github.com/stretchr/testify/assert"

"vitess.io/vitess/go/vt/sqlparser"

"vitess.io/vitess/go/vt/vtgate/engine"
)

type testPlanner struct {
panic interface{}
err error
res engine.Primitive
called bool
}

var _ selectPlanner = (*testPlanner)(nil).plan

func (tp *testPlanner) plan(_ string) func(sqlparser.Statement, ContextVSchema) (engine.Primitive, error) {
return func(statement sqlparser.Statement, schema ContextVSchema) (engine.Primitive, error) {
tp.called = true
if tp.panic != nil {
panic(tp.panic)
}
return tp.res, tp.err
}
}

func TestFallbackPlanner(t *testing.T) {
a := &testPlanner{}
b := &testPlanner{}
fb := &fallbackPlanner{
primary: a.plan,
fallback: b.plan,
}

stmt := &sqlparser.Select{}
var vschema ContextVSchema

// first planner succeeds
_, _ = fb.plan("query")(stmt, vschema)
assert.True(t, a.called)
assert.False(t, b.called)
a.called = false

// first planner errors
a.err = fmt.Errorf("fail")
_, _ = fb.plan("query")(stmt, vschema)
assert.True(t, a.called)
assert.True(t, b.called)

a.called = false
b.called = false

// first planner panics
a.panic = "oh noes"
_, _ = fb.plan("query")(stmt, vschema)
assert.True(t, a.called)
assert.True(t, b.called)
}
8 changes: 4 additions & 4 deletions go/vt/vtgate/planbuilder/plan_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -421,7 +421,7 @@ func testFile(t *testing.T, filename, tempDir string, vschema *vschemaWrapper, c
empty = true
}

vschema.version = V4
vschema.version = Gen4
out, err := getPlanOutput(tcase, vschema)

// our expectation for the new planner on this query is one of three
Expand Down Expand Up @@ -595,7 +595,7 @@ func locateFile(name string) string {
}

func BenchmarkPlanner(b *testing.B) {
filenames := []string{"from_cases.txt", "filter_cases.txt", "large_cases.txt", "aggr_cases.txt", "memory_sort_cases.txt", "select_cases.txt", "union_cases.txt", "wireup_cases.txt"}
filenames := []string{"large_cases.txt"}
vschema := &vschemaWrapper{
Copy link
Member

Choose a reason for hiding this comment

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

is this removed on purpose?

v: loadSchema(b, "schema_test.json"),
sysVarEnabled: true,
Expand All @@ -609,10 +609,10 @@ func BenchmarkPlanner(b *testing.B) {
benchmarkPlanner(b, V3, testCases, vschema)
})
b.Run(filename+"-v4", func(b *testing.B) {
benchmarkPlanner(b, V4, testCases, vschema)
benchmarkPlanner(b, Gen4, testCases, vschema)
})
b.Run(filename+"-v4left2right", func(b *testing.B) {
benchmarkPlanner(b, V4Left2Right, testCases, vschema)
benchmarkPlanner(b, Gen4Left2Right, testCases, vschema)
})
}
}
Expand Down
14 changes: 13 additions & 1 deletion go/vt/vtgate/planbuilder/route_planning.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,18 @@ import (
"vitess.io/vitess/go/vt/vtgate/engine"
)

var _ selectPlanner = gen4Planner

func gen4Planner(_ string) func(sqlparser.Statement, ContextVSchema) (engine.Primitive, error) {
return func(stmt sqlparser.Statement, vschema ContextVSchema) (engine.Primitive, error) {
sel, ok := stmt.(*sqlparser.Select)
if !ok {
return nil, vterrors.Errorf(vtrpcpb.Code_INTERNAL, "%T not yet supported", stmt)
}
return newBuildSelectPlan(sel, vschema)
}
}

func newBuildSelectPlan(sel *sqlparser.Select, vschema ContextVSchema) (engine.Primitive, error) {
semTable, err := semantics.Analyse(sel) // TODO no nil no
if err != nil {
Expand All @@ -46,7 +58,7 @@ func newBuildSelectPlan(sel *sqlparser.Select, vschema ContextVSchema) (engine.P
var tree joinTree

switch {
case vschema.Planner() == V4Left2Right:
case vschema.Planner() == Gen4Left2Right:
tree, err = leftToRightSolve(qgraph, semTable, vschema)
default:
tree, err = greedySolve(qgraph, semTable, vschema)
Expand Down
4 changes: 0 additions & 4 deletions go/vt/vtgate/planbuilder/select.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,6 @@ func buildSelectPlan(query string) func(sqlparser.Statement, ContextVSchema) (en
return func(stmt sqlparser.Statement, vschema ContextVSchema) (engine.Primitive, error) {
sel := stmt.(*sqlparser.Select)

if vschema.Planner() != V3 {
return newBuildSelectPlan(sel, vschema)
}

p, err := handleDualSelects(sel, vschema)
if err != nil {
return nil, err
Expand Down
2 changes: 2 additions & 0 deletions go/vt/vtgate/planbuilder/testdata/unsupported_cases.txt
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,7 @@ Gen4 plan same as above
# create view with Cannot auto-resolve for cross-shard joins
"create view user.view_a as select col from user join user_extra"
"symbol col not found"
Gen4 plan same as above

# create view with join that cannot be served in each shard separately
"create view user.view_a as select user_extra.id from user join user_extra"
Expand All @@ -447,6 +448,7 @@ Gen4 plan same as above
# create view with top level subquery in select
"create view user.view_a as select a, (select col from user) from unsharded"
"Complex select queries are not supported in create or alter view statements"
Gen4 plan same as above

# create view with sql_calc_found_rows with limit
"create view user.view_a as select sql_calc_found_rows * from music limit 100"
Expand Down
12 changes: 7 additions & 5 deletions go/vt/vtgate/vcursor_impl.go
Original file line number Diff line number Diff line change
Expand Up @@ -358,12 +358,14 @@ func (vc *vcursorImpl) Planner() planbuilder.PlannerVersion {
switch strings.ToLower(*plannerVersion) {
case "v3":
return planbuilder.V3
case "v4":
return planbuilder.V4
case "v4greedy", "greedy":
return planbuilder.V4GreedyOnly
case "gen4":
return planbuilder.Gen4
case "gen4greedy", "greedy":
return planbuilder.Gen4GreedyOnly
case "left2right":
return planbuilder.V4Left2Right
return planbuilder.Gen4Left2Right
case "gen4fallback":
return planbuilder.Gen4WithFallback
}

log.Warn("unknown planner version configured. using the default")
Expand Down
2 changes: 1 addition & 1 deletion go/vt/vtgate/vtgate.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ var (

// Put set-passthrough under a flag.
sysVarSetEnabled = flag.Bool("enable_system_settings", true, "This will enable the system settings to be changed per session at the database connection level")
plannerVersion = flag.String("planner_version", "v3", "Sets the default planner to use when the session has not changed it. Valid values are: V3, V4 and V4Greedy. All V4 versions should be considered experimental!")
plannerVersion = flag.String("planner_version", "v3", "Sets the default planner to use when the session has not changed it. Valid values are: V3, Gen4, Gen4Greedy and Gen4Fallback. Gen4Fallback tries the new gen4 planner and falls back to the V3 planner if the gen4 fails. All Gen4 versions should be considered experimental!")

// lockHeartbeatTime is used to set the next heartbeat time.
lockHeartbeatTime = flag.Duration("lock_heartbeat_time", 5*time.Second, "If there is lock function used. This will keep the lock connection active by using this heartbeat")
Expand Down
7 changes: 4 additions & 3 deletions proto/query.proto
Original file line number Diff line number Diff line change
Expand Up @@ -301,9 +301,10 @@ message ExecuteOptions {
enum PlannerVersion {
DEFAULT_PLANNER = 0;
V3 = 1;
V4 = 2;
V4Greedy = 3;
V4Left2Right = 4;
Gen4 = 2;
Gen4Greedy = 3;
Gen4Left2Right = 4;
Gen4WithFallback = 5;
}

// PlannerVersion specifies which planner to use.
Expand Down