github.com/pingcap/ticdc@v0.0.0-20220526033649-485a10ef2652/cdc/entry/schema_storage.go (about) 1 // Copyright 2020 PingCAP, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 package entry 15 16 import ( 17 "context" 18 "sort" 19 "sync" 20 "sync/atomic" 21 "time" 22 23 "github.com/pingcap/errors" 24 "github.com/pingcap/log" 25 timodel "github.com/pingcap/parser/model" 26 "github.com/pingcap/ticdc/cdc/model" 27 cerror "github.com/pingcap/ticdc/pkg/errors" 28 "github.com/pingcap/ticdc/pkg/filter" 29 "github.com/pingcap/ticdc/pkg/retry" 30 timeta "github.com/pingcap/tidb/meta" 31 "go.uber.org/zap" 32 "go.uber.org/zap/zapcore" 33 ) 34 35 // schemaSnapshot stores the source TiDB all schema information 36 // schemaSnapshot is a READ ONLY struct 37 type schemaSnapshot struct { 38 tableNameToID map[model.TableName]int64 39 schemaNameToID map[string]int64 40 41 schemas map[int64]*timodel.DBInfo 42 tables map[int64]*model.TableInfo 43 partitionTable map[int64]*model.TableInfo 44 45 // key is schemaID and value is tableIDs 46 tableInSchema map[int64][]int64 47 48 truncateTableID map[int64]struct{} 49 ineligibleTableID map[int64]struct{} 50 51 currentTs uint64 52 53 // if explicit is true, treat tables without explicit row id as eligible 54 explicitTables bool 55 } 56 57 // SingleSchemaSnapshot is a single schema snapshot independent of schema storage 58 type SingleSchemaSnapshot = schemaSnapshot 59 60 // HandleDDL handles the ddl job 61 func (s *SingleSchemaSnapshot) HandleDDL(job *timodel.Job) error { 62 return s.handleDDL(job) 63 } 64 65 // PreTableInfo returns the table info which will be overwritten by the specified job 66 func (s *SingleSchemaSnapshot) PreTableInfo(job *timodel.Job) (*model.TableInfo, error) { 67 switch job.Type { 68 case timodel.ActionCreateSchema, timodel.ActionModifySchemaCharsetAndCollate, timodel.ActionDropSchema: 69 return nil, nil 70 case timodel.ActionCreateTable, timodel.ActionCreateView, timodel.ActionRecoverTable: 71 // no pre table info 72 return nil, nil 73 case timodel.ActionRenameTable, timodel.ActionDropTable, timodel.ActionDropView, timodel.ActionTruncateTable: 74 // get the table will be dropped 75 table, ok := s.TableByID(job.TableID) 76 if !ok { 77 return nil, cerror.ErrSchemaStorageTableMiss.GenWithStackByArgs(job.TableID) 78 } 79 return table, nil 80 default: 81 binlogInfo := job.BinlogInfo 82 if binlogInfo == nil { 83 log.Warn("ignore a invalid DDL job", zap.Reflect("job", job)) 84 return nil, nil 85 } 86 tbInfo := binlogInfo.TableInfo 87 if tbInfo == nil { 88 log.Warn("ignore a invalid DDL job", zap.Reflect("job", job)) 89 return nil, nil 90 } 91 tableID := tbInfo.ID 92 table, ok := s.TableByID(tableID) 93 if !ok { 94 return nil, cerror.ErrSchemaStorageTableMiss.GenWithStackByArgs(job.TableID) 95 } 96 return table, nil 97 } 98 } 99 100 // NewSingleSchemaSnapshotFromMeta creates a new single schema snapshot from a tidb meta 101 func NewSingleSchemaSnapshotFromMeta(meta *timeta.Meta, currentTs uint64, explicitTables bool) (*SingleSchemaSnapshot, error) { 102 // meta is nil only in unit tests 103 if meta == nil { 104 snap := newEmptySchemaSnapshot(explicitTables) 105 snap.currentTs = currentTs 106 return snap, nil 107 } 108 return newSchemaSnapshotFromMeta(meta, currentTs, explicitTables) 109 } 110 111 func newEmptySchemaSnapshot(explicitTables bool) *schemaSnapshot { 112 return &schemaSnapshot{ 113 tableNameToID: make(map[model.TableName]int64), 114 schemaNameToID: make(map[string]int64), 115 116 schemas: make(map[int64]*timodel.DBInfo), 117 tables: make(map[int64]*model.TableInfo), 118 partitionTable: make(map[int64]*model.TableInfo), 119 120 tableInSchema: make(map[int64][]int64), 121 truncateTableID: make(map[int64]struct{}), 122 ineligibleTableID: make(map[int64]struct{}), 123 124 explicitTables: explicitTables, 125 } 126 } 127 128 func newSchemaSnapshotFromMeta(meta *timeta.Meta, currentTs uint64, explicitTables bool) (*schemaSnapshot, error) { 129 snap := newEmptySchemaSnapshot(explicitTables) 130 dbinfos, err := meta.ListDatabases() 131 if err != nil { 132 return nil, cerror.WrapError(cerror.ErrMetaListDatabases, err) 133 } 134 for _, dbinfo := range dbinfos { 135 snap.schemas[dbinfo.ID] = dbinfo 136 snap.schemaNameToID[dbinfo.Name.O] = dbinfo.ID 137 } 138 for schemaID, dbinfo := range snap.schemas { 139 tableInfos, err := meta.ListTables(schemaID) 140 if err != nil { 141 return nil, cerror.WrapError(cerror.ErrMetaListDatabases, err) 142 } 143 snap.tableInSchema[schemaID] = make([]int64, 0, len(tableInfos)) 144 for _, tableInfo := range tableInfos { 145 snap.tableInSchema[schemaID] = append(snap.tableInSchema[schemaID], tableInfo.ID) 146 tableInfo := model.WrapTableInfo(dbinfo.ID, dbinfo.Name.O, currentTs, tableInfo) 147 snap.tables[tableInfo.ID] = tableInfo 148 snap.tableNameToID[model.TableName{Schema: dbinfo.Name.O, Table: tableInfo.Name.O}] = tableInfo.ID 149 isEligible := tableInfo.IsEligible(explicitTables) 150 if !isEligible { 151 snap.ineligibleTableID[tableInfo.ID] = struct{}{} 152 } 153 if pi := tableInfo.GetPartitionInfo(); pi != nil { 154 for _, partition := range pi.Definitions { 155 snap.partitionTable[partition.ID] = tableInfo 156 if !isEligible { 157 snap.ineligibleTableID[partition.ID] = struct{}{} 158 } 159 } 160 } 161 } 162 } 163 snap.currentTs = currentTs 164 return snap, nil 165 } 166 167 func (s *schemaSnapshot) PrintStatus(logger func(msg string, fields ...zap.Field)) { 168 logger("[SchemaSnap] Start to print status", zap.Uint64("currentTs", s.currentTs)) 169 for id, dbInfo := range s.schemas { 170 logger("[SchemaSnap] --> Schemas", zap.Int64("schemaID", id), zap.Reflect("dbInfo", dbInfo)) 171 // check schemaNameToID 172 if schemaID, exist := s.schemaNameToID[dbInfo.Name.O]; !exist || schemaID != id { 173 logger("[SchemaSnap] ----> schemaNameToID item lost", zap.String("name", dbInfo.Name.O), zap.Int64("schemaNameToID", s.schemaNameToID[dbInfo.Name.O])) 174 } 175 } 176 if len(s.schemaNameToID) != len(s.schemas) { 177 logger("[SchemaSnap] schemaNameToID length mismatch schemas") 178 for schemaName, schemaID := range s.schemaNameToID { 179 logger("[SchemaSnap] --> schemaNameToID", zap.String("schemaName", schemaName), zap.Int64("schemaID", schemaID)) 180 } 181 } 182 for id, tableInfo := range s.tables { 183 logger("[SchemaSnap] --> Tables", zap.Int64("tableID", id), zap.Stringer("tableInfo", tableInfo)) 184 // check tableNameToID 185 if tableID, exist := s.tableNameToID[tableInfo.TableName]; !exist || tableID != id { 186 logger("[SchemaSnap] ----> tableNameToID item lost", zap.Stringer("name", tableInfo.TableName), zap.Int64("tableNameToID", s.tableNameToID[tableInfo.TableName])) 187 } 188 } 189 if len(s.tableNameToID) != len(s.tables) { 190 logger("[SchemaSnap] tableNameToID length mismatch tables") 191 for tableName, tableID := range s.tableNameToID { 192 logger("[SchemaSnap] --> tableNameToID", zap.Stringer("tableName", tableName), zap.Int64("tableID", tableID)) 193 } 194 } 195 for pid, table := range s.partitionTable { 196 logger("[SchemaSnap] --> Partitions", zap.Int64("partitionID", pid), zap.Int64("tableID", table.ID)) 197 } 198 truncateTableID := make([]int64, 0, len(s.truncateTableID)) 199 for id := range s.truncateTableID { 200 truncateTableID = append(truncateTableID, id) 201 } 202 logger("[SchemaSnap] TruncateTableIDs", zap.Int64s("ids", truncateTableID)) 203 204 ineligibleTableID := make([]int64, 0, len(s.ineligibleTableID)) 205 for id := range s.ineligibleTableID { 206 ineligibleTableID = append(ineligibleTableID, id) 207 } 208 logger("[SchemaSnap] IneligibleTableIDs", zap.Int64s("ids", ineligibleTableID)) 209 } 210 211 // Clone clones Storage 212 func (s *schemaSnapshot) Clone() *schemaSnapshot { 213 clone := *s 214 215 tableNameToID := make(map[model.TableName]int64, len(s.tableNameToID)) 216 for k, v := range s.tableNameToID { 217 tableNameToID[k] = v 218 } 219 clone.tableNameToID = tableNameToID 220 221 schemaNameToID := make(map[string]int64, len(s.schemaNameToID)) 222 for k, v := range s.schemaNameToID { 223 schemaNameToID[k] = v 224 } 225 clone.schemaNameToID = schemaNameToID 226 227 schemas := make(map[int64]*timodel.DBInfo, len(s.schemas)) 228 for k, v := range s.schemas { 229 schemas[k] = v.Clone() 230 } 231 clone.schemas = schemas 232 233 tables := make(map[int64]*model.TableInfo, len(s.tables)) 234 for k, v := range s.tables { 235 tables[k] = v 236 } 237 clone.tables = tables 238 239 tableInSchema := make(map[int64][]int64, len(s.tableInSchema)) 240 for k, v := range s.tableInSchema { 241 cloneV := make([]int64, len(v)) 242 copy(cloneV, v) 243 tableInSchema[k] = cloneV 244 } 245 clone.tableInSchema = tableInSchema 246 247 partitionTable := make(map[int64]*model.TableInfo, len(s.partitionTable)) 248 for k, v := range s.partitionTable { 249 partitionTable[k] = v 250 } 251 clone.partitionTable = partitionTable 252 253 truncateTableID := make(map[int64]struct{}, len(s.truncateTableID)) 254 for k, v := range s.truncateTableID { 255 truncateTableID[k] = v 256 } 257 clone.truncateTableID = truncateTableID 258 259 ineligibleTableID := make(map[int64]struct{}, len(s.ineligibleTableID)) 260 for k, v := range s.ineligibleTableID { 261 ineligibleTableID[k] = v 262 } 263 clone.ineligibleTableID = ineligibleTableID 264 265 return &clone 266 } 267 268 // GetTableNameByID looks up a TableName with the given table id 269 func (s *schemaSnapshot) GetTableNameByID(id int64) (model.TableName, bool) { 270 tableInfo, ok := s.tables[id] 271 if !ok { 272 // Try partition, it could be a partition table. 273 partInfo, ok := s.partitionTable[id] 274 if !ok { 275 return model.TableName{}, false 276 } 277 // Must exists an table that contains the partition. 278 tableInfo = s.tables[partInfo.ID] 279 } 280 return tableInfo.TableName, true 281 } 282 283 // GetTableIDByName returns the tableID by table schemaName and tableName 284 func (s *schemaSnapshot) GetTableIDByName(schemaName string, tableName string) (int64, bool) { 285 id, ok := s.tableNameToID[model.TableName{ 286 Schema: schemaName, 287 Table: tableName, 288 }] 289 return id, ok 290 } 291 292 // GetTableByName queries a table by name, 293 // the second returned value is false if no table with the specified name is found. 294 func (s *schemaSnapshot) GetTableByName(schema, table string) (info *model.TableInfo, ok bool) { 295 id, ok := s.GetTableIDByName(schema, table) 296 if !ok { 297 return nil, ok 298 } 299 return s.TableByID(id) 300 } 301 302 // SchemaByID returns the DBInfo by schema id 303 func (s *schemaSnapshot) SchemaByID(id int64) (val *timodel.DBInfo, ok bool) { 304 val, ok = s.schemas[id] 305 return 306 } 307 308 // SchemaByTableID returns the schema ID by table ID 309 func (s *schemaSnapshot) SchemaByTableID(tableID int64) (*timodel.DBInfo, bool) { 310 tableInfo, ok := s.tables[tableID] 311 if !ok { 312 return nil, false 313 } 314 schemaID, ok := s.schemaNameToID[tableInfo.TableName.Schema] 315 if !ok { 316 return nil, false 317 } 318 return s.SchemaByID(schemaID) 319 } 320 321 // TableByID returns the TableInfo by table id 322 func (s *schemaSnapshot) TableByID(id int64) (val *model.TableInfo, ok bool) { 323 val, ok = s.tables[id] 324 return 325 } 326 327 // PhysicalTableByID returns the TableInfo by table id or partition ID. 328 func (s *schemaSnapshot) PhysicalTableByID(id int64) (val *model.TableInfo, ok bool) { 329 val, ok = s.tables[id] 330 if !ok { 331 val, ok = s.partitionTable[id] 332 } 333 return 334 } 335 336 // IsTruncateTableID returns true if the table id have been truncated by truncate table DDL 337 func (s *schemaSnapshot) IsTruncateTableID(id int64) bool { 338 _, ok := s.truncateTableID[id] 339 return ok 340 } 341 342 // IsIneligibleTableID returns true if the table is ineligible 343 func (s *schemaSnapshot) IsIneligibleTableID(id int64) bool { 344 _, ok := s.ineligibleTableID[id] 345 return ok 346 } 347 348 // FillSchemaName fills the schema name in ddl job 349 func (s *schemaSnapshot) FillSchemaName(job *timodel.Job) error { 350 if job.Type == timodel.ActionCreateSchema || 351 job.Type == timodel.ActionDropSchema { 352 job.SchemaName = job.BinlogInfo.DBInfo.Name.O 353 return nil 354 } 355 dbInfo, exist := s.SchemaByID(job.SchemaID) 356 if !exist { 357 return cerror.ErrSnapshotSchemaNotFound.GenWithStackByArgs(job.SchemaID) 358 } 359 job.SchemaName = dbInfo.Name.O 360 return nil 361 } 362 363 func (s *schemaSnapshot) dropSchema(id int64) error { 364 schema, ok := s.schemas[id] 365 if !ok { 366 return cerror.ErrSnapshotSchemaNotFound.GenWithStackByArgs(id) 367 } 368 369 for _, tableID := range s.tableInSchema[id] { 370 tableName := s.tables[tableID].TableName 371 if pi := s.tables[tableID].GetPartitionInfo(); pi != nil { 372 for _, partition := range pi.Definitions { 373 delete(s.partitionTable, partition.ID) 374 } 375 } 376 delete(s.tables, tableID) 377 delete(s.tableNameToID, tableName) 378 } 379 380 delete(s.schemas, id) 381 delete(s.tableInSchema, id) 382 delete(s.schemaNameToID, schema.Name.O) 383 384 return nil 385 } 386 387 func (s *schemaSnapshot) createSchema(db *timodel.DBInfo) error { 388 if _, ok := s.schemas[db.ID]; ok { 389 return cerror.ErrSnapshotSchemaExists.GenWithStackByArgs(db.Name, db.ID) 390 } 391 392 s.schemas[db.ID] = db.Clone() 393 s.schemaNameToID[db.Name.O] = db.ID 394 s.tableInSchema[db.ID] = []int64{} 395 396 log.Debug("create schema success, schema id", zap.String("name", db.Name.O), zap.Int64("id", db.ID)) 397 return nil 398 } 399 400 func (s *schemaSnapshot) replaceSchema(db *timodel.DBInfo) error { 401 _, ok := s.schemas[db.ID] 402 if !ok { 403 return cerror.ErrSnapshotSchemaNotFound.GenWithStack("schema %s(%d) not found", db.Name, db.ID) 404 } 405 s.schemas[db.ID] = db.Clone() 406 s.schemaNameToID[db.Name.O] = db.ID 407 return nil 408 } 409 410 func (s *schemaSnapshot) dropTable(id int64) error { 411 table, ok := s.tables[id] 412 if !ok { 413 return cerror.ErrSnapshotTableNotFound.GenWithStackByArgs(id) 414 } 415 tableInSchema, ok := s.tableInSchema[table.SchemaID] 416 if !ok { 417 return cerror.ErrSnapshotSchemaNotFound.GenWithStack("table(%d)'s schema", id) 418 } 419 420 for i, tableID := range tableInSchema { 421 if tableID == id { 422 copy(tableInSchema[i:], tableInSchema[i+1:]) 423 s.tableInSchema[table.SchemaID] = tableInSchema[:len(tableInSchema)-1] 424 break 425 } 426 } 427 428 tableName := s.tables[id].TableName 429 delete(s.tables, id) 430 if pi := table.GetPartitionInfo(); pi != nil { 431 for _, partition := range pi.Definitions { 432 delete(s.partitionTable, partition.ID) 433 delete(s.ineligibleTableID, partition.ID) 434 } 435 } 436 delete(s.tableNameToID, tableName) 437 delete(s.ineligibleTableID, id) 438 439 log.Debug("drop table success", zap.String("name", table.Name.O), zap.Int64("id", id)) 440 return nil 441 } 442 443 func (s *schemaSnapshot) updatePartition(tbl *model.TableInfo) error { 444 id := tbl.ID 445 table, ok := s.tables[id] 446 if !ok { 447 return cerror.ErrSnapshotTableNotFound.GenWithStackByArgs(id) 448 } 449 oldPi := table.GetPartitionInfo() 450 if oldPi == nil { 451 return cerror.ErrSnapshotTableNotFound.GenWithStack("table %d is not a partition table", id) 452 } 453 oldIDs := make(map[int64]struct{}, len(oldPi.Definitions)) 454 for _, p := range oldPi.Definitions { 455 oldIDs[p.ID] = struct{}{} 456 } 457 458 newPi := tbl.GetPartitionInfo() 459 if newPi == nil { 460 return cerror.ErrSnapshotTableNotFound.GenWithStack("table %d is not a partition table", id) 461 } 462 s.tables[id] = tbl 463 for _, partition := range newPi.Definitions { 464 // update table info. 465 if _, ok := s.partitionTable[partition.ID]; ok { 466 log.Debug("add table partition success", zap.String("name", tbl.Name.O), zap.Int64("tid", id), zap.Reflect("add partition id", partition.ID)) 467 } 468 s.partitionTable[partition.ID] = tbl 469 if !tbl.ExistTableUniqueColumn() { 470 s.ineligibleTableID[partition.ID] = struct{}{} 471 } 472 delete(oldIDs, partition.ID) 473 } 474 475 // drop old partition. 476 for pid := range oldIDs { 477 s.truncateTableID[pid] = struct{}{} 478 delete(s.partitionTable, pid) 479 delete(s.ineligibleTableID, pid) 480 log.Debug("drop table partition success", zap.String("name", tbl.Name.O), zap.Int64("tid", id), zap.Reflect("truncated partition id", pid)) 481 } 482 483 return nil 484 } 485 486 func (s *schemaSnapshot) createTable(table *model.TableInfo) error { 487 schema, ok := s.schemas[table.SchemaID] 488 if !ok { 489 return cerror.ErrSnapshotSchemaNotFound.GenWithStack("table's schema(%d)", table.SchemaID) 490 } 491 tableInSchema, ok := s.tableInSchema[table.SchemaID] 492 if !ok { 493 return cerror.ErrSnapshotSchemaNotFound.GenWithStack("table's schema(%d)", table.SchemaID) 494 } 495 _, ok = s.tables[table.ID] 496 if ok { 497 return cerror.ErrSnapshotTableExists.GenWithStackByArgs(schema.Name, table.Name) 498 } 499 tableInSchema = append(tableInSchema, table.ID) 500 s.tableInSchema[table.SchemaID] = tableInSchema 501 502 s.tables[table.ID] = table 503 if !table.IsEligible(s.explicitTables) { 504 log.Warn("this table is not eligible to replicate", zap.String("tableName", table.Name.O), zap.Int64("tableID", table.ID)) 505 s.ineligibleTableID[table.ID] = struct{}{} 506 } 507 if pi := table.GetPartitionInfo(); pi != nil { 508 for _, partition := range pi.Definitions { 509 s.partitionTable[partition.ID] = table 510 if !table.IsEligible(s.explicitTables) { 511 s.ineligibleTableID[partition.ID] = struct{}{} 512 } 513 } 514 } 515 s.tableNameToID[table.TableName] = table.ID 516 517 log.Debug("create table success", zap.String("name", schema.Name.O+"."+table.Name.O), zap.Int64("id", table.ID)) 518 return nil 519 } 520 521 // ReplaceTable replace the table by new tableInfo 522 func (s *schemaSnapshot) replaceTable(table *model.TableInfo) error { 523 _, ok := s.tables[table.ID] 524 if !ok { 525 return cerror.ErrSnapshotTableNotFound.GenWithStack("table %s(%d)", table.Name, table.ID) 526 } 527 s.tables[table.ID] = table 528 if !table.IsEligible(s.explicitTables) { 529 log.Warn("this table is not eligible to replicate", zap.String("tableName", table.Name.O), zap.Int64("tableID", table.ID)) 530 s.ineligibleTableID[table.ID] = struct{}{} 531 } 532 if pi := table.GetPartitionInfo(); pi != nil { 533 for _, partition := range pi.Definitions { 534 s.partitionTable[partition.ID] = table 535 if !table.IsEligible(s.explicitTables) { 536 s.ineligibleTableID[partition.ID] = struct{}{} 537 } 538 } 539 } 540 541 return nil 542 } 543 544 func (s *schemaSnapshot) handleDDL(job *timodel.Job) error { 545 if err := s.FillSchemaName(job); err != nil { 546 return errors.Trace(err) 547 } 548 log.Debug("handle job: ", zap.String("sql query", job.Query), zap.Stringer("job", job)) 549 getWrapTableInfo := func(job *timodel.Job) *model.TableInfo { 550 return model.WrapTableInfo(job.SchemaID, job.SchemaName, 551 job.BinlogInfo.FinishedTS, 552 job.BinlogInfo.TableInfo) 553 } 554 switch job.Type { 555 case timodel.ActionCreateSchema: 556 // get the DBInfo from job rawArgs 557 err := s.createSchema(job.BinlogInfo.DBInfo) 558 if err != nil { 559 return errors.Trace(err) 560 } 561 case timodel.ActionModifySchemaCharsetAndCollate: 562 err := s.replaceSchema(job.BinlogInfo.DBInfo) 563 if err != nil { 564 return errors.Trace(err) 565 } 566 case timodel.ActionDropSchema: 567 err := s.dropSchema(job.SchemaID) 568 if err != nil { 569 return errors.Trace(err) 570 } 571 case timodel.ActionRenameTable: 572 // first drop the table 573 err := s.dropTable(job.TableID) 574 if err != nil { 575 return errors.Trace(err) 576 } 577 // create table 578 err = s.createTable(getWrapTableInfo(job)) 579 if err != nil { 580 return errors.Trace(err) 581 } 582 case timodel.ActionCreateTable, timodel.ActionCreateView, timodel.ActionRecoverTable: 583 err := s.createTable(getWrapTableInfo(job)) 584 if err != nil { 585 return errors.Trace(err) 586 } 587 case timodel.ActionDropTable, timodel.ActionDropView: 588 err := s.dropTable(job.TableID) 589 if err != nil { 590 return errors.Trace(err) 591 } 592 593 case timodel.ActionTruncateTable: 594 // job.TableID is the old table id, different from table.ID 595 err := s.dropTable(job.TableID) 596 if err != nil { 597 return errors.Trace(err) 598 } 599 600 err = s.createTable(getWrapTableInfo(job)) 601 if err != nil { 602 return errors.Trace(err) 603 } 604 605 s.truncateTableID[job.TableID] = struct{}{} 606 case timodel.ActionTruncateTablePartition, timodel.ActionAddTablePartition, timodel.ActionDropTablePartition: 607 err := s.updatePartition(getWrapTableInfo(job)) 608 if err != nil { 609 return errors.Trace(err) 610 } 611 default: 612 binlogInfo := job.BinlogInfo 613 if binlogInfo == nil { 614 log.Warn("ignore a invalid DDL job", zap.Reflect("job", job)) 615 return nil 616 } 617 tbInfo := binlogInfo.TableInfo 618 if tbInfo == nil { 619 log.Warn("ignore a invalid DDL job", zap.Reflect("job", job)) 620 return nil 621 } 622 err := s.replaceTable(getWrapTableInfo(job)) 623 if err != nil { 624 return errors.Trace(err) 625 } 626 } 627 s.currentTs = job.BinlogInfo.FinishedTS 628 return nil 629 } 630 631 // CloneTables return a clone of the existing tables. 632 func (s *schemaSnapshot) CloneTables() map[model.TableID]model.TableName { 633 mp := make(map[model.TableID]model.TableName, len(s.tables)) 634 635 for id, table := range s.tables { 636 mp[id] = table.TableName 637 } 638 639 return mp 640 } 641 642 // Tables return a map between table id and table info 643 // the returned map must be READ-ONLY. Any modified of this map will lead to the internal state confusion in schema storage 644 func (s *schemaSnapshot) Tables() map[model.TableID]*model.TableInfo { 645 return s.tables 646 } 647 648 // SchemaStorage stores the schema information with multi-version 649 type SchemaStorage interface { 650 // GetSnapshot returns the snapshot which of ts is specified 651 GetSnapshot(ctx context.Context, ts uint64) (*schemaSnapshot, error) 652 // GetLastSnapshot returns the last snapshot 653 GetLastSnapshot() *schemaSnapshot 654 // HandleDDLJob creates a new snapshot in storage and handles the ddl job 655 HandleDDLJob(job *timodel.Job) error 656 // AdvanceResolvedTs advances the resolved 657 AdvanceResolvedTs(ts uint64) 658 // ResolvedTs returns the resolved ts of the schema storage 659 ResolvedTs() uint64 660 // DoGC removes snaps which of ts less than this specified ts 661 DoGC(ts uint64) 662 } 663 664 type schemaStorageImpl struct { 665 snaps []*schemaSnapshot 666 snapsMu sync.RWMutex 667 gcTs uint64 668 resolvedTs uint64 669 670 filter *filter.Filter 671 explicitTables bool 672 } 673 674 // NewSchemaStorage creates a new schema storage 675 func NewSchemaStorage(meta *timeta.Meta, startTs uint64, filter *filter.Filter, forceReplicate bool) (SchemaStorage, error) { 676 var snap *schemaSnapshot 677 var err error 678 if meta == nil { 679 snap = newEmptySchemaSnapshot(forceReplicate) 680 } else { 681 snap, err = newSchemaSnapshotFromMeta(meta, startTs, forceReplicate) 682 } 683 if err != nil { 684 return nil, errors.Trace(err) 685 } 686 schema := &schemaStorageImpl{ 687 snaps: []*schemaSnapshot{snap}, 688 resolvedTs: startTs, 689 filter: filter, 690 explicitTables: forceReplicate, 691 } 692 return schema, nil 693 } 694 695 func (s *schemaStorageImpl) getSnapshot(ts uint64) (*schemaSnapshot, error) { 696 gcTs := atomic.LoadUint64(&s.gcTs) 697 if ts < gcTs { 698 return nil, cerror.ErrSchemaStorageGCed.GenWithStackByArgs(ts, gcTs) 699 } 700 resolvedTs := atomic.LoadUint64(&s.resolvedTs) 701 if ts > resolvedTs { 702 return nil, cerror.ErrSchemaStorageUnresolved.GenWithStackByArgs(ts, resolvedTs) 703 } 704 s.snapsMu.RLock() 705 defer s.snapsMu.RUnlock() 706 i := sort.Search(len(s.snaps), func(i int) bool { 707 return s.snaps[i].currentTs > ts 708 }) 709 if i <= 0 { 710 return nil, cerror.ErrSchemaSnapshotNotFound.GenWithStackByArgs(ts) 711 } 712 return s.snaps[i-1], nil 713 } 714 715 // GetSnapshot returns the snapshot which of ts is specified 716 func (s *schemaStorageImpl) GetSnapshot(ctx context.Context, ts uint64) (*schemaSnapshot, error) { 717 var snap *schemaSnapshot 718 719 // The infinite retry here is a temporary solution to the `ErrSchemaStorageUnresolved` caused by 720 // DDL puller lagging too much. 721 startTime := time.Now() 722 err := retry.Do(ctx, func() error { 723 var err error 724 snap, err = s.getSnapshot(ts) 725 if time.Since(startTime) >= 5*time.Minute && isRetryable(err) { 726 log.Warn("GetSnapshot is taking too long, DDL puller stuck?", zap.Uint64("ts", ts)) 727 } 728 return err 729 }, retry.WithBackoffBaseDelay(10), retry.WithInfiniteTries(), retry.WithIsRetryableErr(isRetryable)) 730 731 return snap, err 732 } 733 734 func isRetryable(err error) bool { 735 return cerror.IsRetryableError(err) && cerror.ErrSchemaStorageUnresolved.Equal(err) 736 } 737 738 // GetLastSnapshot returns the last snapshot 739 func (s *schemaStorageImpl) GetLastSnapshot() *schemaSnapshot { 740 s.snapsMu.RLock() 741 defer s.snapsMu.RUnlock() 742 return s.snaps[len(s.snaps)-1] 743 } 744 745 // HandleDDLJob creates a new snapshot in storage and handles the ddl job 746 func (s *schemaStorageImpl) HandleDDLJob(job *timodel.Job) error { 747 if s.skipJob(job) { 748 s.AdvanceResolvedTs(job.BinlogInfo.FinishedTS) 749 return nil 750 } 751 s.snapsMu.Lock() 752 defer s.snapsMu.Unlock() 753 var snap *schemaSnapshot 754 if len(s.snaps) > 0 { 755 lastSnap := s.snaps[len(s.snaps)-1] 756 if job.BinlogInfo.FinishedTS <= lastSnap.currentTs { 757 log.Debug("ignore foregone DDL job", zap.Reflect("job", job)) 758 return nil 759 } 760 snap = lastSnap.Clone() 761 } else { 762 snap = newEmptySchemaSnapshot(s.explicitTables) 763 } 764 if err := snap.handleDDL(job); err != nil { 765 return errors.Trace(err) 766 } 767 s.snaps = append(s.snaps, snap) 768 s.AdvanceResolvedTs(job.BinlogInfo.FinishedTS) 769 return nil 770 } 771 772 // AdvanceResolvedTs advances the resolved 773 func (s *schemaStorageImpl) AdvanceResolvedTs(ts uint64) { 774 var swapped bool 775 for !swapped { 776 oldResolvedTs := atomic.LoadUint64(&s.resolvedTs) 777 if ts < oldResolvedTs { 778 return 779 } 780 swapped = atomic.CompareAndSwapUint64(&s.resolvedTs, oldResolvedTs, ts) 781 } 782 } 783 784 // ResolvedTs returns the resolved ts of the schema storage 785 func (s *schemaStorageImpl) ResolvedTs() uint64 { 786 return atomic.LoadUint64(&s.resolvedTs) 787 } 788 789 // DoGC removes snaps which of ts less than this specified ts 790 func (s *schemaStorageImpl) DoGC(ts uint64) { 791 s.snapsMu.Lock() 792 defer s.snapsMu.Unlock() 793 var startIdx int 794 for i, snap := range s.snaps { 795 if snap.currentTs > ts { 796 break 797 } 798 startIdx = i 799 } 800 if startIdx == 0 { 801 return 802 } 803 if log.GetLevel() == zapcore.DebugLevel { 804 log.Debug("Do GC in schema storage") 805 for i := 0; i < startIdx; i++ { 806 s.snaps[i].PrintStatus(log.Debug) 807 } 808 } 809 s.snaps = s.snaps[startIdx:] 810 atomic.StoreUint64(&s.gcTs, s.snaps[0].currentTs) 811 log.Info("finished gc in schema storage", zap.Uint64("gcTs", s.snaps[0].currentTs)) 812 } 813 814 // SkipJob skip the job should not be executed 815 // TiDB write DDL Binlog for every DDL Job, we must ignore jobs that are cancelled or rollback 816 // For older version TiDB, it write DDL Binlog in the txn that the state of job is changed to *synced* 817 // Now, it write DDL Binlog in the txn that the state of job is changed to *done* (before change to *synced*) 818 // At state *done*, it will be always and only changed to *synced*. 819 func (s *schemaStorageImpl) skipJob(job *timodel.Job) bool { 820 if s.filter != nil && s.filter.ShouldDiscardDDL(job.Type) { 821 log.Info("discard the ddl job", zap.Int64("jobID", job.ID), zap.String("query", job.Query)) 822 return true 823 } 824 return !job.IsSynced() && !job.IsDone() 825 }