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

Range Query Optimization (For sequential Vindex types) #17342

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 7 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
5 changes: 5 additions & 0 deletions go/vt/key/key.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,11 @@ func Empty(id []byte) bool {
// KeyRange helper methods
//

// Make a Key Range
func NewKeyRange(start []byte, end []byte) *topodatapb.KeyRange {
return &topodatapb.KeyRange{Start: start, End: end}
}

// KeyRangeAdd adds two adjacent KeyRange values (in any order) into a single value. If the values are not adjacent,
// it returns false.
func KeyRangeAdd(a, b *topodatapb.KeyRange) (*topodatapb.KeyRange, bool) {
Expand Down
43 changes: 43 additions & 0 deletions go/vt/vtgate/engine/routing.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ const (
// IN is for routing a statement to a multi shard.
// Requires: A Vindex, and a multi Values.
IN
// Between is for routing a statement to a multi shard
// Requires: A Vindex, and start and end Value.
Between
// MultiEqual is used for routing queries with IN with tuple clause
// Requires: A Vindex, and a multi Tuple Values.
MultiEqual
Expand Down Expand Up @@ -78,6 +81,7 @@ var opName = map[Opcode]string{
EqualUnique: "EqualUnique",
Equal: "Equal",
IN: "IN",
Between: "Between",
MultiEqual: "MultiEqual",
Scatter: "Scatter",
DBA: "DBA",
Expand Down Expand Up @@ -157,6 +161,14 @@ func (rp *RoutingParameters) findRoute(ctx context.Context, vcursor VCursor, bin
default:
return rp.in(ctx, vcursor, bindVars)
}
case Between:
switch rp.Vindex.(type) {
case vindexes.SingleColumn:
return rp.between(ctx, vcursor, bindVars)
default:
// Only SingleColumn vindex supported.
return nil, nil, vterrors.VT13001("between supported on SingleColumn vindex only")
}
case MultiEqual:
switch rp.Vindex.(type) {
case vindexes.MultiColumn:
Expand Down Expand Up @@ -396,6 +408,19 @@ func (rp *RoutingParameters) inMultiCol(ctx context.Context, vcursor VCursor, bi
return rss, shardVarsMultiCol(bindVars, mapVals, isSingleVal), nil
}

func (rp *RoutingParameters) between(ctx context.Context, vcursor VCursor, bindVars map[string]*querypb.BindVariable) ([]*srvtopo.ResolvedShard, []map[string]*querypb.BindVariable, error) {
env := evalengine.NewExpressionEnv(ctx, bindVars, vcursor)
value, err := env.Evaluate(rp.Values[0])
if err != nil {
return nil, nil, err
}
rss, values, err := resolveShardsBetween(ctx, vcursor, rp.Vindex.(vindexes.Sequential), rp.Keyspace, value.TupleValues())
if err != nil {
return nil, nil, err
}
return rss, shardVars(bindVars, values), nil
}

func (rp *RoutingParameters) multiEqual(ctx context.Context, vcursor VCursor, bindVars map[string]*querypb.BindVariable) ([]*srvtopo.ResolvedShard, []map[string]*querypb.BindVariable, error) {
env := evalengine.NewExpressionEnv(ctx, bindVars, vcursor)
value, err := env.Evaluate(rp.Values[0])
Expand Down Expand Up @@ -520,6 +545,24 @@ func buildMultiColumnVindexValues(shardsValues [][][]sqltypes.Value) [][][]*quer
return shardsIds
}

func resolveShardsBetween(ctx context.Context, vcursor VCursor, vindex vindexes.Sequential, keyspace *vindexes.Keyspace, vindexKeys []sqltypes.Value) ([]*srvtopo.ResolvedShard, [][]*querypb.Value, error) {
// Convert vindexKeys to []*querypb.Value
ids := make([]*querypb.Value, len(vindexKeys))
for i, vik := range vindexKeys {
ids[i] = sqltypes.ValueToProto(vik)
}

// RangeMap using the Vindex
destinations, err := vindex.RangeMap(ctx, vcursor, vindexKeys[0], vindexKeys[1])
if err != nil {
return nil, nil, err

}

// And use the Resolver to map to ResolvedShards.
return vcursor.ResolveDestinations(ctx, keyspace.Name, ids, destinations)
}

func shardVars(bv map[string]*querypb.BindVariable, mapVals [][]*querypb.Value) []map[string]*querypb.BindVariable {
shardVars := make([]map[string]*querypb.BindVariable, len(mapVals))
for i, vals := range mapVals {
Expand Down
40 changes: 40 additions & 0 deletions go/vt/vtgate/planbuilder/operators/sharded_routing.go
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,9 @@ func (tr *ShardedRouting) resetRoutingLogic(ctx *plancontext.PlanningContext) Ro
func (tr *ShardedRouting) searchForNewVindexes(ctx *plancontext.PlanningContext, predicate sqlparser.Expr) (Routing, bool) {
newVindexFound := false
switch node := predicate.(type) {
case *sqlparser.BetweenExpr:
return tr.planBetweenOp(ctx, node)

case *sqlparser.ComparisonExpr:
return tr.planComparison(ctx, node)

Expand All @@ -234,6 +237,35 @@ func (tr *ShardedRouting) searchForNewVindexes(ctx *plancontext.PlanningContext,
return nil, newVindexFound
}

func (tr *ShardedRouting) planBetweenOp(ctx *plancontext.PlanningContext, node *sqlparser.BetweenExpr) (routing Routing, foundNew bool) {
column, ok := node.Left.(*sqlparser.ColName)
if !ok {
return nil, false
}
var vdValue sqlparser.ValTuple = sqlparser.ValTuple([]sqlparser.Expr{node.From, node.To})

opcode := func(vindex *vindexes.ColumnVindex) engine.Opcode {
if _, ok := vindex.Vindex.(vindexes.Sequential); ok {
return engine.Between
}
return engine.Scatter
}

sequentialVdx := func(vindex *vindexes.ColumnVindex) vindexes.Vindex {
if _, ok := vindex.Vindex.(vindexes.Sequential); ok {
return vindex.Vindex
}
// if vindex is not of type Sequential, we can't use this vindex at all
return nil
}

val := makeEvalEngineExpr(ctx, vdValue)
if val == nil {
return nil, false
}
return nil, tr.haveMatchingVindex(ctx, node, vdValue, column, val, opcode, sequentialVdx)
}

func (tr *ShardedRouting) planComparison(ctx *plancontext.PlanningContext, cmp *sqlparser.ComparisonExpr) (routing Routing, foundNew bool) {
switch cmp.Operator {
case sqlparser.EqualOp:
Expand Down Expand Up @@ -332,6 +364,8 @@ func (tr *ShardedRouting) Cost() int {
return 5
case engine.IN:
return 10
case engine.Between:
return 10
case engine.MultiEqual:
return 10
case engine.Scatter:
Expand Down Expand Up @@ -441,6 +475,12 @@ func (tr *ShardedRouting) processMultiColumnVindex(
return newVindexFound
}

routeOpcode := opcode(v.ColVindex)
vindex := vfunc(v.ColVindex)
if vindex == nil || routeOpcode == engine.Scatter {
return newVindexFound
}

var newOption []*VindexOption
for _, op := range v.Options {
if op.Ready {
Expand Down
143 changes: 143 additions & 0 deletions go/vt/vtgate/planbuilder/testdata/filter_cases.json
Original file line number Diff line number Diff line change
Expand Up @@ -4820,5 +4820,148 @@
"user.authoritative"
]
}
},
{
"comment": "Between clause on primary indexed id column (binary vindex on id)",
"query": "select id from unq_binary_idx where id between 1 and 5",
"plan": {
"QueryType": "SELECT",
"Original": "select id from unq_binary_idx where id between 1 and 5",
"Instructions": {
"OperatorType": "Route",
"Variant": "Between",
"Keyspace": {
"Name": "user",
"Sharded": true
},
"FieldQuery": "select id from unq_binary_idx where 1 != 1",
"Query": "select id from unq_binary_idx where id between 1 and 5",
"Table": "unq_binary_idx",
"Values": [
"(1, 5)"
],
"Vindex": "binary"
},
"TablesUsed": [
"user.unq_binary_idx"
]
}
},
{
"comment": "Between clause on customer.id column (xxhash vindex on id)",
"query": "select id from customer where id between 1 and 5",
"plan": {
"QueryType": "SELECT",
"Original": "select id from customer where id between 1 and 5",
"Instructions": {
"OperatorType": "Route",
"Variant": "Scatter",
"Keyspace": {
"Name": "user",
"Sharded": true
},
"FieldQuery": "select id from customer where 1 != 1",
"Query": "select id from customer where id between 1 and 5",
"Table": "customer"
},
"TablesUsed": [
"user.customer"
]
}
},
{
"comment": "Between clause on col1 column (there is no vindex on this column)",
"query": "select id, col1 from unq_binary_idx where col1 between 10 and 50",
"plan": {
"QueryType": "SELECT",
"Original": "select id, col1 from unq_binary_idx where col1 between 10 and 50",
"Instructions": {
"OperatorType": "Route",
"Variant": "Scatter",
"Keyspace": {
"Name": "user",
"Sharded": true
},
"FieldQuery": "select id, col1 from unq_binary_idx where 1 != 1",
"Query": "select id, col1 from unq_binary_idx where col1 between 10 and 50",
"Table": "unq_binary_idx"
},
"TablesUsed": [
"user.unq_binary_idx"
]
}
},
{
Copy link
Collaborator

Choose a reason for hiding this comment

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

nice set of planner tests. could you add at least one test where the values in the between are not literal values, but instead columns from another table. something like:

select * 
from tblA 
  join tblB on tblA.foo = tbl.bar 
where tblA.X between tblB.C and tblB.D

it'd be good to make sure that this also works as expected.

Copy link
Author

Choose a reason for hiding this comment

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

thanks for suggesting this , added a test case now 0697cb0 , and also fixed missing Cost for RoutOpCode.Between.

"comment": "Between clause on multicolumn vindex (cola,colb)",
"query": "select cola,colb,colc from multicol_tbl where cola between 1 and 5",
"plan": {
"QueryType": "SELECT",
"Original": "select cola,colb,colc from multicol_tbl where cola between 1 and 5",
"Instructions": {
"OperatorType": "Route",
"Variant": "Scatter",
"Keyspace": {
"Name": "user",
"Sharded": true
},
"FieldQuery": "select cola, colb, colc from multicol_tbl where 1 != 1",
"Query": "select cola, colb, colc from multicol_tbl where cola between 1 and 5",
"Table": "multicol_tbl"
},
"TablesUsed": [
"user.multicol_tbl"
]
}
},
{
"comment": "Between clause on a binary vindex field with values from a different table",
"query": "select s.oid,s.col1, se.colb from sales s join sales_extra se on s.col1 = se.cola where s.oid between se.start and se.end",
"plan": {
"QueryType": "SELECT",
"Original": "select s.oid,s.col1, se.colb from sales s join sales_extra se on s.col1 = se.cola where s.oid between se.start and se.end",
"Instructions": {
"OperatorType": "Join",
"Variant": "Join",
"JoinColumnIndexes": "R:0,R:1,L:0",
"JoinVars": {
"se_cola": 1,
"se_end": 3,
"se_start": 2
},
"TableName": "sales_extra_sales",
"Inputs": [
{
"OperatorType": "Route",
"Variant": "Scatter",
"Keyspace": {
"Name": "user",
"Sharded": true
},
"FieldQuery": "select se.colb, se.cola, se.`start`, se.`end` from sales_extra as se where 1 != 1",
"Query": "select se.colb, se.cola, se.`start`, se.`end` from sales_extra as se",
"Table": "sales_extra"
},
{
"OperatorType": "Route",
"Variant": "Between",
"Keyspace": {
"Name": "user",
"Sharded": true
},
"FieldQuery": "select s.oid, s.col1 from sales as s where 1 != 1",
"Query": "select s.oid, s.col1 from sales as s where s.oid between :se_start /* INT16 */ and :se_end /* INT16 */ and s.col1 = :se_cola /* VARCHAR */",
"Table": "sales",
"Values": [
"(:se_start, :se_end)"
],
"Vindex": "binary"
}
]
},
"TablesUsed": [
"user.sales",
"user.sales_extra"
]
}
}
]
59 changes: 59 additions & 0 deletions go/vt/vtgate/planbuilder/testdata/vschemas/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,9 @@
"to": "keyspace_id",
"write_only": "true"
}
},
"binary": {
"type": "binary"
}
},
"tables": {
Expand Down Expand Up @@ -513,6 +516,62 @@
"name": "shard_index"
}
]
},
"unq_binary_idx": {
"column_vindexes" : [
{
"column" : "id",
"name": "binary"
}
],
"columns" :[
{
"name": "col1",
"type": "INT16"
}
]
},
"sales": {
"column_vindexes" : [
{
"column" : "oid",
"name" : "binary"
}
],
"columns" : [
{
"name" : "col1",
"type" : "VARCHAR"
}
]
},
"sales_extra" : {
"column_vindexes": [
{
"columns": [
"colx"
],
"name": "shard_index"
}
],
"columns" : [
{
"name" : "cola",
"type" : "VARCHAR"
},
{
"name" : "colb",
"type" : "VARCHAR"
},
{
"name" : "start",
"type" : "INT16"
},
{
"name" : "end",
"type" : "INT16"
}
]
}
}
},
Expand Down
Loading
Loading