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 }