github.com/whtcorpsinc/MilevaDB-Prod@v0.0.0-20211104133533-f57f4be3b597/dbs/dbs_worker.go (about) 1 // Copyright 2020 WHTCORPS INC, 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 dbs 15 16 import ( 17 "context" 18 "fmt" 19 "sync" 20 "sync/atomic" 21 "time" 22 23 "github.com/whtcorpsinc/BerolinaSQL/perceptron" 24 "github.com/whtcorpsinc/BerolinaSQL/terror" 25 "github.com/whtcorpsinc/errors" 26 "github.com/whtcorpsinc/failpoint" 27 pumpcli "github.com/whtcorpsinc/milevadb-tools/milevadb-binlog/pump_client" 28 "github.com/whtcorpsinc/milevadb/dbs/soliton" 29 "github.com/whtcorpsinc/milevadb/ekv" 30 "github.com/whtcorpsinc/milevadb/metrics" 31 milevadbutil "github.com/whtcorpsinc/milevadb/soliton" 32 "github.com/whtcorpsinc/milevadb/soliton/admin" 33 "github.com/whtcorpsinc/milevadb/soliton/logutil" 34 "github.com/whtcorpsinc/milevadb/spacetime" 35 "github.com/whtcorpsinc/milevadb/stochastikctx" 36 "github.com/whtcorpsinc/milevadb/stochastikctx/binloginfo" 37 "github.com/whtcorpsinc/milevadb/stochastikctx/variable" 38 "go.uber.org/zap" 39 ) 40 41 var ( 42 // RunWorker indicates if this MilevaDB server starts DBS worker and can run DBS job. 43 RunWorker = true 44 // dbsWorkerID is used for generating the next DBS worker ID. 45 dbsWorkerID = int32(0) 46 // WaitTimeWhenErrorOccurred is waiting interval when processing DBS jobs encounter errors. 47 WaitTimeWhenErrorOccurred = int64(1 * time.Second) 48 ) 49 50 // GetWaitTimeWhenErrorOccurred return waiting interval when processing DBS jobs encounter errors. 51 func GetWaitTimeWhenErrorOccurred() time.Duration { 52 return time.Duration(atomic.LoadInt64(&WaitTimeWhenErrorOccurred)) 53 } 54 55 // SetWaitTimeWhenErrorOccurred uFIDelate waiting interval when processing DBS jobs encounter errors. 56 func SetWaitTimeWhenErrorOccurred(dur time.Duration) { 57 atomic.StoreInt64(&WaitTimeWhenErrorOccurred, int64(dur)) 58 } 59 60 type workerType byte 61 62 const ( 63 // generalWorker is the worker who handles all DBS memexs except “add index”. 64 generalWorker workerType = 0 65 // addIdxWorker is the worker who handles the operation of adding indexes. 66 addIdxWorker workerType = 1 67 // waitDependencyJobInterval is the interval when the dependency job doesn't be done. 68 waitDependencyJobInterval = 200 * time.Millisecond 69 // noneDependencyJob means a job has no dependency-job. 70 noneDependencyJob = 0 71 ) 72 73 // worker is used for handling DBS jobs. 74 // Now we have two HoTTs of workers. 75 type worker struct { 76 id int32 77 tp workerType 78 dbsJobCh chan struct{} 79 ctx context.Context 80 wg sync.WaitGroup 81 82 sessPool *stochastikPool // sessPool is used to new stochastik to execute ALLEGROALLEGROSQL in dbs package. 83 reorgCtx *reorgCtx // reorgCtx is used for reorganization. 84 delRangeManager delRangeManager 85 logCtx context.Context 86 } 87 88 func newWorker(ctx context.Context, tp workerType, sessPool *stochastikPool, delRangeMgr delRangeManager) *worker { 89 worker := &worker{ 90 id: atomic.AddInt32(&dbsWorkerID, 1), 91 tp: tp, 92 dbsJobCh: make(chan struct{}, 1), 93 ctx: ctx, 94 reorgCtx: &reorgCtx{notifyCancelReorgJob: 0}, 95 sessPool: sessPool, 96 delRangeManager: delRangeMgr, 97 } 98 99 worker.logCtx = logutil.WithKeyValue(context.Background(), "worker", worker.String()) 100 return worker 101 } 102 103 func (w *worker) typeStr() string { 104 var str string 105 switch w.tp { 106 case generalWorker: 107 str = "general" 108 case addIdxWorker: 109 str = perceptron.AddIndexStr 110 default: 111 str = "unknown" 112 } 113 return str 114 } 115 116 func (w *worker) String() string { 117 return fmt.Sprintf("worker %d, tp %s", w.id, w.typeStr()) 118 } 119 120 func (w *worker) close() { 121 startTime := time.Now() 122 w.wg.Wait() 123 logutil.Logger(w.logCtx).Info("[dbs] DBS worker closed", zap.Duration("take time", time.Since(startTime))) 124 } 125 126 // start is used for async online schemaReplicant changing, it will try to become the tenant firstly, 127 // then wait or pull the job queue to handle a schemaReplicant change job. 128 func (w *worker) start(d *dbsCtx) { 129 logutil.Logger(w.logCtx).Info("[dbs] start DBS worker") 130 defer w.wg.Done() 131 defer milevadbutil.Recover( 132 metrics.LabelDBSWorker, 133 fmt.Sprintf("DBS ID %s, %s start", d.uuid, w), 134 nil, true, 135 ) 136 137 // We use 4 * lease time to check tenant's timeout, so here, we will uFIDelate tenant's status 138 // every 2 * lease time. If lease is 0, we will use default 1s. 139 // But we use etcd to speed up, normally it takes less than 1s now, so we use 1s as the max value. 140 checkTime := chooseLeaseTime(2*d.lease, 1*time.Second) 141 142 ticker := time.NewTicker(checkTime) 143 defer ticker.Stop() 144 145 for { 146 select { 147 case <-ticker.C: 148 logutil.Logger(w.logCtx).Debug("[dbs] wait to check DBS status again", zap.Duration("interval", checkTime)) 149 case <-w.dbsJobCh: 150 case <-w.ctx.Done(): 151 return 152 } 153 154 err := w.handleDBSJobQueue(d) 155 if err != nil { 156 logutil.Logger(w.logCtx).Error("[dbs] handle DBS job failed", zap.Error(err)) 157 } 158 } 159 } 160 161 func asyncNotify(ch chan struct{}) { 162 select { 163 case ch <- struct{}{}: 164 default: 165 } 166 } 167 168 // buildJobDependence sets the curjob's dependency-ID. 169 // The dependency-job's ID must less than the current job's ID, and we need the largest one in the list. 170 func buildJobDependence(t *spacetime.Meta, curJob *perceptron.Job) error { 171 // Jobs in the same queue are ordered. If we want to find a job's dependency-job, we need to look for 172 // it from the other queue. So if the job is "CausetActionAddIndex" job, we need find its dependency-job from DefaultJobList. 173 var jobs []*perceptron.Job 174 var err error 175 switch curJob.Type { 176 case perceptron.CausetActionAddIndex, perceptron.CausetActionAddPrimaryKey: 177 jobs, err = t.GetAllDBSJobsInQueue(spacetime.DefaultJobListKey) 178 default: 179 jobs, err = t.GetAllDBSJobsInQueue(spacetime.AddIndexJobListKey) 180 } 181 if err != nil { 182 return errors.Trace(err) 183 } 184 185 for _, job := range jobs { 186 if curJob.ID < job.ID { 187 continue 188 } 189 isDependent, err := curJob.IsDependentOn(job) 190 if err != nil { 191 return errors.Trace(err) 192 } 193 if isDependent { 194 logutil.BgLogger().Info("[dbs] current DBS job depends on other job", zap.String("currentJob", curJob.String()), zap.String("dependentJob", job.String())) 195 curJob.DependencyID = job.ID 196 break 197 } 198 } 199 return nil 200 } 201 202 func (d *dbs) limitDBSJobs() { 203 defer d.wg.Done() 204 defer milevadbutil.Recover(metrics.LabelDBS, "limitDBSJobs", nil, true) 205 206 tasks := make([]*limitJobTask, 0, batchAddingJobs) 207 for { 208 select { 209 case task := <-d.limitJobCh: 210 tasks = tasks[:0] 211 jobLen := len(d.limitJobCh) 212 tasks = append(tasks, task) 213 for i := 0; i < jobLen; i++ { 214 tasks = append(tasks, <-d.limitJobCh) 215 } 216 d.addBatchDBSJobs(tasks) 217 case <-d.ctx.Done(): 218 return 219 } 220 } 221 } 222 223 // addBatchDBSJobs gets global job IDs and puts the DBS jobs in the DBS queue. 224 func (d *dbs) addBatchDBSJobs(tasks []*limitJobTask) { 225 startTime := time.Now() 226 err := ekv.RunInNewTxn(d.causetstore, true, func(txn ekv.Transaction) error { 227 t := spacetime.NewMeta(txn) 228 ids, err := t.GenGlobalIDs(len(tasks)) 229 if err != nil { 230 return errors.Trace(err) 231 } 232 for i, task := range tasks { 233 job := task.job 234 job.Version = currentVersion 235 job.StartTS = txn.StartTS() 236 job.ID = ids[i] 237 if err = buildJobDependence(t, job); err != nil { 238 return errors.Trace(err) 239 } 240 241 if job.Type == perceptron.CausetActionAddIndex || job.Type == perceptron.CausetActionAddPrimaryKey { 242 jobKey := spacetime.AddIndexJobListKey 243 err = t.EnQueueDBSJob(job, jobKey) 244 } else { 245 err = t.EnQueueDBSJob(job) 246 } 247 if err != nil { 248 return errors.Trace(err) 249 } 250 } 251 return nil 252 }) 253 var jobs string 254 for _, task := range tasks { 255 task.err <- err 256 jobs += task.job.String() + "; " 257 metrics.DBSWorkerHistogram.WithLabelValues(metrics.WorkerAddDBSJob, task.job.Type.String(), 258 metrics.RetLabel(err)).Observe(time.Since(startTime).Seconds()) 259 } 260 logutil.BgLogger().Info("[dbs] add DBS jobs", zap.Int("batch count", len(tasks)), zap.String("jobs", jobs)) 261 } 262 263 // getHistoryDBSJob gets a DBS job with job's ID from history queue. 264 func (d *dbs) getHistoryDBSJob(id int64) (*perceptron.Job, error) { 265 var job *perceptron.Job 266 267 err := ekv.RunInNewTxn(d.causetstore, false, func(txn ekv.Transaction) error { 268 t := spacetime.NewMeta(txn) 269 var err1 error 270 job, err1 = t.GetHistoryDBSJob(id) 271 return errors.Trace(err1) 272 }) 273 274 return job, errors.Trace(err) 275 } 276 277 // getFirstDBSJob gets the first DBS job form DBS queue. 278 func (w *worker) getFirstDBSJob(t *spacetime.Meta) (*perceptron.Job, error) { 279 job, err := t.GetDBSJobByIdx(0) 280 return job, errors.Trace(err) 281 } 282 283 // handleUFIDelateJobError handles the too large DBS job. 284 func (w *worker) handleUFIDelateJobError(t *spacetime.Meta, job *perceptron.Job, err error) error { 285 if err == nil { 286 return nil 287 } 288 if ekv.ErrEntryTooLarge.Equal(err) { 289 logutil.Logger(w.logCtx).Warn("[dbs] uFIDelate DBS job failed", zap.String("job", job.String()), zap.Error(err)) 290 // Reduce this txn entry size. 291 job.BinlogInfo.Clean() 292 job.Error = toTError(err) 293 job.ErrorCount++ 294 job.SchemaState = perceptron.StateNone 295 job.State = perceptron.JobStateCancelled 296 err = w.finishDBSJob(t, job) 297 } 298 return errors.Trace(err) 299 } 300 301 // uFIDelateDBSJob uFIDelates the DBS job information. 302 // Every time we enter another state except final state, we must call this function. 303 func (w *worker) uFIDelateDBSJob(t *spacetime.Meta, job *perceptron.Job, meetErr bool) error { 304 failpoint.Inject("mockErrEntrySizeTooLarge", func(val failpoint.Value) { 305 if val.(bool) { 306 failpoint.Return(ekv.ErrEntryTooLarge) 307 } 308 }) 309 uFIDelateRawArgs := true 310 // If there is an error when running job and the RawArgs hasn't been decoded by DecodeArgs, 311 // so we shouldn't replace RawArgs with the marshaling Args. 312 if meetErr && (job.RawArgs != nil && job.Args == nil) { 313 logutil.Logger(w.logCtx).Info("[dbs] meet something wrong before uFIDelate DBS job, shouldn't uFIDelate raw args", 314 zap.String("job", job.String())) 315 uFIDelateRawArgs = false 316 } 317 return errors.Trace(t.UFIDelateDBSJob(0, job, uFIDelateRawArgs)) 318 } 319 320 func (w *worker) deleteRange(job *perceptron.Job) error { 321 var err error 322 if job.Version <= currentVersion { 323 err = w.delRangeManager.addDelRangeJob(job) 324 } else { 325 err = errInvalidDBSJobVersion.GenWithStackByArgs(job.Version, currentVersion) 326 } 327 return errors.Trace(err) 328 } 329 330 // finishDBSJob deletes the finished DBS job in the dbs queue and puts it to history queue. 331 // If the DBS job need to handle in background, it will prepare a background job. 332 func (w *worker) finishDBSJob(t *spacetime.Meta, job *perceptron.Job) (err error) { 333 startTime := time.Now() 334 defer func() { 335 metrics.DBSWorkerHistogram.WithLabelValues(metrics.WorkerFinishDBSJob, job.Type.String(), metrics.RetLabel(err)).Observe(time.Since(startTime).Seconds()) 336 }() 337 338 if !job.IsCancelled() { 339 switch job.Type { 340 case perceptron.CausetActionAddIndex, perceptron.CausetActionAddPrimaryKey: 341 if job.State != perceptron.JobStateRollbackDone { 342 break 343 } 344 345 // After rolling back an AddIndex operation, we need to use delete-range to delete the half-done index data. 346 err = w.deleteRange(job) 347 case perceptron.CausetActionDropSchema, perceptron.CausetActionDropBlock, perceptron.CausetActionTruncateBlock, perceptron.CausetActionDropIndex, perceptron.CausetActionDropPrimaryKey, 348 perceptron.CausetActionDropBlockPartition, perceptron.CausetActionTruncateBlockPartition, perceptron.CausetActionDropDeferredCauset, perceptron.CausetActionDropDeferredCausets, perceptron.CausetActionModifyDeferredCauset: 349 err = w.deleteRange(job) 350 } 351 } 352 switch job.Type { 353 case perceptron.CausetActionRecoverBlock: 354 err = finishRecoverBlock(w, t, job) 355 } 356 if err != nil { 357 return errors.Trace(err) 358 } 359 360 _, err = t.DeQueueDBSJob() 361 if err != nil { 362 return errors.Trace(err) 363 } 364 365 job.BinlogInfo.FinishedTS = t.StartTS 366 logutil.Logger(w.logCtx).Info("[dbs] finish DBS job", zap.String("job", job.String())) 367 uFIDelateRawArgs := true 368 if job.Type == perceptron.CausetActionAddPrimaryKey && !job.IsCancelled() { 369 // CausetActionAddPrimaryKey needs to check the warnings information in job.Args. 370 // Notice: warnings is used to support non-strict mode. 371 uFIDelateRawArgs = false 372 } 373 err = t.AddHistoryDBSJob(job, uFIDelateRawArgs) 374 return errors.Trace(err) 375 } 376 377 func finishRecoverBlock(w *worker, t *spacetime.Meta, job *perceptron.Job) error { 378 tbInfo := &perceptron.BlockInfo{} 379 var autoIncID, autoRandID, dropJobID, recoverBlockCheckFlag int64 380 var snapshotTS uint64 381 err := job.DecodeArgs(tbInfo, &autoIncID, &dropJobID, &snapshotTS, &recoverBlockCheckFlag, &autoRandID) 382 if err != nil { 383 return errors.Trace(err) 384 } 385 if recoverBlockCheckFlag == recoverBlockCheckFlagEnableGC { 386 err = enableGC(w) 387 if err != nil { 388 return errors.Trace(err) 389 } 390 } 391 return nil 392 } 393 394 func isDependencyJobDone(t *spacetime.Meta, job *perceptron.Job) (bool, error) { 395 if job.DependencyID == noneDependencyJob { 396 return true, nil 397 } 398 399 historyJob, err := t.GetHistoryDBSJob(job.DependencyID) 400 if err != nil { 401 return false, errors.Trace(err) 402 } 403 if historyJob == nil { 404 return false, nil 405 } 406 logutil.BgLogger().Info("[dbs] current DBS job dependent job is finished", zap.String("currentJob", job.String()), zap.Int64("dependentJobID", job.DependencyID)) 407 job.DependencyID = noneDependencyJob 408 return true, nil 409 } 410 411 func newMetaWithQueueTp(txn ekv.Transaction, tp string) *spacetime.Meta { 412 if tp == perceptron.AddIndexStr || tp == perceptron.AddPrimaryKeyStr { 413 return spacetime.NewMeta(txn, spacetime.AddIndexJobListKey) 414 } 415 return spacetime.NewMeta(txn) 416 } 417 418 // handleDBSJobQueue handles DBS jobs in DBS Job queue. 419 func (w *worker) handleDBSJobQueue(d *dbsCtx) error { 420 once := true 421 waitDependencyJobCnt := 0 422 for { 423 if isChanClosed(w.ctx.Done()) { 424 return nil 425 } 426 427 var ( 428 job *perceptron.Job 429 schemaVer int64 430 runJobErr error 431 ) 432 waitTime := 2 * d.lease 433 err := ekv.RunInNewTxn(d.causetstore, false, func(txn ekv.Transaction) error { 434 // We are not tenant, return and retry checking later. 435 if !d.isTenant() { 436 return nil 437 } 438 439 var err error 440 t := newMetaWithQueueTp(txn, w.typeStr()) 441 // We become the tenant. Get the first job and run it. 442 job, err = w.getFirstDBSJob(t) 443 if job == nil || err != nil { 444 return errors.Trace(err) 445 } 446 if isDone, err1 := isDependencyJobDone(t, job); err1 != nil || !isDone { 447 return errors.Trace(err1) 448 } 449 450 if once { 451 w.waitSchemaSynced(d, job, waitTime) 452 once = false 453 return nil 454 } 455 456 if job.IsDone() || job.IsRollbackDone() { 457 if !job.IsRollbackDone() { 458 job.State = perceptron.JobStateSynced 459 } 460 err = w.finishDBSJob(t, job) 461 return errors.Trace(err) 462 } 463 464 d.mu.RLock() 465 d.mu.hook.OnJobRunBefore(job) 466 d.mu.RUnlock() 467 468 // If running job meets error, we will save this error in job Error 469 // and retry later if the job is not cancelled. 470 schemaVer, runJobErr = w.runDBSJob(d, t, job) 471 if job.IsCancelled() { 472 txn.Reset() 473 err = w.finishDBSJob(t, job) 474 return errors.Trace(err) 475 } 476 if runJobErr != nil && !job.IsRollingback() && !job.IsRollbackDone() { 477 // If the running job meets an error 478 // and the job state is rolling back, it means that we have already handled this error. 479 // Some DBS jobs (such as adding indexes) may need to uFIDelate the causet info and the schemaReplicant version, 480 // then shouldn't discard the KV modification. 481 // And the job state is rollback done, it means the job was already finished, also shouldn't discard too. 482 // Otherwise, we should discard the KV modification when running job. 483 txn.Reset() 484 // If error happens after uFIDelateSchemaVersion(), then the schemaVer is uFIDelated. 485 // Result in the retry duration is up to 2 * lease. 486 schemaVer = 0 487 } 488 err = w.uFIDelateDBSJob(t, job, runJobErr != nil) 489 if err = w.handleUFIDelateJobError(t, job, err); err != nil { 490 return errors.Trace(err) 491 } 492 writeBinlog(d.binlogCli, txn, job) 493 return nil 494 }) 495 496 if runJobErr != nil { 497 // wait a while to retry again. If we don't wait here, DBS will retry this job immediately, 498 // which may act like a deadlock. 499 logutil.Logger(w.logCtx).Info("[dbs] run DBS job failed, sleeps a while then retries it.", 500 zap.Duration("waitTime", GetWaitTimeWhenErrorOccurred()), zap.Error(runJobErr)) 501 time.Sleep(GetWaitTimeWhenErrorOccurred()) 502 } 503 504 if err != nil { 505 return errors.Trace(err) 506 } else if job == nil { 507 // No job now, return and retry getting later. 508 return nil 509 } 510 w.waitDependencyJobFinished(job, &waitDependencyJobCnt) 511 512 // Here means the job enters another state (delete only, write only, public, etc...) or is cancelled. 513 // If the job is done or still running or rolling back, we will wait 2 * lease time to guarantee other servers to uFIDelate 514 // the newest schemaReplicant. 515 ctx, cancel := context.WithTimeout(w.ctx, waitTime) 516 w.waitSchemaChanged(ctx, d, waitTime, schemaVer, job) 517 cancel() 518 519 d.mu.RLock() 520 d.mu.hook.OnJobUFIDelated(job) 521 d.mu.RUnlock() 522 523 if job.IsSynced() || job.IsCancelled() { 524 asyncNotify(d.dbsJobDoneCh) 525 } 526 } 527 } 528 529 func skipWriteBinlog(job *perceptron.Job) bool { 530 switch job.Type { 531 // CausetActionUFIDelateTiFlashReplicaStatus is a MilevaDB internal DBS, 532 // it's used to uFIDelate causet's TiFlash replica available status. 533 case perceptron.CausetActionUFIDelateTiFlashReplicaStatus: 534 return true 535 // It is done without modifying causet info, bin log is not needed 536 case perceptron.CausetActionAlterBlockAlterPartition: 537 return true 538 } 539 540 return false 541 } 542 543 func writeBinlog(binlogCli *pumpcli.PumpsClient, txn ekv.Transaction, job *perceptron.Job) { 544 if job.IsDone() || job.IsRollbackDone() || 545 // When this defCausumn is in the "delete only" and "delete reorg" states, the binlog of "drop defCausumn" has not been written yet, 546 // but the defCausumn has been removed from the binlog of the write operation. 547 // So we add this binlog to enable downstream components to handle DML correctly in this schemaReplicant state. 548 ((job.Type == perceptron.CausetActionDropDeferredCauset || job.Type == perceptron.CausetActionDropDeferredCausets) && job.SchemaState == perceptron.StateDeleteOnly) { 549 if skipWriteBinlog(job) { 550 return 551 } 552 binloginfo.SetDBSBinlog(binlogCli, txn, job.ID, int32(job.SchemaState), job.Query) 553 } 554 } 555 556 // waitDependencyJobFinished waits for the dependency-job to be finished. 557 // If the dependency job isn't finished yet, we'd better wait a moment. 558 func (w *worker) waitDependencyJobFinished(job *perceptron.Job, cnt *int) { 559 if job.DependencyID != noneDependencyJob { 560 intervalCnt := int(3 * time.Second / waitDependencyJobInterval) 561 if *cnt%intervalCnt == 0 { 562 logutil.Logger(w.logCtx).Info("[dbs] DBS job need to wait dependent job, sleeps a while, then retries it.", 563 zap.Int64("jobID", job.ID), 564 zap.Int64("dependentJobID", job.DependencyID), 565 zap.Duration("waitTime", waitDependencyJobInterval)) 566 } 567 time.Sleep(waitDependencyJobInterval) 568 *cnt++ 569 } else { 570 *cnt = 0 571 } 572 } 573 574 func chooseLeaseTime(t, max time.Duration) time.Duration { 575 if t == 0 || t > max { 576 return max 577 } 578 return t 579 } 580 581 // runDBSJob runs a DBS job. It returns the current schemaReplicant version in this transaction and the error. 582 func (w *worker) runDBSJob(d *dbsCtx, t *spacetime.Meta, job *perceptron.Job) (ver int64, err error) { 583 defer milevadbutil.Recover(metrics.LabelDBSWorker, fmt.Sprintf("%s runDBSJob", w), 584 func() { 585 // If run DBS job panic, just cancel the DBS jobs. 586 job.State = perceptron.JobStateCancelling 587 }, false) 588 589 // Mock for run dbs job panic. 590 failpoint.Inject("mockPanicInRunDBSJob", func(val failpoint.Value) {}) 591 592 logutil.Logger(w.logCtx).Info("[dbs] run DBS job", zap.String("job", job.String())) 593 timeStart := time.Now() 594 defer func() { 595 metrics.DBSWorkerHistogram.WithLabelValues(metrics.WorkerRunDBSJob, job.Type.String(), metrics.RetLabel(err)).Observe(time.Since(timeStart).Seconds()) 596 }() 597 if job.IsFinished() { 598 return 599 } 600 // The cause of this job state is that the job is cancelled by client. 601 if job.IsCancelling() { 602 return convertJob2RollbackJob(w, d, t, job) 603 } 604 605 if !job.IsRollingback() && !job.IsCancelling() { 606 job.State = perceptron.JobStateRunning 607 } 608 609 switch job.Type { 610 case perceptron.CausetActionCreateSchema: 611 ver, err = onCreateSchema(d, t, job) 612 case perceptron.CausetActionModifySchemaCharsetAndDefCauslate: 613 ver, err = onModifySchemaCharsetAndDefCauslate(t, job) 614 case perceptron.CausetActionDropSchema: 615 ver, err = onDropSchema(t, job) 616 case perceptron.CausetActionCreateBlock: 617 ver, err = onCreateBlock(d, t, job) 618 case perceptron.CausetActionRepairBlock: 619 ver, err = onRepairBlock(d, t, job) 620 case perceptron.CausetActionCreateView: 621 ver, err = onCreateView(d, t, job) 622 case perceptron.CausetActionDropBlock, perceptron.CausetActionDropView, perceptron.CausetActionDropSequence: 623 ver, err = onDropBlockOrView(t, job) 624 case perceptron.CausetActionDropBlockPartition: 625 ver, err = onDropBlockPartition(t, job) 626 case perceptron.CausetActionTruncateBlockPartition: 627 ver, err = onTruncateBlockPartition(d, t, job) 628 case perceptron.CausetActionExchangeBlockPartition: 629 ver, err = w.onExchangeBlockPartition(d, t, job) 630 case perceptron.CausetActionAddDeferredCauset: 631 ver, err = onAddDeferredCauset(d, t, job) 632 case perceptron.CausetActionAddDeferredCausets: 633 ver, err = onAddDeferredCausets(d, t, job) 634 case perceptron.CausetActionDropDeferredCauset: 635 ver, err = onDropDeferredCauset(t, job) 636 case perceptron.CausetActionDropDeferredCausets: 637 ver, err = onDropDeferredCausets(t, job) 638 case perceptron.CausetActionModifyDeferredCauset: 639 ver, err = w.onModifyDeferredCauset(d, t, job) 640 case perceptron.CausetActionSetDefaultValue: 641 ver, err = onSetDefaultValue(t, job) 642 case perceptron.CausetActionAddIndex: 643 ver, err = w.onCreateIndex(d, t, job, false) 644 case perceptron.CausetActionAddPrimaryKey: 645 ver, err = w.onCreateIndex(d, t, job, true) 646 case perceptron.CausetActionDropIndex, perceptron.CausetActionDropPrimaryKey: 647 ver, err = onDropIndex(t, job) 648 case perceptron.CausetActionRenameIndex: 649 ver, err = onRenameIndex(t, job) 650 case perceptron.CausetActionAddForeignKey: 651 ver, err = onCreateForeignKey(t, job) 652 case perceptron.CausetActionDropForeignKey: 653 ver, err = onDropForeignKey(t, job) 654 case perceptron.CausetActionTruncateBlock: 655 ver, err = onTruncateBlock(d, t, job) 656 case perceptron.CausetActionRebaseAutoID: 657 ver, err = onRebaseRowIDType(d.causetstore, t, job) 658 case perceptron.CausetActionRebaseAutoRandomBase: 659 ver, err = onRebaseAutoRandomType(d.causetstore, t, job) 660 case perceptron.CausetActionRenameBlock: 661 ver, err = onRenameBlock(d, t, job) 662 case perceptron.CausetActionShardRowID: 663 ver, err = w.onShardRowID(d, t, job) 664 case perceptron.CausetActionModifyBlockComment: 665 ver, err = onModifyBlockComment(t, job) 666 case perceptron.CausetActionModifyBlockAutoIdCache: 667 ver, err = onModifyBlockAutoIDCache(t, job) 668 case perceptron.CausetActionAddBlockPartition: 669 ver, err = onAddBlockPartition(d, t, job) 670 case perceptron.CausetActionModifyBlockCharsetAndDefCauslate: 671 ver, err = onModifyBlockCharsetAndDefCauslate(t, job) 672 case perceptron.CausetActionRecoverBlock: 673 ver, err = w.onRecoverBlock(d, t, job) 674 case perceptron.CausetActionLockBlock: 675 ver, err = onLockBlocks(t, job) 676 case perceptron.CausetActionUnlockBlock: 677 ver, err = onUnlockBlocks(t, job) 678 case perceptron.CausetActionSetTiFlashReplica: 679 ver, err = w.onSetBlockFlashReplica(t, job) 680 case perceptron.CausetActionUFIDelateTiFlashReplicaStatus: 681 ver, err = onUFIDelateFlashReplicaStatus(t, job) 682 case perceptron.CausetActionCreateSequence: 683 ver, err = onCreateSequence(d, t, job) 684 case perceptron.CausetActionAlterIndexVisibility: 685 ver, err = onAlterIndexVisibility(t, job) 686 case perceptron.CausetActionAlterBlockAlterPartition: 687 ver, err = onAlterBlockPartition(t, job) 688 default: 689 // Invalid job, cancel it. 690 job.State = perceptron.JobStateCancelled 691 err = errInvalidDBSJob.GenWithStack("invalid dbs job type: %v", job.Type) 692 } 693 694 // Save errors in job, so that others can know errors happened. 695 if err != nil { 696 job.Error = toTError(err) 697 job.ErrorCount++ 698 699 // If job is cancelled, we shouldn't return an error and shouldn't load DBS variables. 700 if job.State == perceptron.JobStateCancelled { 701 logutil.Logger(w.logCtx).Info("[dbs] DBS job is cancelled normally", zap.Error(err)) 702 return ver, nil 703 } 704 logutil.Logger(w.logCtx).Error("[dbs] run DBS job error", zap.Error(err)) 705 706 // Load global dbs variables. 707 if err1 := loadDBSVars(w); err1 != nil { 708 logutil.Logger(w.logCtx).Error("[dbs] load DBS global variable failed", zap.Error(err1)) 709 } 710 // Check error limit to avoid falling into an infinite loop. 711 if job.ErrorCount > variable.GetDBSErrorCountLimit() && job.State == perceptron.JobStateRunning && admin.IsJobRollbackable(job) { 712 logutil.Logger(w.logCtx).Warn("[dbs] DBS job error count exceed the limit, cancelling it now", zap.Int64("jobID", job.ID), zap.Int64("errorCountLimit", variable.GetDBSErrorCountLimit())) 713 job.State = perceptron.JobStateCancelling 714 } 715 } 716 return 717 } 718 719 func loadDBSVars(w *worker) error { 720 // Get stochastikctx from context resource pool. 721 var ctx stochastikctx.Context 722 ctx, err := w.sessPool.get() 723 if err != nil { 724 return errors.Trace(err) 725 } 726 defer w.sessPool.put(ctx) 727 return soliton.LoadDBSVars(ctx) 728 } 729 730 func toTError(err error) *terror.Error { 731 originErr := errors.Cause(err) 732 tErr, ok := originErr.(*terror.Error) 733 if ok { 734 return tErr 735 } 736 737 // TODO: Add the error code. 738 return terror.ClassDBS.Synthesize(terror.CodeUnknown, err.Error()) 739 } 740 741 // waitSchemaChanged waits for the completion of uFIDelating all servers' schemaReplicant. In order to make sure that happens, 742 // we wait 2 * lease time. 743 func (w *worker) waitSchemaChanged(ctx context.Context, d *dbsCtx, waitTime time.Duration, latestSchemaVersion int64, job *perceptron.Job) { 744 if !job.IsRunning() && !job.IsRollingback() && !job.IsDone() && !job.IsRollbackDone() { 745 return 746 } 747 if waitTime == 0 { 748 return 749 } 750 751 timeStart := time.Now() 752 var err error 753 defer func() { 754 metrics.DBSWorkerHistogram.WithLabelValues(metrics.WorkerWaitSchemaChanged, job.Type.String(), metrics.RetLabel(err)).Observe(time.Since(timeStart).Seconds()) 755 }() 756 757 if latestSchemaVersion == 0 { 758 logutil.Logger(w.logCtx).Info("[dbs] schemaReplicant version doesn't change") 759 return 760 } 761 762 err = d.schemaSyncer.TenantUFIDelateGlobalVersion(ctx, latestSchemaVersion) 763 if err != nil { 764 logutil.Logger(w.logCtx).Info("[dbs] uFIDelate latest schemaReplicant version failed", zap.Int64("ver", latestSchemaVersion), zap.Error(err)) 765 if terror.ErrorEqual(err, context.DeadlineExceeded) { 766 // If err is context.DeadlineExceeded, it means waitTime(2 * lease) is elapsed. So all the schemas are synced by ticker. 767 // There is no need to use etcd to sync. The function returns directly. 768 return 769 } 770 } 771 772 // TenantCheckAllVersions returns only when context is timeout(2 * lease) or all MilevaDB schemas are synced. 773 err = d.schemaSyncer.TenantCheckAllVersions(ctx, latestSchemaVersion) 774 if err != nil { 775 logutil.Logger(w.logCtx).Info("[dbs] wait latest schemaReplicant version to deadline", zap.Int64("ver", latestSchemaVersion), zap.Error(err)) 776 if terror.ErrorEqual(err, context.DeadlineExceeded) { 777 return 778 } 779 d.schemaSyncer.NotifyCleanExpiredPaths() 780 // Wait until timeout. 781 select { 782 case <-ctx.Done(): 783 return 784 } 785 } 786 logutil.Logger(w.logCtx).Info("[dbs] wait latest schemaReplicant version changed", 787 zap.Int64("ver", latestSchemaVersion), 788 zap.Duration("take time", time.Since(timeStart)), 789 zap.String("job", job.String())) 790 } 791 792 // waitSchemaSynced handles the following situation: 793 // If the job enters a new state, and the worker crashs when it's in the process of waiting for 2 * lease time, 794 // Then the worker restarts quickly, we may run the job immediately again, 795 // but in this case we don't wait enough 2 * lease time to let other servers uFIDelate the schemaReplicant. 796 // So here we get the latest schemaReplicant version to make sure all servers' schemaReplicant version uFIDelate to the latest schemaReplicant version 797 // in a cluster, or to wait for 2 * lease time. 798 func (w *worker) waitSchemaSynced(d *dbsCtx, job *perceptron.Job, waitTime time.Duration) { 799 if !job.IsRunning() && !job.IsRollingback() && !job.IsDone() && !job.IsRollbackDone() { 800 return 801 } 802 ctx, cancelFunc := context.WithTimeout(w.ctx, waitTime) 803 defer cancelFunc() 804 805 latestSchemaVersion, err := d.schemaSyncer.MustGetGlobalVersion(ctx) 806 if err != nil { 807 logutil.Logger(w.logCtx).Warn("[dbs] get global version failed", zap.Error(err)) 808 return 809 } 810 w.waitSchemaChanged(ctx, d, waitTime, latestSchemaVersion, job) 811 } 812 813 // uFIDelateSchemaVersion increments the schemaReplicant version by 1 and sets SchemaDiff. 814 func uFIDelateSchemaVersion(t *spacetime.Meta, job *perceptron.Job) (int64, error) { 815 schemaVersion, err := t.GenSchemaVersion() 816 if err != nil { 817 return 0, errors.Trace(err) 818 } 819 diff := &perceptron.SchemaDiff{ 820 Version: schemaVersion, 821 Type: job.Type, 822 SchemaID: job.SchemaID, 823 } 824 switch job.Type { 825 case perceptron.CausetActionTruncateBlock: 826 // Truncate causet has two causet ID, should be handled differently. 827 err = job.DecodeArgs(&diff.BlockID) 828 if err != nil { 829 return 0, errors.Trace(err) 830 } 831 diff.OldBlockID = job.BlockID 832 case perceptron.CausetActionCreateView: 833 tbInfo := &perceptron.BlockInfo{} 834 var orReplace bool 835 var oldTbInfoID int64 836 if err := job.DecodeArgs(tbInfo, &orReplace, &oldTbInfoID); err != nil { 837 return 0, errors.Trace(err) 838 } 839 // When the memex is "create or replace view " and we need to drop the old view, 840 // it has two causet IDs and should be handled differently. 841 if oldTbInfoID > 0 && orReplace { 842 diff.OldBlockID = oldTbInfoID 843 } 844 diff.BlockID = tbInfo.ID 845 case perceptron.CausetActionRenameBlock: 846 err = job.DecodeArgs(&diff.OldSchemaID) 847 if err != nil { 848 return 0, errors.Trace(err) 849 } 850 diff.BlockID = job.BlockID 851 case perceptron.CausetActionExchangeBlockPartition: 852 var ( 853 ptSchemaID int64 854 ptBlockID int64 855 ) 856 err = job.DecodeArgs(&diff.BlockID, &ptSchemaID, &ptBlockID) 857 if err != nil { 858 return 0, errors.Trace(err) 859 } 860 diff.OldBlockID = job.BlockID 861 affects := make([]*perceptron.AffectedOption, 1) 862 affects[0] = &perceptron.AffectedOption{ 863 SchemaID: ptSchemaID, 864 BlockID: ptBlockID, 865 OldBlockID: ptBlockID, 866 } 867 diff.AffectedOpts = affects 868 default: 869 diff.BlockID = job.BlockID 870 } 871 err = t.SetSchemaDiff(diff) 872 return schemaVersion, errors.Trace(err) 873 } 874 875 func isChanClosed(quitCh <-chan struct{}) bool { 876 select { 877 case <-quitCh: 878 return true 879 default: 880 return false 881 } 882 }