Skip to content

Commit

Permalink
Merge pull request #7370 from planetscale/gen4-fail
Browse files Browse the repository at this point in the history
Gen4 fallback planning
  • Loading branch information
systay authored Jan 28, 2021
2 parents a05c153 + 63917b7 commit 002b800
Show file tree
Hide file tree
Showing 10 changed files with 419 additions and 241 deletions.
438 changes: 221 additions & 217 deletions go/vt/proto/query/query.pb.go

Large diffs are not rendered by default.

40 changes: 33 additions & 7 deletions go/vt/vtgate/planbuilder/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,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 @@ -94,6 +96,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)
if err != nil {
return nil, err
Expand All @@ -107,17 +110,40 @@ func BuildFromStmt(query string, stmt sqlparser.Statement, vschema ContextVSchem
return plan, nil
}

func getConfiguredPlanner(vschema ContextVSchema) (selectPlanner, error) {
switch vschema.Planner() {
case V3:
return buildSelectPlan, nil
case Gen4, Gen4Left2Right, Gen4GreedyOnly:
return gen4Planner, nil
case Gen4WithFallback:
fp := &fallbackPlanner{
primary: gen4Planner,
fallback: buildSelectPlan,
}
return fp.plan, nil
default:
return nil, vterrors.Errorf(vtrpcpb.Code_INTERNAL, "unknown planner selected %d", vschema.Planner())
}
}

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)
}

type selectPlanner func(query string) func(sqlparser.Statement, ContextVSchema) (engine.Primitive, error)

func createInstructionFor(query string, stmt sqlparser.Statement, vschema ContextVSchema) (engine.Primitive, error) {
switch stmt := stmt.(type) {
case *sqlparser.Select:
return buildRoutePlan(stmt, vschema, buildSelectPlan(query))
configuredPlanner, err := getConfiguredPlanner(vschema)
if err != nil {
return nil, err
}
return buildRoutePlan(stmt, vschema, configuredPlanner(query))
case *sqlparser.Insert:
return buildRoutePlan(stmt, vschema, buildInsertPlan)
case *sqlparser.Update:
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)
}
6 changes: 3 additions & 3 deletions go/vt/vtgate/planbuilder/plan_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -422,7 +422,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 @@ -610,10 +610,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 @@ -33,6 +33,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 @@ -47,7 +59,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
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

0 comments on commit 002b800

Please sign in to comment.