vitess.io/vitess@v0.16.2/go/vt/vtadmin/cluster/internal/caches/schemacache/cache.go (about)

     1  /*
     2  Copyright 2022 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 schemacache provides wrapper functions for interacting with
    18  // instances of the generic (vtadmin/cache).Cache that store schemas.
    19  package schemacache
    20  
    21  import (
    22  	"fmt"
    23  	"time"
    24  
    25  	"google.golang.org/protobuf/proto"
    26  
    27  	"vitess.io/vitess/go/vt/log"
    28  	"vitess.io/vitess/go/vt/mysqlctl/tmutils"
    29  	"vitess.io/vitess/go/vt/vtadmin/cache"
    30  	"vitess.io/vitess/go/vt/vterrors"
    31  
    32  	tabletmanagerdatapb "vitess.io/vitess/go/vt/proto/tabletmanagerdata"
    33  	vtadminpb "vitess.io/vitess/go/vt/proto/vtadmin"
    34  	vtctldatapb "vitess.io/vitess/go/vt/proto/vtctldata"
    35  	"vitess.io/vitess/go/vt/proto/vtrpc"
    36  )
    37  
    38  // Key is the cache key for vtadmin's schema caches.
    39  type Key struct {
    40  	ClusterID               string
    41  	Keyspace                string
    42  	IncludeNonServingShards bool
    43  }
    44  
    45  // Key implements cache.Keyer for Key.
    46  func (k Key) Key() string {
    47  	return fmt.Sprintf("%s/%s/%v", k.ClusterID, k.Keyspace, k.IncludeNonServingShards)
    48  }
    49  
    50  type schemaCache = cache.Cache[Key, []*vtadminpb.Schema] // define a type alias for brevity within this package.
    51  
    52  // AddOrBackfill takes a (schemas, key, duration) triple (see cache.Add) and a
    53  // LoadOptions and either adds the schemas to the cache directly for the key, or
    54  // enqueues a backfill for the key. Which of these occurs depends on the given
    55  // LoadOptions. If the options, when passed to Load(All|One) would result in a
    56  // full payload of the cached data, then the schemas are added to the cache
    57  // directly. Otherwise, data would be missing for future Load(All|One) calls,
    58  // so we enqueue a backfill to fetch the full payload and add that to the cache.
    59  //
    60  // Callers are expected to call this function in the background, so it returns
    61  // nothing and only logs warnings on failures.
    62  func AddOrBackfill(c *schemaCache, schemas []*vtadminpb.Schema, key Key, d time.Duration, opts LoadOptions) {
    63  	if opts.isFullPayload() {
    64  		if err := c.Add(key, schemas, d); err != nil {
    65  			log.Warningf("failed to add schema to cache for %+v: %s", key, err)
    66  		}
    67  	} else {
    68  		if !c.EnqueueBackfill(key) {
    69  			log.Warningf("failed to enqueue backfill for schema cache %+v", key)
    70  		}
    71  	}
    72  }
    73  
    74  // LoadOptions is the set of options used by Load(All|One) to filter down fully-
    75  // cached schema payloads.
    76  type LoadOptions struct {
    77  	BaseRequest    *vtctldatapb.GetSchemaRequest
    78  	AggregateSizes bool
    79  	filter         *tmutils.TableFilter
    80  }
    81  
    82  // isFullPayload returns true if the schema(s) returned by calls to
    83  // Load(All|One) from these options represents the full cached payload. if
    84  // false, Load(All|One) will return only a subset of what is in the cache,
    85  // meaning that AddOrBackfill for these options should enqueue a backfill to
    86  // fetch the full payload.
    87  func (opts LoadOptions) isFullPayload() bool {
    88  	if opts.BaseRequest == nil {
    89  		return false
    90  	}
    91  
    92  	if len(opts.BaseRequest.Tables) > 0 {
    93  		return false
    94  	}
    95  
    96  	if len(opts.BaseRequest.ExcludeTables) > 0 {
    97  		return false
    98  	}
    99  
   100  	if !opts.BaseRequest.IncludeViews {
   101  		return false
   102  	}
   103  
   104  	if opts.BaseRequest.TableNamesOnly {
   105  		return false
   106  	}
   107  
   108  	if opts.BaseRequest.TableSizesOnly {
   109  		return false
   110  	}
   111  
   112  	if opts.BaseRequest.TableSchemaOnly {
   113  		return false
   114  	}
   115  
   116  	if !opts.AggregateSizes {
   117  		return false
   118  	}
   119  
   120  	return true
   121  }
   122  
   123  // LoadAll returns the slice of all schemas cached for the given key, filtering
   124  // down based on LoadOptions.
   125  //
   126  // The boolean return value will be false if there is nothing in the cache for
   127  // key.
   128  func LoadAll(c *schemaCache, key Key, opts LoadOptions) (schemas []*vtadminpb.Schema, ok bool, err error) {
   129  	cachedSchemas, ok := c.Get(key)
   130  	if !ok {
   131  		return nil, false, nil
   132  	}
   133  
   134  	opts.filter, err = tmutils.NewTableFilter(opts.BaseRequest.Tables, opts.BaseRequest.ExcludeTables, opts.BaseRequest.IncludeViews)
   135  	if err != nil {
   136  		// Note that there's no point in falling back to a full fetch in this
   137  		// case; this exact code gets executed on the tabletserver side, too,
   138  		// so we would just fail on the same issue _after_ paying the cost to
   139  		// wait for a roundtrip to the remote tablet.
   140  		return nil, ok, err
   141  	}
   142  
   143  	schemas = make([]*vtadminpb.Schema, 0, len(cachedSchemas))
   144  	for _, schema := range cachedSchemas {
   145  		if key.Keyspace != "" && schema.Keyspace != key.Keyspace {
   146  			continue
   147  		}
   148  
   149  		schemas = append(schemas, loadSchema(schema, opts))
   150  	}
   151  
   152  	return schemas, ok, nil
   153  }
   154  
   155  // LoadOne loads a single schema for the given key, filtering down based on the
   156  // LoadOptions. If there is not exactly one schema for the key, an error is
   157  // returned.
   158  //
   159  // The boolean return value will be false if there is nothing in the cache for
   160  // key.
   161  func LoadOne(c *schemaCache, key Key, opts LoadOptions) (schema *vtadminpb.Schema, found bool, err error) {
   162  	schemas, found, err := LoadAll(c, key, opts)
   163  	if err != nil {
   164  		return nil, found, err
   165  	}
   166  
   167  	if !found {
   168  		return nil, found, nil
   169  	}
   170  
   171  	if len(schemas) != 1 {
   172  		err = vterrors.Errorf(vtrpc.Code_INTERNAL, "impossible: cache should have exactly 1 schema for request %+v (have %d)", key, len(schemas))
   173  		log.Errorf(err.Error())
   174  		return nil, found, err
   175  	}
   176  
   177  	return schemas[0], found, nil
   178  }
   179  
   180  func loadSchema(cachedSchema *vtadminpb.Schema, opts LoadOptions) *vtadminpb.Schema {
   181  	schema := proto.Clone(cachedSchema).(*vtadminpb.Schema)
   182  
   183  	if !opts.AggregateSizes {
   184  		schema.TableSizes = nil
   185  	}
   186  
   187  	tables := make([]*tabletmanagerdatapb.TableDefinition, 0, len(schema.TableDefinitions))
   188  	tableSizes := make(map[string]*vtadminpb.Schema_TableSize, len(schema.TableSizes))
   189  
   190  	for _, td := range schema.TableDefinitions {
   191  		if !opts.filter.Includes(td.Name, td.Type) {
   192  			continue
   193  		}
   194  
   195  		if opts.AggregateSizes {
   196  			tableSizes[td.Name] = schema.TableSizes[td.Name]
   197  		}
   198  
   199  		if opts.BaseRequest.TableNamesOnly {
   200  			tables = append(tables, &tabletmanagerdatapb.TableDefinition{
   201  				Name: td.Name,
   202  			})
   203  			continue
   204  		}
   205  
   206  		if opts.BaseRequest.TableSizesOnly {
   207  			tables = append(tables, &tabletmanagerdatapb.TableDefinition{
   208  				Name:       td.Name,
   209  				DataLength: td.DataLength,
   210  				RowCount:   td.RowCount,
   211  			})
   212  			continue
   213  		}
   214  
   215  		tables = append(tables, td)
   216  	}
   217  
   218  	schema.TableDefinitions = tables
   219  	schema.TableSizes = tableSizes
   220  
   221  	return schema
   222  }