vitess.io/vitess@v0.16.2/go/vt/srvtopo/resolver.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 srvtopo
    18  
    19  import (
    20  	"sort"
    21  
    22  	"vitess.io/vitess/go/sqltypes"
    23  
    24  	"context"
    25  
    26  	"google.golang.org/protobuf/proto"
    27  
    28  	"vitess.io/vitess/go/vt/key"
    29  	"vitess.io/vitess/go/vt/topo/topoproto"
    30  	"vitess.io/vitess/go/vt/vterrors"
    31  	"vitess.io/vitess/go/vt/vttablet/queryservice"
    32  
    33  	querypb "vitess.io/vitess/go/vt/proto/query"
    34  	topodatapb "vitess.io/vitess/go/vt/proto/topodata"
    35  	vtrpcpb "vitess.io/vitess/go/vt/proto/vtrpc"
    36  )
    37  
    38  // A Gateway is the query processing module for each shard,
    39  // which is used by ScatterConn.
    40  type Gateway interface {
    41  	// the query service that this Gateway wraps around
    42  	queryservice.QueryService
    43  
    44  	// QueryServiceByAlias returns a QueryService
    45  	QueryServiceByAlias(alias *topodatapb.TabletAlias, target *querypb.Target) (queryservice.QueryService, error)
    46  }
    47  
    48  // A Resolver can resolve keyspace ids and key ranges into ResolvedShard*
    49  // objects. It uses an underlying srvtopo.Server to find the topology,
    50  // and a TargetStats object to find the healthy destinations.
    51  type Resolver struct {
    52  	// topoServ is the srvtopo.Server to use for topo queries.
    53  	topoServ Server
    54  
    55  	// gateway
    56  	gateway Gateway
    57  
    58  	// localCell is the local cell for the queries.
    59  	localCell string
    60  
    61  	// FIXME(alainjobart) also need a list of remote cells.
    62  	// FIXME(alainjobart) and a policy on how to use them.
    63  	// But for now we only use the local cell.
    64  }
    65  
    66  // NewResolver creates a new Resolver.
    67  func NewResolver(topoServ Server, gateway Gateway, localCell string) *Resolver {
    68  	return &Resolver{
    69  		topoServ:  topoServ,
    70  		gateway:   gateway,
    71  		localCell: localCell,
    72  	}
    73  }
    74  
    75  // ResolvedShard contains everything we need to send a query to a shard.
    76  type ResolvedShard struct {
    77  	// Target describes the target shard.
    78  	Target *querypb.Target
    79  
    80  	// Gateway is the way to execute a query on this shard
    81  	Gateway Gateway
    82  }
    83  
    84  // ResolvedShardEqual is an equality check on *ResolvedShard.
    85  func ResolvedShardEqual(rs1, rs2 *ResolvedShard) bool {
    86  	return proto.Equal(rs1.Target, rs2.Target)
    87  }
    88  
    89  // ResolvedShardsEqual is an equality check on []*ResolvedShard.
    90  func ResolvedShardsEqual(rss1, rss2 []*ResolvedShard) bool {
    91  	if len(rss1) != len(rss2) {
    92  		return false
    93  	}
    94  	for i, rs1 := range rss1 {
    95  		if !ResolvedShardEqual(rs1, rss2[i]) {
    96  			return false
    97  		}
    98  	}
    99  	return true
   100  }
   101  
   102  // WithKeyspace returns a ResolvedShard with a new keyspace keeping other parameters the same
   103  func (rs *ResolvedShard) WithKeyspace(newKeyspace string) *ResolvedShard {
   104  	return &ResolvedShard{
   105  		Target: &querypb.Target{
   106  			Keyspace:   newKeyspace,
   107  			Shard:      rs.Target.Shard,
   108  			TabletType: rs.Target.TabletType,
   109  			Cell:       rs.Target.Cell,
   110  		},
   111  		Gateway: rs.Gateway,
   112  	}
   113  }
   114  
   115  // GetKeyspaceShards return all the shards in a keyspace. It follows
   116  // redirection if ServedFrom is set. It is only valid for the local cell.
   117  // Do not use it to further resolve shards, instead use the Resolve* methods.
   118  func (r *Resolver) GetKeyspaceShards(ctx context.Context, keyspace string, tabletType topodatapb.TabletType) (string, *topodatapb.SrvKeyspace, []*topodatapb.ShardReference, error) {
   119  	srvKeyspace, err := r.topoServ.GetSrvKeyspace(ctx, r.localCell, keyspace)
   120  	if err != nil {
   121  		return "", nil, nil, vterrors.Errorf(vtrpcpb.Code_UNKNOWN, "keyspace %v fetch error: %v", keyspace, err)
   122  	}
   123  
   124  	// check if the keyspace has been redirected for this tabletType.
   125  	for _, sf := range srvKeyspace.ServedFrom {
   126  		if sf.TabletType == tabletType {
   127  			keyspace = sf.Keyspace
   128  			srvKeyspace, err = r.topoServ.GetSrvKeyspace(ctx, r.localCell, keyspace)
   129  			if err != nil {
   130  				return "", nil, nil, vterrors.Errorf(vtrpcpb.Code_UNKNOWN, "keyspace %v fetch error: %v", keyspace, err)
   131  			}
   132  		}
   133  	}
   134  
   135  	partition := topoproto.SrvKeyspaceGetPartition(srvKeyspace, tabletType)
   136  	if partition == nil {
   137  		return "", nil, nil, vterrors.Errorf(vtrpcpb.Code_UNKNOWN, "No partition found for tabletType %v in keyspace %v", topoproto.TabletTypeLString(tabletType), keyspace)
   138  	}
   139  	return keyspace, srvKeyspace, partition.ShardReferences, nil
   140  }
   141  
   142  // GetAllShards returns the list of ResolvedShards associated with all
   143  // the shards in a keyspace.
   144  // FIXME(alainjobart) callers should convert to ResolveDestination(),
   145  // and GetSrvKeyspace.
   146  func (r *Resolver) GetAllShards(ctx context.Context, keyspace string, tabletType topodatapb.TabletType) ([]*ResolvedShard, *topodatapb.SrvKeyspace, error) {
   147  	keyspace, srvKeyspace, allShards, err := r.GetKeyspaceShards(ctx, keyspace, tabletType)
   148  	if err != nil {
   149  		return nil, nil, err
   150  	}
   151  
   152  	res := make([]*ResolvedShard, len(allShards))
   153  	for i, shard := range allShards {
   154  		target := &querypb.Target{
   155  			Keyspace:   keyspace,
   156  			Shard:      shard.Name,
   157  			TabletType: tabletType,
   158  			Cell:       r.localCell,
   159  		}
   160  		// Right now we always set the Cell to ""
   161  		// Later we can fallback to another cell if needed.
   162  		// We would then need to read the SrvKeyspace there too.
   163  		target.Cell = ""
   164  		res[i] = &ResolvedShard{
   165  			Target:  target,
   166  			Gateway: r.gateway,
   167  		}
   168  	}
   169  	return res, srvKeyspace, nil
   170  }
   171  
   172  // GetAllKeyspaces returns all the known keyspaces in the local cell.
   173  func (r *Resolver) GetAllKeyspaces(ctx context.Context) ([]string, error) {
   174  	keyspaces, err := r.topoServ.GetSrvKeyspaceNames(ctx, r.localCell, true)
   175  	if err != nil {
   176  		return nil, vterrors.Errorf(vtrpcpb.Code_UNKNOWN, "keyspace names fetch error: %v", err)
   177  	}
   178  	// FIXME(alainjobart) this should be unnecessary. The results
   179  	// of ListDir are sorted, and that's the underlying topo code.
   180  	// But the tests depend on this behavior now.
   181  	sort.Strings(keyspaces)
   182  	return keyspaces, nil
   183  }
   184  
   185  // ResolveDestinationsMultiCol resolves values and their destinations into their
   186  // respective shards for multi col vindex.
   187  //
   188  // If ids is nil, the returned [][][]sqltypes.Value is also nil.
   189  // Otherwise, len(ids) has to match len(destinations), and then the returned
   190  // [][][]sqltypes.Value is populated with all the values that go in each shard,
   191  // and len([]*ResolvedShard) matches len([][][]sqltypes.Value).
   192  //
   193  // Sample input / output:
   194  // - destinations: dst1, 			dst2, 		dst3
   195  // - ids:          [id1a,id1b],  [id2a,id2b],  [id3a,id3b]
   196  // If dst1 is in shard1, and dst2 and dst3 are in shard2, the output will be:
   197  // - []*ResolvedShard:   shard1, 			shard2
   198  // - [][][]sqltypes.Value: [[id1a,id1b]],  [[id2a,id2b], [id3a,id3b]]
   199  func (r *Resolver) ResolveDestinationsMultiCol(ctx context.Context, keyspace string, tabletType topodatapb.TabletType, ids [][]sqltypes.Value, destinations []key.Destination) ([]*ResolvedShard, [][][]sqltypes.Value, error) {
   200  	keyspace, _, allShards, err := r.GetKeyspaceShards(ctx, keyspace, tabletType)
   201  	if err != nil {
   202  		return nil, nil, err
   203  	}
   204  	accumulator := &resultAcc{
   205  		resolved:   make(map[string]int),
   206  		resolver:   r,
   207  		ids:        ids,
   208  		keyspace:   keyspace,
   209  		tabletType: tabletType,
   210  	}
   211  
   212  	for i, destination := range destinations {
   213  		if err := destination.Resolve(allShards, accumulator.resolveShard(i)); err != nil {
   214  			return nil, nil, err
   215  		}
   216  	}
   217  	return accumulator.shards, accumulator.values, nil
   218  }
   219  
   220  type resultAcc struct {
   221  	shards     []*ResolvedShard
   222  	values     [][][]sqltypes.Value
   223  	resolved   map[string]int
   224  	resolver   *Resolver
   225  	ids        [][]sqltypes.Value
   226  	keyspace   string
   227  	tabletType topodatapb.TabletType
   228  }
   229  
   230  // resolveShard is called once per shard that is resolved. It will keep track of which shards that are
   231  // the destinations resolved, and the values that are bound to each shard
   232  func (acc *resultAcc) resolveShard(idx int) func(shard string) error {
   233  	return func(shard string) error {
   234  		offsetInValues, ok := acc.resolved[shard]
   235  		if !ok {
   236  			target := &querypb.Target{
   237  				Keyspace:   acc.keyspace,
   238  				Shard:      shard,
   239  				TabletType: acc.tabletType,
   240  			}
   241  			// Right now we always set the Cell to ""
   242  			// Later we can fallback to another cell if needed.
   243  			// We would then need to read the SrvKeyspace there too.
   244  			target.Cell = ""
   245  			offsetInValues = len(acc.shards)
   246  			acc.shards = append(acc.shards, &ResolvedShard{
   247  				Target:  target,
   248  				Gateway: acc.resolver.gateway,
   249  			})
   250  			if acc.ids != nil {
   251  				acc.values = append(acc.values, nil)
   252  			}
   253  			acc.resolved[shard] = offsetInValues
   254  		}
   255  		if acc.ids != nil {
   256  			acc.values[offsetInValues] = append(acc.values[offsetInValues], acc.ids[idx])
   257  		}
   258  		return nil
   259  	}
   260  }
   261  
   262  // ResolveDestinations resolves values and their destinations into their
   263  // respective shards.
   264  //
   265  // If ids is nil, the returned [][]*querypb.Value is also nil.
   266  // Otherwise, len(ids) has to match len(destinations), and then the returned
   267  // [][]*querypb.Value is populated with all the values that go in each shard,
   268  // and len([]*ResolvedShard) matches len([][]*querypb.Value).
   269  //
   270  // Sample input / output:
   271  // - destinations: dst1, dst2, dst3
   272  // - ids:          id1,  id2,  id3
   273  // If dst1 is in shard1, and dst2 and dst3 are in shard2, the output will be:
   274  // - []*ResolvedShard:   shard1, shard2
   275  // - [][]*querypb.Value: [id1],  [id2, id3]
   276  func (r *Resolver) ResolveDestinations(ctx context.Context, keyspace string, tabletType topodatapb.TabletType, ids []*querypb.Value, destinations []key.Destination) ([]*ResolvedShard, [][]*querypb.Value, error) {
   277  	keyspace, _, allShards, err := r.GetKeyspaceShards(ctx, keyspace, tabletType)
   278  	if err != nil {
   279  		return nil, nil, err
   280  	}
   281  
   282  	var result []*ResolvedShard
   283  	var values [][]*querypb.Value
   284  	resolved := make(map[string]int)
   285  	for i, destination := range destinations {
   286  		if err := destination.Resolve(allShards, func(shard string) error {
   287  			s, ok := resolved[shard]
   288  			if !ok {
   289  				target := &querypb.Target{
   290  					Keyspace:   keyspace,
   291  					Shard:      shard,
   292  					TabletType: tabletType,
   293  					Cell:       r.localCell,
   294  				}
   295  				// Right now we always set the Cell to ""
   296  				// Later we can fallback to another cell if needed.
   297  				// We would then need to read the SrvKeyspace there too.
   298  				target.Cell = ""
   299  				s = len(result)
   300  				result = append(result, &ResolvedShard{
   301  					Target:  target,
   302  					Gateway: r.gateway,
   303  				})
   304  				if ids != nil {
   305  					values = append(values, nil)
   306  				}
   307  				resolved[shard] = s
   308  			}
   309  			if ids != nil {
   310  				values[s] = append(values[s], ids[i])
   311  			}
   312  			return nil
   313  		}); err != nil {
   314  			return nil, nil, err
   315  		}
   316  	}
   317  	return result, values, nil
   318  }
   319  
   320  // ResolveDestination is a shortcut to ResolveDestinations with only
   321  // one Destination, and no ids.
   322  func (r *Resolver) ResolveDestination(ctx context.Context, keyspace string, tabletType topodatapb.TabletType, destination key.Destination) ([]*ResolvedShard, error) {
   323  	rss, _, err := r.ResolveDestinations(ctx, keyspace, tabletType, nil, []key.Destination{destination})
   324  	return rss, err
   325  }
   326  
   327  // ValuesEqual is a helper method to compare arrays of values.
   328  func ValuesEqual(vss1, vss2 [][]*querypb.Value) bool {
   329  	if len(vss1) != len(vss2) {
   330  		return false
   331  	}
   332  	for i, vs1 := range vss1 {
   333  		if len(vs1) != len(vss2[i]) {
   334  			return false
   335  		}
   336  		for j, v1 := range vs1 {
   337  			if !proto.Equal(v1, vss2[i][j]) {
   338  				return false
   339  			}
   340  		}
   341  	}
   342  	return true
   343  }
   344  
   345  // GetGateway returns the used gateway
   346  func (r *Resolver) GetGateway() Gateway {
   347  	return r.gateway
   348  }