vitess.io/vitess@v0.16.2/go/vt/vtgate/schema/tracker.go (about) 1 /* 2 Copyright 2021 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 schema 18 19 import ( 20 "context" 21 "sync" 22 "time" 23 24 vtrpcpb "vitess.io/vitess/go/vt/proto/vtrpc" 25 "vitess.io/vitess/go/vt/vterrors" 26 27 "vitess.io/vitess/go/vt/callerid" 28 29 "vitess.io/vitess/go/vt/vttablet/queryservice" 30 31 "vitess.io/vitess/go/mysql" 32 "vitess.io/vitess/go/sqltypes" 33 querypb "vitess.io/vitess/go/vt/proto/query" 34 35 "vitess.io/vitess/go/vt/discovery" 36 "vitess.io/vitess/go/vt/log" 37 "vitess.io/vitess/go/vt/sqlparser" 38 "vitess.io/vitess/go/vt/vtgate/vindexes" 39 ) 40 41 type ( 42 keyspaceStr = string 43 tableNameStr = string 44 viewNameStr = string 45 46 // Tracker contains the required fields to perform schema tracking. 47 Tracker struct { 48 ch chan *discovery.TabletHealth 49 cancel context.CancelFunc 50 51 mu sync.Mutex 52 tables *tableMap 53 views *viewMap 54 ctx context.Context 55 signal func() // a function that we'll call whenever we have new schema data 56 57 // map of keyspace currently tracked 58 tracked map[keyspaceStr]*updateController 59 consumeDelay time.Duration 60 } 61 ) 62 63 // defaultConsumeDelay is the default time, the updateController will wait before checking the schema fetch request queue. 64 const defaultConsumeDelay = 1 * time.Second 65 66 // aclErrorMessageLog is for logging a warning when an acl error message is received for querying schema tracking table. 67 const aclErrorMessageLog = "Table ACL might be enabled, --schema_change_signal_user needs to be passed to VTGate for schema tracking to work. Check 'schema tracking' docs on vitess.io" 68 69 // NewTracker creates the tracker object. 70 func NewTracker(ch chan *discovery.TabletHealth, user string, enableViews bool) *Tracker { 71 ctx := context.Background() 72 // Set the caller on the context if the user is provided. 73 // This user that will be sent down to vttablet calls. 74 if user != "" { 75 ctx = callerid.NewContext(ctx, nil, callerid.NewImmediateCallerID(user)) 76 } 77 78 t := &Tracker{ 79 ctx: ctx, 80 ch: ch, 81 tables: &tableMap{m: map[keyspaceStr]map[tableNameStr][]vindexes.Column{}}, 82 tracked: map[keyspaceStr]*updateController{}, 83 consumeDelay: defaultConsumeDelay, 84 } 85 86 if enableViews { 87 t.views = &viewMap{m: map[keyspaceStr]map[viewNameStr]sqlparser.SelectStatement{}} 88 } 89 return t 90 } 91 92 // LoadKeyspace loads the keyspace schema. 93 func (t *Tracker) LoadKeyspace(conn queryservice.QueryService, target *querypb.Target) error { 94 err := t.loadTables(conn, target) 95 if err != nil { 96 return err 97 } 98 err = t.loadViews(conn, target) 99 if err != nil { 100 return err 101 } 102 103 t.tracked[target.Keyspace].setLoaded(true) 104 return nil 105 } 106 107 func (t *Tracker) loadTables(conn queryservice.QueryService, target *querypb.Target) error { 108 if t.tables == nil { 109 // this can only happen in testing 110 return nil 111 } 112 113 ftRes, err := conn.Execute(t.ctx, target, mysql.FetchTables, nil, 0, 0, nil) 114 if err != nil { 115 return err 116 } 117 t.mu.Lock() 118 defer t.mu.Unlock() 119 120 // We must clear out any previous schema before loading it here as this is called 121 // whenever a shard's primary tablet starts and sends the initial signal. Without 122 // clearing out the previous schema we can end up with duplicate entries when the 123 // tablet is simply restarted or potentially when we elect a new primary. 124 t.clearKeyspaceTables(target.Keyspace) 125 t.updateTables(target.Keyspace, ftRes) 126 log.Infof("finished loading schema for keyspace %s. Found %d columns in total across the tables", target.Keyspace, len(ftRes.Rows)) 127 128 return nil 129 } 130 131 func (t *Tracker) loadViews(conn queryservice.QueryService, target *querypb.Target) error { 132 if t.views == nil { 133 // This happens only when views are not enabled. 134 return nil 135 } 136 137 t.mu.Lock() 138 defer t.mu.Unlock() 139 // We must clear out any previous view definition before loading it here as this is called 140 // whenever a shard's primary tablet starts and sends the initial signal. 141 // This is needed clear out any stale view definitions. 142 t.clearKeyspaceViews(target.Keyspace) 143 144 var numViews int 145 err := conn.GetSchema(t.ctx, target, querypb.SchemaTableType_VIEWS, nil, func(schemaRes *querypb.GetSchemaResponse) error { 146 t.updateViews(target.Keyspace, schemaRes.TableDefinition) 147 numViews += len(schemaRes.TableDefinition) 148 return nil 149 }) 150 if err != nil { 151 return err 152 } 153 log.Infof("finished loading views for keyspace %s. Found %d views", target.Keyspace, numViews) 154 return nil 155 } 156 157 // Start starts the schema tracking. 158 func (t *Tracker) Start() { 159 log.Info("Starting schema tracking") 160 ctx, cancel := context.WithCancel(t.ctx) 161 t.cancel = cancel 162 go func(ctx context.Context, t *Tracker) { 163 for { 164 select { 165 case th := <-t.ch: 166 ksUpdater := t.getKeyspaceUpdateController(th) 167 ksUpdater.add(th) 168 case <-ctx.Done(): 169 // closing of the channel happens outside the scope of the tracker. It is the responsibility of the one who created this tracker. 170 return 171 } 172 } 173 }(ctx, t) 174 } 175 176 // getKeyspaceUpdateController returns the updateController for the given keyspace 177 // the updateController will be created if there was none. 178 func (t *Tracker) getKeyspaceUpdateController(th *discovery.TabletHealth) *updateController { 179 t.mu.Lock() 180 defer t.mu.Unlock() 181 182 ksUpdater, exists := t.tracked[th.Target.Keyspace] 183 if !exists { 184 ksUpdater = t.newUpdateController() 185 t.tracked[th.Target.Keyspace] = ksUpdater 186 } 187 return ksUpdater 188 } 189 190 func (t *Tracker) newUpdateController() *updateController { 191 return &updateController{update: t.updateSchema, reloadKeyspace: t.initKeyspace, signal: t.signal, consumeDelay: t.consumeDelay} 192 } 193 194 func (t *Tracker) initKeyspace(th *discovery.TabletHealth) error { 195 err := t.LoadKeyspace(th.Conn, th.Target) 196 if err != nil { 197 log.Warningf("Unable to add the %s keyspace to the schema tracker: %v", th.Target.Keyspace, err) 198 code := vterrors.Code(err) 199 if code == vtrpcpb.Code_UNAUTHENTICATED || code == vtrpcpb.Code_PERMISSION_DENIED { 200 log.Warning(aclErrorMessageLog) 201 } 202 return err 203 } 204 return nil 205 } 206 207 // Stop stops the schema tracking 208 func (t *Tracker) Stop() { 209 log.Info("Stopping schema tracking") 210 t.cancel() 211 } 212 213 // GetColumns returns the column list for table in the given keyspace. 214 func (t *Tracker) GetColumns(ks string, tbl string) []vindexes.Column { 215 t.mu.Lock() 216 defer t.mu.Unlock() 217 218 return t.tables.get(ks, tbl) 219 } 220 221 // Tables returns a map with the columns for all known tables in the keyspace 222 func (t *Tracker) Tables(ks string) map[string][]vindexes.Column { 223 t.mu.Lock() 224 defer t.mu.Unlock() 225 226 m := t.tables.m[ks] 227 if m == nil { 228 return map[string][]vindexes.Column{} // we know nothing about this KS, so that is the info we can give out 229 } 230 231 return m 232 } 233 234 // Views returns all known views in the keyspace with their definition. 235 func (t *Tracker) Views(ks string) map[string]sqlparser.SelectStatement { 236 t.mu.Lock() 237 defer t.mu.Unlock() 238 239 if t.views == nil { 240 return nil 241 } 242 return t.views.m[ks] 243 } 244 245 func (t *Tracker) updateSchema(th *discovery.TabletHealth) bool { 246 success := true 247 if th.Stats.TableSchemaChanged != nil { 248 success = t.updatedTableSchema(th) 249 } 250 if !success || th.Stats.ViewSchemaChanged == nil { 251 return success 252 } 253 // there is view definition change in the tablet 254 return t.updatedViewSchema(th) 255 } 256 257 func (t *Tracker) updatedTableSchema(th *discovery.TabletHealth) bool { 258 tablesUpdated := th.Stats.TableSchemaChanged 259 tables, err := sqltypes.BuildBindVariable(tablesUpdated) 260 if err != nil { 261 log.Errorf("failed to read updated tables from TabletHealth: %v", err) 262 return false 263 } 264 bv := map[string]*querypb.BindVariable{"tableNames": tables} 265 res, err := th.Conn.Execute(t.ctx, th.Target, mysql.FetchUpdatedTables, bv, 0, 0, nil) 266 if err != nil { 267 t.tracked[th.Target.Keyspace].setLoaded(false) 268 // TODO: optimize for the tables that got errored out. 269 log.Warningf("error fetching new schema for %v, making them non-authoritative: %v", tablesUpdated, err) 270 code := vterrors.Code(err) 271 if code == vtrpcpb.Code_UNAUTHENTICATED || code == vtrpcpb.Code_PERMISSION_DENIED { 272 log.Warning(aclErrorMessageLog) 273 } 274 return false 275 } 276 277 t.mu.Lock() 278 defer t.mu.Unlock() 279 280 // first we empty all prior schema. deleted tables will not show up in the result, 281 // so this is the only chance to delete 282 for _, tbl := range tablesUpdated { 283 t.tables.delete(th.Target.Keyspace, tbl) 284 } 285 t.updateTables(th.Target.Keyspace, res) 286 return true 287 } 288 289 func (t *Tracker) updateTables(keyspace string, res *sqltypes.Result) { 290 for _, row := range res.Rows { 291 tbl := row[0].ToString() 292 colName := row[1].ToString() 293 colType := row[2].ToString() 294 collation := row[3].ToString() 295 296 cType := sqlparser.ColumnType{Type: colType} 297 col := vindexes.Column{Name: sqlparser.NewIdentifierCI(colName), Type: cType.SQLType(), CollationName: collation} 298 cols := t.tables.get(keyspace, tbl) 299 300 t.tables.set(keyspace, tbl, append(cols, col)) 301 } 302 } 303 304 func (t *Tracker) updatedViewSchema(th *discovery.TabletHealth) bool { 305 t.mu.Lock() 306 defer t.mu.Unlock() 307 308 viewsUpdated := th.Stats.ViewSchemaChanged 309 310 // first we empty all prior schema. deleted tables will not show up in the result, 311 // so this is the only chance to delete 312 for _, view := range viewsUpdated { 313 t.views.delete(th.Target.Keyspace, view) 314 } 315 err := th.Conn.GetSchema(t.ctx, th.Target, querypb.SchemaTableType_VIEWS, viewsUpdated, func(schemaRes *querypb.GetSchemaResponse) error { 316 t.updateViews(th.Target.Keyspace, schemaRes.TableDefinition) 317 return nil 318 }) 319 if err != nil { 320 t.tracked[th.Target.Keyspace].setLoaded(false) 321 // TODO: optimize for the views that got errored out. 322 log.Warningf("error fetching new views definition for %v", viewsUpdated, err) 323 return false 324 } 325 return true 326 } 327 328 func (t *Tracker) updateViews(keyspace string, res map[string]string) { 329 for viewName, viewDef := range res { 330 t.views.set(keyspace, viewName, viewDef) 331 } 332 } 333 334 // RegisterSignalReceiver allows a function to register to be called when new schema is available 335 func (t *Tracker) RegisterSignalReceiver(f func()) { 336 t.mu.Lock() 337 defer t.mu.Unlock() 338 for _, controller := range t.tracked { 339 controller.signal = f 340 } 341 t.signal = f 342 } 343 344 // AddNewKeyspace adds keyspace to the tracker. 345 func (t *Tracker) AddNewKeyspace(conn queryservice.QueryService, target *querypb.Target) error { 346 updateController := t.newUpdateController() 347 t.tracked[target.Keyspace] = updateController 348 err := t.LoadKeyspace(conn, target) 349 if err != nil { 350 updateController.setIgnore(checkIfWeShouldIgnoreKeyspace(err)) 351 } 352 return err 353 } 354 355 type tableMap struct { 356 m map[keyspaceStr]map[tableNameStr][]vindexes.Column 357 } 358 359 func (tm *tableMap) set(ks, tbl string, cols []vindexes.Column) { 360 m := tm.m[ks] 361 if m == nil { 362 m = make(map[tableNameStr][]vindexes.Column) 363 tm.m[ks] = m 364 } 365 m[tbl] = cols 366 } 367 368 func (tm *tableMap) get(ks, tbl string) []vindexes.Column { 369 m := tm.m[ks] 370 if m == nil { 371 return nil 372 } 373 return m[tbl] 374 } 375 376 func (tm *tableMap) delete(ks, tbl string) { 377 m := tm.m[ks] 378 if m == nil { 379 return 380 } 381 delete(m, tbl) 382 } 383 384 // This empties out any previous schema for all tables in a keyspace. 385 // You should call this before initializing/loading a keyspace of the same 386 // name in the cache. 387 func (t *Tracker) clearKeyspaceTables(ks string) { 388 if t.tables != nil && t.tables.m != nil { 389 delete(t.tables.m, ks) 390 } 391 } 392 393 type viewMap struct { 394 m map[keyspaceStr]map[viewNameStr]sqlparser.SelectStatement 395 } 396 397 func (vm *viewMap) set(ks, tbl, sql string) { 398 m := vm.m[ks] 399 if m == nil { 400 m = make(map[tableNameStr]sqlparser.SelectStatement) 401 vm.m[ks] = m 402 } 403 stmt, err := sqlparser.Parse(sql) 404 if err != nil { 405 log.Warningf("ignoring view '%s', parsing error in view definition: '%s'", tbl, sql) 406 return 407 } 408 cv, ok := stmt.(*sqlparser.CreateView) 409 if !ok { 410 log.Warningf("ignoring view '%s', view definition is not a create view query: %T", tbl, stmt) 411 return 412 } 413 m[tbl] = cv.Select 414 } 415 416 func (vm *viewMap) get(ks, tbl string) sqlparser.SelectStatement { 417 m := vm.m[ks] 418 if m == nil { 419 return nil 420 } 421 return m[tbl] 422 } 423 424 func (vm *viewMap) delete(ks, tbl string) { 425 m := vm.m[ks] 426 if m == nil { 427 return 428 } 429 delete(m, tbl) 430 } 431 432 func (t *Tracker) clearKeyspaceViews(ks string) { 433 if t.views != nil && t.views.m != nil { 434 delete(t.views.m, ks) 435 } 436 } 437 438 // GetViews returns the view statement for the given keyspace and view name. 439 func (t *Tracker) GetViews(ks string, tbl string) sqlparser.SelectStatement { 440 t.mu.Lock() 441 defer t.mu.Unlock() 442 443 return t.views.get(ks, tbl) 444 }