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 }