vitess.io/vitess@v0.16.2/go/vt/vtexplain/vtexplain_vtgate.go (about)

     1  /*
     2  Copyright 2019 The Vitess Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  // Package vtexplain analyzes a set of sql statements and returns the
    18  // corresponding vtgate and vttablet query plans that will be executed
    19  // on the given statements
    20  package vtexplain
    21  
    22  import (
    23  	"context"
    24  	"fmt"
    25  	"sort"
    26  	"strings"
    27  
    28  	"vitess.io/vitess/go/vt/vtgate/vindexes"
    29  
    30  	"vitess.io/vitess/go/cache"
    31  	"vitess.io/vitess/go/vt/topo"
    32  	"vitess.io/vitess/go/vt/topo/memorytopo"
    33  
    34  	"vitess.io/vitess/go/vt/vterrors"
    35  
    36  	"vitess.io/vitess/go/json2"
    37  	"vitess.io/vitess/go/streamlog"
    38  	"vitess.io/vitess/go/vt/discovery"
    39  	"vitess.io/vitess/go/vt/key"
    40  	"vitess.io/vitess/go/vt/log"
    41  	"vitess.io/vitess/go/vt/srvtopo"
    42  	"vitess.io/vitess/go/vt/vtgate"
    43  	"vitess.io/vitess/go/vt/vtgate/engine"
    44  	"vitess.io/vitess/go/vt/vttablet/queryservice"
    45  
    46  	querypb "vitess.io/vitess/go/vt/proto/query"
    47  	topodatapb "vitess.io/vitess/go/vt/proto/topodata"
    48  	vschemapb "vitess.io/vitess/go/vt/proto/vschema"
    49  	vtgatepb "vitess.io/vitess/go/vt/proto/vtgate"
    50  )
    51  
    52  func (vte *VTExplain) initVtgateExecutor(vSchemaStr, ksShardMapStr string, opts *Options) error {
    53  	vte.explainTopo = &ExplainTopo{NumShards: opts.NumShards}
    54  	vte.explainTopo.TopoServer = memorytopo.NewServer(vtexplainCell)
    55  	vte.healthCheck = discovery.NewFakeHealthCheck(nil)
    56  
    57  	resolver := vte.newFakeResolver(opts, vte.explainTopo, vtexplainCell)
    58  
    59  	err := vte.buildTopology(opts, vSchemaStr, ksShardMapStr, opts.NumShards)
    60  	if err != nil {
    61  		return err
    62  	}
    63  
    64  	vte.vtgateSession.TargetString = opts.Target
    65  
    66  	if opts.PlannerVersion != querypb.ExecuteOptions_DEFAULT_PLANNER {
    67  		if vte.vtgateSession.Options == nil {
    68  			vte.vtgateSession.Options = &querypb.ExecuteOptions{}
    69  		}
    70  		vte.vtgateSession.Options.PlannerVersion = opts.PlannerVersion
    71  	}
    72  
    73  	streamSize := 10
    74  	var schemaTracker vtgate.SchemaInfo // no schema tracker for these tests
    75  	vte.vtgateExecutor = vtgate.NewExecutor(context.Background(), vte.explainTopo, vtexplainCell, resolver, opts.Normalize, false, streamSize, cache.DefaultConfig, schemaTracker, false, opts.PlannerVersion)
    76  
    77  	queryLogBufferSize := 10
    78  	vtgate.SetQueryLogger(streamlog.New("VTGate", queryLogBufferSize))
    79  
    80  	return nil
    81  }
    82  
    83  func (vte *VTExplain) newFakeResolver(opts *Options, serv srvtopo.Server, cell string) *vtgate.Resolver {
    84  	ctx := context.Background()
    85  	gw := vtgate.NewTabletGateway(ctx, vte.healthCheck, serv, cell)
    86  	_ = gw.WaitForTablets([]topodatapb.TabletType{topodatapb.TabletType_REPLICA})
    87  
    88  	txMode := vtgatepb.TransactionMode_MULTI
    89  	if opts.ExecutionMode == ModeTwoPC {
    90  		txMode = vtgatepb.TransactionMode_TWOPC
    91  	}
    92  	tc := vtgate.NewTxConn(gw, txMode)
    93  	sc := vtgate.NewScatterConn("", tc, gw)
    94  	srvResolver := srvtopo.NewResolver(serv, gw, cell)
    95  	return vtgate.NewResolver(srvResolver, serv, cell, sc)
    96  }
    97  
    98  func (vte *VTExplain) buildTopology(opts *Options, vschemaStr string, ksShardMapStr string, numShardsPerKeyspace int) error {
    99  	vte.explainTopo.Lock.Lock()
   100  	defer vte.explainTopo.Lock.Unlock()
   101  
   102  	// We have to use proto's custom json loader so it can
   103  	// handle string->enum conversion correctly.
   104  	var srvVSchema vschemapb.SrvVSchema
   105  	wrappedStr := fmt.Sprintf(`{"keyspaces": %s}`, vschemaStr)
   106  	err := json2.Unmarshal([]byte(wrappedStr), &srvVSchema)
   107  	if err != nil {
   108  		return err
   109  	}
   110  	schema := vindexes.BuildVSchema(&srvVSchema)
   111  	for ks, ksSchema := range schema.Keyspaces {
   112  		if ksSchema.Error != nil {
   113  			return vterrors.Wrapf(ksSchema.Error, "vschema failed to load on keyspace [%s]", ks)
   114  		}
   115  	}
   116  	vte.explainTopo.Keyspaces = srvVSchema.Keyspaces
   117  
   118  	ksShardMap, err := getKeyspaceShardMap(ksShardMapStr)
   119  	if err != nil {
   120  		return err
   121  	}
   122  
   123  	vte.explainTopo.TabletConns = make(map[string]*explainTablet)
   124  	vte.explainTopo.KeyspaceShards = make(map[string]map[string]*topodatapb.ShardReference)
   125  	for ks, vschema := range vte.explainTopo.Keyspaces {
   126  		shards, err := getShardRanges(ks, vschema, ksShardMap, numShardsPerKeyspace)
   127  		if err != nil {
   128  			return err
   129  		}
   130  
   131  		vte.explainTopo.KeyspaceShards[ks] = make(map[string]*topodatapb.ShardReference)
   132  
   133  		for _, shard := range shards {
   134  			// If the topology is in the middle of a reshard, there can be two shards covering the same key range (e.g.
   135  			// both source shard 80- and target shard 80-c0 cover the keyrange 80-c0). For the purposes of explain, we
   136  			// should only consider the one that is serving, hence we skip the ones not serving. Otherwise, vtexplain
   137  			// gives inconsistent results - sometimes it will route the query being explained to the source shard, and
   138  			// sometimes to the destination shard. See https://github.com/vitessio/vitess/issues/11632 .
   139  			if shardInfo, ok := ksShardMap[ks][shard.Name]; ok && !shardInfo.IsPrimaryServing {
   140  				continue
   141  			}
   142  			hostname := fmt.Sprintf("%s/%s", ks, shard.Name)
   143  			log.Infof("registering test tablet %s for keyspace %s shard %s", hostname, ks, shard.Name)
   144  
   145  			tablet := vte.healthCheck.AddFakeTablet(vtexplainCell, hostname, 1, ks, shard.Name, topodatapb.TabletType_PRIMARY, true, 1, nil, func(t *topodatapb.Tablet) queryservice.QueryService {
   146  				return vte.newTablet(opts, t)
   147  			})
   148  			vte.explainTopo.TabletConns[hostname] = tablet.(*explainTablet)
   149  			vte.explainTopo.KeyspaceShards[ks][shard.Name] = shard
   150  		}
   151  	}
   152  
   153  	return err
   154  }
   155  
   156  func getKeyspaceShardMap(ksShardMapStr string) (map[string]map[string]*topo.ShardInfo, error) {
   157  	if ksShardMapStr == "" {
   158  		return map[string]map[string]*topo.ShardInfo{}, nil
   159  	}
   160  
   161  	// keyspace-name -> shard-name -> ShardInfo
   162  	var ksShardMap map[string]map[string]*topo.ShardInfo
   163  	err := json2.Unmarshal([]byte(ksShardMapStr), &ksShardMap)
   164  
   165  	return ksShardMap, err
   166  }
   167  
   168  func getShardRanges(ks string, vschema *vschemapb.Keyspace, ksShardMap map[string]map[string]*topo.ShardInfo, numShardsPerKeyspace int) ([]*topodatapb.ShardReference, error) {
   169  	shardMap, ok := ksShardMap[ks]
   170  	if ok {
   171  		shards := make([]*topodatapb.ShardReference, 0, len(shardMap))
   172  		for shard, info := range shardMap {
   173  			ref := &topodatapb.ShardReference{
   174  				Name:     shard,
   175  				KeyRange: info.KeyRange,
   176  			}
   177  
   178  			shards = append(shards, ref)
   179  		}
   180  		return shards, nil
   181  
   182  	}
   183  
   184  	numShards := 1
   185  	if vschema.Sharded {
   186  		numShards = numShardsPerKeyspace
   187  	}
   188  
   189  	shards := make([]*topodatapb.ShardReference, numShards)
   190  
   191  	for i := 0; i < numShards; i++ {
   192  		kr, err := key.EvenShardsKeyRange(i, numShards)
   193  		if err != nil {
   194  			return nil, err
   195  		}
   196  
   197  		shards[i] = &topodatapb.ShardReference{
   198  			Name:     key.KeyRangeString(kr),
   199  			KeyRange: kr,
   200  		}
   201  	}
   202  
   203  	return shards, nil
   204  }
   205  
   206  func (vte *VTExplain) vtgateExecute(sql string) ([]*engine.Plan, map[string]*TabletActions, error) {
   207  	// This method will sort the shard session lexicographically.
   208  	// This will ensure that the commit/rollback order is predictable.
   209  	vte.sortShardSession()
   210  
   211  	// use the plan cache to get the set of plans used for this query, then
   212  	// clear afterwards for the next run
   213  	planCache := vte.vtgateExecutor.Plans()
   214  
   215  	_, err := vte.vtgateExecutor.Execute(context.Background(), "VtexplainExecute", vtgate.NewSafeSession(vte.vtgateSession), sql, nil)
   216  	if err != nil {
   217  		for _, tc := range vte.explainTopo.TabletConns {
   218  			tc.tabletQueries = nil
   219  			tc.mysqlQueries = nil
   220  		}
   221  		planCache.Clear()
   222  
   223  		return nil, nil, vterrors.Wrapf(err, "vtexplain execute error in '%s'", sql)
   224  	}
   225  
   226  	var plans []*engine.Plan
   227  	planCache.ForEach(func(value any) bool {
   228  		plan := value.(*engine.Plan)
   229  		plan.ExecTime = 0
   230  		plans = append(plans, plan)
   231  		return true
   232  	})
   233  	planCache.Clear()
   234  
   235  	tabletActions := make(map[string]*TabletActions)
   236  	for shard, tc := range vte.explainTopo.TabletConns {
   237  		if len(tc.tabletQueries) == 0 {
   238  			continue
   239  		}
   240  
   241  		func() {
   242  			tc.mu.Lock()
   243  			defer tc.mu.Unlock()
   244  
   245  			tabletActions[shard] = &TabletActions{
   246  				TabletQueries: tc.tabletQueries,
   247  				MysqlQueries:  tc.mysqlQueries,
   248  			}
   249  
   250  			tc.tabletQueries = nil
   251  			tc.mysqlQueries = nil
   252  		}()
   253  	}
   254  
   255  	return plans, tabletActions, nil
   256  }
   257  
   258  func (vte *VTExplain) sortShardSession() {
   259  	ss := vte.vtgateSession.ShardSessions
   260  
   261  	sort.Slice(ss, func(i, j int) bool {
   262  		if ss[i].Target.Keyspace != ss[j].Target.Keyspace {
   263  			return strings.Compare(ss[i].Target.Keyspace, ss[j].Target.Keyspace) <= 0
   264  		}
   265  		return strings.Compare(ss[i].Target.Shard, ss[j].Target.Shard) <= 0
   266  	})
   267  }