-
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 fallback planning #7370
Gen4 fallback planning #7370
Changes from 3 commits
b29b2c6
365fd3c
0bd2ae0
ad679ae
63917b7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 { | ||
|
@@ -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 | ||
} | ||
|
@@ -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 | ||
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()) | ||
} | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we do not need to pass selectPlanner here and |
||
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: | ||
|
@@ -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 | ||
} | ||
|
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 | ||
} | ||
} |
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) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 | ||
|
@@ -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{ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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, | ||
|
@@ -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) | ||
}) | ||
} | ||
} | ||
|
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.
primary should be gen4, right?