github.com/whtcorpsinc/milevadb-prod@v0.0.0-20211104133533-f57f4be3b597/interlock/distsql.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 interlock 15 16 import ( 17 "context" 18 "fmt" 19 "math" 20 "runtime" 21 "runtime/trace" 22 "sort" 23 "sync" 24 "sync/atomic" 25 "unsafe" 26 27 "github.com/whtcorpsinc/BerolinaSQL/allegrosql" 28 "github.com/whtcorpsinc/BerolinaSQL/charset" 29 "github.com/whtcorpsinc/BerolinaSQL/perceptron" 30 "github.com/whtcorpsinc/BerolinaSQL/terror" 31 "github.com/whtcorpsinc/errors" 32 "github.com/whtcorpsinc/fidelpb/go-fidelpb" 33 "github.com/whtcorpsinc/milevadb/allegrosql" 34 "github.com/whtcorpsinc/milevadb/blockcodec" 35 "github.com/whtcorpsinc/milevadb/causet" 36 causetembedded "github.com/whtcorpsinc/milevadb/causet/embedded" 37 "github.com/whtcorpsinc/milevadb/ekv" 38 "github.com/whtcorpsinc/milevadb/memex" 39 "github.com/whtcorpsinc/milevadb/soliton" 40 "github.com/whtcorpsinc/milevadb/soliton/chunk" 41 "github.com/whtcorpsinc/milevadb/soliton/codec" 42 "github.com/whtcorpsinc/milevadb/soliton/defCauslate" 43 "github.com/whtcorpsinc/milevadb/soliton/logutil" 44 "github.com/whtcorpsinc/milevadb/soliton/memory" 45 "github.com/whtcorpsinc/milevadb/soliton/ranger" 46 "github.com/whtcorpsinc/milevadb/statistics" 47 "github.com/whtcorpsinc/milevadb/stochastikctx" 48 "github.com/whtcorpsinc/milevadb/types" 49 "go.uber.org/zap" 50 ) 51 52 var ( 53 _ InterlockingDirectorate = &BlockReaderInterlockingDirectorate{} 54 _ InterlockingDirectorate = &IndexReaderInterlockingDirectorate{} 55 _ InterlockingDirectorate = &IndexLookUpInterlockingDirectorate{} 56 ) 57 58 // LookupBlockTaskChannelSize represents the channel size of the index double read taskChan. 59 var LookupBlockTaskChannelSize int32 = 50 60 61 // lookupBlockTask is created from a partial result of an index request which 62 // contains the handles in those index keys. 63 type lookupBlockTask struct { 64 handles []ekv.Handle 65 rowIdx []int // rowIdx represents the handle index for every event. Only used when keep order. 66 rows []chunk.Event 67 idxEvents *chunk.Chunk 68 cursor int 69 70 doneCh chan error 71 72 // indexOrder map is used to save the original index order for the handles. 73 // Without this map, the original index order might be lost. 74 // The handles fetched from index is originally ordered by index, but we need handles to be ordered by itself 75 // to do causet request. 76 indexOrder *ekv.HandleMap 77 // duplicatedIndexOrder map likes indexOrder. But it's used when checHoTTexValue isn't nil and 78 // the same handle of index has multiple values. 79 duplicatedIndexOrder *ekv.HandleMap 80 81 // memUsage records the memory usage of this task calculated by causet worker. 82 // memTracker is used to release memUsage after task is done and unused. 83 // 84 // The sequence of function calls are: 85 // 1. calculate task.memUsage. 86 // 2. task.memTracker = blockWorker.memTracker 87 // 3. task.memTracker.Consume(task.memUsage) 88 // 4. task.memTracker.Consume(-task.memUsage) 89 // 90 // Step 1~3 are completed in "blockWorker.executeTask". 91 // Step 4 is completed in "IndexLookUpInterlockingDirectorate.Next". 92 memUsage int64 93 memTracker *memory.Tracker 94 } 95 96 func (task *lookupBlockTask) Len() int { 97 return len(task.rows) 98 } 99 100 func (task *lookupBlockTask) Less(i, j int) bool { 101 return task.rowIdx[i] < task.rowIdx[j] 102 } 103 104 func (task *lookupBlockTask) Swap(i, j int) { 105 task.rowIdx[i], task.rowIdx[j] = task.rowIdx[j], task.rowIdx[i] 106 task.rows[i], task.rows[j] = task.rows[j], task.rows[i] 107 } 108 109 // Closeable is a interface for closeable structures. 110 type Closeable interface { 111 // Close closes the object. 112 Close() error 113 } 114 115 // closeAll closes all objects even if an object returns an error. 116 // If multiple objects returns error, the first error will be returned. 117 func closeAll(objs ...Closeable) error { 118 var err error 119 for _, obj := range objs { 120 if obj != nil { 121 err1 := obj.Close() 122 if err == nil && err1 != nil { 123 err = err1 124 } 125 } 126 } 127 if err != nil { 128 return errors.Trace(err) 129 } 130 return nil 131 } 132 133 // handleIsExtra checks whether this defCausumn is a extra handle defCausumn generated during plan building phase. 134 func handleIsExtra(defCaus *memex.DeferredCauset) bool { 135 if defCaus != nil && defCaus.ID == perceptron.ExtraHandleID { 136 return true 137 } 138 return false 139 } 140 141 func splitRanges(ranges []*ranger.Range, keepOrder bool, desc bool) ([]*ranger.Range, []*ranger.Range) { 142 if len(ranges) == 0 || ranges[0].LowVal[0].HoTT() == types.HoTTInt64 { 143 return ranges, nil 144 } 145 idx := sort.Search(len(ranges), func(i int) bool { return ranges[i].HighVal[0].GetUint64() > math.MaxInt64 }) 146 if idx == len(ranges) { 147 return ranges, nil 148 } 149 if ranges[idx].LowVal[0].GetUint64() > math.MaxInt64 { 150 signedRanges := ranges[0:idx] 151 unsignedRanges := ranges[idx:] 152 if !keepOrder { 153 return append(unsignedRanges, signedRanges...), nil 154 } 155 if desc { 156 return unsignedRanges, signedRanges 157 } 158 return signedRanges, unsignedRanges 159 } 160 signedRanges := make([]*ranger.Range, 0, idx+1) 161 unsignedRanges := make([]*ranger.Range, 0, len(ranges)-idx) 162 signedRanges = append(signedRanges, ranges[0:idx]...) 163 if !(ranges[idx].LowVal[0].GetUint64() == math.MaxInt64 && ranges[idx].LowExclude) { 164 signedRanges = append(signedRanges, &ranger.Range{ 165 LowVal: ranges[idx].LowVal, 166 LowExclude: ranges[idx].LowExclude, 167 HighVal: []types.Causet{types.NewUintCauset(math.MaxInt64)}, 168 }) 169 } 170 if !(ranges[idx].HighVal[0].GetUint64() == math.MaxInt64+1 && ranges[idx].HighExclude) { 171 unsignedRanges = append(unsignedRanges, &ranger.Range{ 172 LowVal: []types.Causet{types.NewUintCauset(math.MaxInt64 + 1)}, 173 HighVal: ranges[idx].HighVal, 174 HighExclude: ranges[idx].HighExclude, 175 }) 176 } 177 if idx < len(ranges) { 178 unsignedRanges = append(unsignedRanges, ranges[idx+1:]...) 179 } 180 if !keepOrder { 181 return append(unsignedRanges, signedRanges...), nil 182 } 183 if desc { 184 return unsignedRanges, signedRanges 185 } 186 return signedRanges, unsignedRanges 187 } 188 189 // rebuildIndexRanges will be called if there's correlated defCausumn in access conditions. We will rebuild the range 190 // by substitute correlated defCausumn with the constant. 191 func rebuildIndexRanges(ctx stochastikctx.Context, is *causetembedded.PhysicalIndexScan, idxDefCauss []*memex.DeferredCauset, defCausLens []int) (ranges []*ranger.Range, err error) { 192 access := make([]memex.Expression, 0, len(is.AccessCondition)) 193 for _, cond := range is.AccessCondition { 194 newCond, err1 := memex.SubstituteCorDefCaus2Constant(cond) 195 if err1 != nil { 196 return nil, err1 197 } 198 access = append(access, newCond) 199 } 200 ranges, _, err = ranger.DetachSimpleCondAndBuildRangeForIndex(ctx, access, idxDefCauss, defCausLens) 201 return ranges, err 202 } 203 204 // IndexReaderInterlockingDirectorate sends posetPosetDag request and reads index data from ekv layer. 205 type IndexReaderInterlockingDirectorate struct { 206 baseInterlockingDirectorate 207 208 // For a partitioned causet, the IndexReaderInterlockingDirectorate works on a partition, so 209 // the type of this causet field is actually `causet.PhysicalBlock`. 210 causet causet.Block 211 index *perceptron.IndexInfo 212 physicalBlockID int64 213 ranges []*ranger.Range 214 // ekvRanges are only used for union scan. 215 ekvRanges []ekv.KeyRange 216 posetPosetDagPB *fidelpb.PosetDagRequest 217 startTS uint64 218 219 // result returns one or more allegrosql.PartialResult and each PartialResult is returned by one region. 220 result allegrosql.SelectResult 221 // defCausumns are only required by union scan. 222 defCausumns []*perceptron.DeferredCausetInfo 223 // outputDeferredCausets are only required by union scan. 224 outputDeferredCausets []*memex.DeferredCauset 225 226 feedback *statistics.QueryFeedback 227 streaming bool 228 229 keepOrder bool 230 desc bool 231 232 corDefCausInFilter bool 233 corDefCausInAccess bool 234 idxDefCauss []*memex.DeferredCauset 235 defCausLens []int 236 plans []causetembedded.PhysicalCauset 237 238 memTracker *memory.Tracker 239 240 selectResultHook // for testing 241 } 242 243 // Close clears all resources hold by current object. 244 func (e *IndexReaderInterlockingDirectorate) Close() error { 245 err := e.result.Close() 246 e.result = nil 247 e.ctx.StoreQueryFeedback(e.feedback) 248 return err 249 } 250 251 // Next implements the InterlockingDirectorate Next interface. 252 func (e *IndexReaderInterlockingDirectorate) Next(ctx context.Context, req *chunk.Chunk) error { 253 err := e.result.Next(ctx, req) 254 if err != nil { 255 e.feedback.Invalidate() 256 } 257 return err 258 } 259 260 // Open implements the InterlockingDirectorate Open interface. 261 func (e *IndexReaderInterlockingDirectorate) Open(ctx context.Context) error { 262 var err error 263 if e.corDefCausInAccess { 264 e.ranges, err = rebuildIndexRanges(e.ctx, e.plans[0].(*causetembedded.PhysicalIndexScan), e.idxDefCauss, e.defCausLens) 265 if err != nil { 266 return err 267 } 268 } 269 ekvRanges, err := allegrosql.IndexRangesToKVRanges(e.ctx.GetStochastikVars().StmtCtx, e.physicalBlockID, e.index.ID, e.ranges, e.feedback) 270 if err != nil { 271 e.feedback.Invalidate() 272 return err 273 } 274 return e.open(ctx, ekvRanges) 275 } 276 277 func (e *IndexReaderInterlockingDirectorate) open(ctx context.Context, ekvRanges []ekv.KeyRange) error { 278 var err error 279 if e.corDefCausInFilter { 280 e.posetPosetDagPB.InterlockingDirectorates, _, err = constructDistInterDirc(e.ctx, e.plans) 281 if err != nil { 282 return err 283 } 284 } 285 286 if e.runtimeStats != nil { 287 defCauslInterDirc := true 288 e.posetPosetDagPB.DefCauslectInterDircutionSummaries = &defCauslInterDirc 289 } 290 e.ekvRanges = ekvRanges 291 292 e.memTracker = memory.NewTracker(e.id, -1) 293 e.memTracker.AttachTo(e.ctx.GetStochastikVars().StmtCtx.MemTracker) 294 var builder allegrosql.RequestBuilder 295 ekvReq, err := builder.SetKeyRanges(ekvRanges). 296 SetPosetDagRequest(e.posetPosetDagPB). 297 SetStartTS(e.startTS). 298 SetDesc(e.desc). 299 SetKeepOrder(e.keepOrder). 300 SetStreaming(e.streaming). 301 SetFromStochastikVars(e.ctx.GetStochastikVars()). 302 SetMemTracker(e.memTracker). 303 Build() 304 if err != nil { 305 e.feedback.Invalidate() 306 return err 307 } 308 e.result, err = e.SelectResult(ctx, e.ctx, ekvReq, retTypes(e), e.feedback, getPhysicalCausetIDs(e.plans), e.id) 309 if err != nil { 310 e.feedback.Invalidate() 311 return err 312 } 313 e.result.Fetch(ctx) 314 return nil 315 } 316 317 // IndexLookUpInterlockingDirectorate implements double read for index scan. 318 type IndexLookUpInterlockingDirectorate struct { 319 baseInterlockingDirectorate 320 321 causet causet.Block 322 index *perceptron.IndexInfo 323 ranges []*ranger.Range 324 posetPosetDagPB *fidelpb.PosetDagRequest 325 startTS uint64 326 // handleIdx is the index of handle, which is only used for case of keeping order. 327 handleIdx []int 328 handleDefCauss []*memex.DeferredCauset 329 primaryKeyIndex *perceptron.IndexInfo 330 blockRequest *fidelpb.PosetDagRequest 331 // defCausumns are only required by union scan. 332 defCausumns []*perceptron.DeferredCausetInfo 333 *dataReaderBuilder 334 // All fields above are immublock. 335 336 idxWorkerWg sync.WaitGroup 337 tblWorkerWg sync.WaitGroup 338 finished chan struct{} 339 340 resultCh chan *lookupBlockTask 341 resultCurr *lookupBlockTask 342 feedback *statistics.QueryFeedback 343 344 // memTracker is used to track the memory usage of this interlock. 345 memTracker *memory.Tracker 346 347 // checHoTTexValue is used to check the consistency of the index data. 348 *checHoTTexValue 349 350 ekvRanges []ekv.KeyRange 351 workerStarted bool 352 353 keepOrder bool 354 desc bool 355 356 indexStreaming bool 357 blockStreaming bool 358 359 corDefCausInIdxSide bool 360 corDefCausInTblSide bool 361 corDefCausInAccess bool 362 idxCausets []causetembedded.PhysicalCauset 363 tblCausets []causetembedded.PhysicalCauset 364 idxDefCauss []*memex.DeferredCauset 365 defCausLens []int 366 // PushedLimit is used to skip the preceding and tailing handles when Limit is sunk into IndexLookUpReader. 367 PushedLimit *causetembedded.PushedDownLimit 368 } 369 370 type getHandleType int8 371 372 const ( 373 getHandleFromIndex getHandleType = iota 374 getHandleFromBlock 375 ) 376 377 type checHoTTexValue struct { 378 idxDefCausTps []*types.FieldType 379 idxTblDefCauss []*causet.DeferredCauset 380 } 381 382 // Open implements the InterlockingDirectorate Open interface. 383 func (e *IndexLookUpInterlockingDirectorate) Open(ctx context.Context) error { 384 var err error 385 if e.corDefCausInAccess { 386 e.ranges, err = rebuildIndexRanges(e.ctx, e.idxCausets[0].(*causetembedded.PhysicalIndexScan), e.idxDefCauss, e.defCausLens) 387 if err != nil { 388 return err 389 } 390 } 391 sc := e.ctx.GetStochastikVars().StmtCtx 392 physicalID := getPhysicalBlockID(e.causet) 393 if e.index.ID == -1 { 394 e.ekvRanges, err = allegrosql.CommonHandleRangesToKVRanges(sc, physicalID, e.ranges) 395 } else { 396 e.ekvRanges, err = allegrosql.IndexRangesToKVRanges(sc, physicalID, e.index.ID, e.ranges, e.feedback) 397 } 398 if err != nil { 399 e.feedback.Invalidate() 400 return err 401 } 402 err = e.open(ctx) 403 if err != nil { 404 e.feedback.Invalidate() 405 } 406 return err 407 } 408 409 func (e *IndexLookUpInterlockingDirectorate) open(ctx context.Context) error { 410 // We have to initialize "memTracker" and other execution resources in here 411 // instead of in function "Open", because this "IndexLookUpInterlockingDirectorate" may be 412 // constructed by a "IndexLookUpJoin" and "Open" will not be called in that 413 // situation. 414 e.memTracker = memory.NewTracker(e.id, -1) 415 e.memTracker.AttachTo(e.ctx.GetStochastikVars().StmtCtx.MemTracker) 416 417 e.finished = make(chan struct{}) 418 e.resultCh = make(chan *lookupBlockTask, atomic.LoadInt32(&LookupBlockTaskChannelSize)) 419 420 var err error 421 if e.corDefCausInIdxSide { 422 e.posetPosetDagPB.InterlockingDirectorates, _, err = constructDistInterDirc(e.ctx, e.idxCausets) 423 if err != nil { 424 return err 425 } 426 } 427 428 if e.corDefCausInTblSide { 429 e.blockRequest.InterlockingDirectorates, _, err = constructDistInterDirc(e.ctx, e.tblCausets) 430 if err != nil { 431 return err 432 } 433 } 434 return nil 435 } 436 437 func (e *IndexLookUpInterlockingDirectorate) startWorkers(ctx context.Context, initBatchSize int) error { 438 // indexWorker will write to workCh and blockWorker will read from workCh, 439 // so fetching index and getting causet data can run concurrently. 440 workCh := make(chan *lookupBlockTask, 1) 441 if err := e.startIndexWorker(ctx, e.ekvRanges, workCh, initBatchSize); err != nil { 442 return err 443 } 444 e.startBlockWorker(ctx, workCh) 445 e.workerStarted = true 446 return nil 447 } 448 449 func (e *IndexLookUpInterlockingDirectorate) isCommonHandle() bool { 450 return !(len(e.handleDefCauss) == 1 && e.handleDefCauss[0].ID == perceptron.ExtraHandleID) && e.causet.Meta() != nil && e.causet.Meta().IsCommonHandle 451 } 452 453 func (e *IndexLookUpInterlockingDirectorate) getRetTpsByHandle() []*types.FieldType { 454 var tps []*types.FieldType 455 if e.isCommonHandle() { 456 for _, handleDefCaus := range e.handleDefCauss { 457 tps = append(tps, handleDefCaus.RetType) 458 } 459 } else { 460 tps = []*types.FieldType{types.NewFieldType(allegrosql.TypeLonglong)} 461 } 462 if e.checHoTTexValue != nil { 463 tps = e.idxDefCausTps 464 } 465 return tps 466 } 467 468 // startIndexWorker launch a background goroutine to fetch handles, send the results to workCh. 469 func (e *IndexLookUpInterlockingDirectorate) startIndexWorker(ctx context.Context, ekvRanges []ekv.KeyRange, workCh chan<- *lookupBlockTask, initBatchSize int) error { 470 if e.runtimeStats != nil { 471 defCauslInterDirc := true 472 e.posetPosetDagPB.DefCauslectInterDircutionSummaries = &defCauslInterDirc 473 } 474 475 tracker := memory.NewTracker(memory.LabelForIndexWorker, -1) 476 tracker.AttachTo(e.memTracker) 477 var builder allegrosql.RequestBuilder 478 ekvReq, err := builder.SetKeyRanges(ekvRanges). 479 SetPosetDagRequest(e.posetPosetDagPB). 480 SetStartTS(e.startTS). 481 SetDesc(e.desc). 482 SetKeepOrder(e.keepOrder). 483 SetStreaming(e.indexStreaming). 484 SetFromStochastikVars(e.ctx.GetStochastikVars()). 485 SetMemTracker(tracker). 486 Build() 487 if err != nil { 488 return err 489 } 490 tps := e.getRetTpsByHandle() 491 // Since the first read only need handle information. So its returned defCaus is only 1. 492 result, err := allegrosql.SelectWithRuntimeStats(ctx, e.ctx, ekvReq, tps, e.feedback, getPhysicalCausetIDs(e.idxCausets), e.id) 493 if err != nil { 494 return err 495 } 496 result.Fetch(ctx) 497 worker := &indexWorker{ 498 idxLookup: e, 499 workCh: workCh, 500 finished: e.finished, 501 resultCh: e.resultCh, 502 keepOrder: e.keepOrder, 503 batchSize: initBatchSize, 504 checHoTTexValue: e.checHoTTexValue, 505 maxBatchSize: e.ctx.GetStochastikVars().IndexLookupSize, 506 maxChunkSize: e.maxChunkSize, 507 PushedLimit: e.PushedLimit, 508 } 509 if worker.batchSize > worker.maxBatchSize { 510 worker.batchSize = worker.maxBatchSize 511 } 512 e.idxWorkerWg.Add(1) 513 go func() { 514 defer trace.StartRegion(ctx, "IndexLookUpIndexWorker").End() 515 ctx1, cancel := context.WithCancel(ctx) 516 _, err := worker.fetchHandles(ctx1, result) 517 if err != nil { 518 e.feedback.Invalidate() 519 } 520 cancel() 521 if err := result.Close(); err != nil { 522 logutil.Logger(ctx).Error("close Select result failed", zap.Error(err)) 523 } 524 e.ctx.StoreQueryFeedback(e.feedback) 525 close(workCh) 526 close(e.resultCh) 527 e.idxWorkerWg.Done() 528 }() 529 return nil 530 } 531 532 // startBlockWorker launchs some background goroutines which pick tasks from workCh and execute the task. 533 func (e *IndexLookUpInterlockingDirectorate) startBlockWorker(ctx context.Context, workCh <-chan *lookupBlockTask) { 534 lookupConcurrencyLimit := e.ctx.GetStochastikVars().IndexLookupConcurrency() 535 e.tblWorkerWg.Add(lookupConcurrencyLimit) 536 for i := 0; i < lookupConcurrencyLimit; i++ { 537 workerID := i 538 worker := &blockWorker{ 539 idxLookup: e, 540 workCh: workCh, 541 finished: e.finished, 542 buildTblReader: e.buildBlockReader, 543 keepOrder: e.keepOrder, 544 handleIdx: e.handleIdx, 545 checHoTTexValue: e.checHoTTexValue, 546 memTracker: memory.NewTracker(workerID, -1), 547 } 548 worker.memTracker.AttachTo(e.memTracker) 549 ctx1, cancel := context.WithCancel(ctx) 550 go func() { 551 defer trace.StartRegion(ctx1, "IndexLookUpBlockWorker").End() 552 worker.pickAndInterDircTask(ctx1) 553 cancel() 554 e.tblWorkerWg.Done() 555 }() 556 } 557 } 558 559 func (e *IndexLookUpInterlockingDirectorate) buildBlockReader(ctx context.Context, handles []ekv.Handle) (InterlockingDirectorate, error) { 560 blockReaderInterDirc := &BlockReaderInterlockingDirectorate{ 561 baseInterlockingDirectorate: newBaseInterlockingDirectorate(e.ctx, e.schemaReplicant, 0), 562 causet: e.causet, 563 posetPosetDagPB: e.blockRequest, 564 startTS: e.startTS, 565 defCausumns: e.defCausumns, 566 streaming: e.blockStreaming, 567 feedback: statistics.NewQueryFeedback(0, nil, 0, false), 568 corDefCausInFilter: e.corDefCausInTblSide, 569 plans: e.tblCausets, 570 } 571 blockReaderInterDirc.buildVirtualDeferredCausetInfo() 572 blockReader, err := e.dataReaderBuilder.buildBlockReaderFromHandles(ctx, blockReaderInterDirc, handles) 573 if err != nil { 574 logutil.Logger(ctx).Error("build causet reader from handles failed", zap.Error(err)) 575 return nil, err 576 } 577 return blockReader, nil 578 } 579 580 // Close implements InterDirc Close interface. 581 func (e *IndexLookUpInterlockingDirectorate) Close() error { 582 if !e.workerStarted || e.finished == nil { 583 return nil 584 } 585 586 close(e.finished) 587 // Drain the resultCh and discard the result, in case that Next() doesn't fully 588 // consume the data, background worker still writing to resultCh and causet forever. 589 for range e.resultCh { 590 } 591 e.idxWorkerWg.Wait() 592 e.tblWorkerWg.Wait() 593 e.finished = nil 594 e.workerStarted = false 595 e.memTracker = nil 596 e.resultCurr = nil 597 return nil 598 } 599 600 // Next implements InterDirc Next interface. 601 func (e *IndexLookUpInterlockingDirectorate) Next(ctx context.Context, req *chunk.Chunk) error { 602 if !e.workerStarted { 603 if err := e.startWorkers(ctx, req.RequiredEvents()); err != nil { 604 return err 605 } 606 } 607 req.Reset() 608 for { 609 resultTask, err := e.getResultTask() 610 if err != nil { 611 return err 612 } 613 if resultTask == nil { 614 return nil 615 } 616 for resultTask.cursor < len(resultTask.rows) { 617 req.AppendEvent(resultTask.rows[resultTask.cursor]) 618 resultTask.cursor++ 619 if req.IsFull() { 620 return nil 621 } 622 } 623 } 624 } 625 626 func (e *IndexLookUpInterlockingDirectorate) getResultTask() (*lookupBlockTask, error) { 627 if e.resultCurr != nil && e.resultCurr.cursor < len(e.resultCurr.rows) { 628 return e.resultCurr, nil 629 } 630 task, ok := <-e.resultCh 631 if !ok { 632 return nil, nil 633 } 634 if err := <-task.doneCh; err != nil { 635 return nil, err 636 } 637 638 // Release the memory usage of last task before we handle a new task. 639 if e.resultCurr != nil { 640 e.resultCurr.memTracker.Consume(-e.resultCurr.memUsage) 641 } 642 e.resultCurr = task 643 return e.resultCurr, nil 644 } 645 646 // indexWorker is used by IndexLookUpInterlockingDirectorate to maintain index lookup background goroutines. 647 type indexWorker struct { 648 idxLookup *IndexLookUpInterlockingDirectorate 649 workCh chan<- *lookupBlockTask 650 finished <-chan struct{} 651 resultCh chan<- *lookupBlockTask 652 keepOrder bool 653 654 // batchSize is for lightweight startup. It will be increased exponentially until reaches the max batch size value. 655 batchSize int 656 maxBatchSize int 657 maxChunkSize int 658 659 // checHoTTexValue is used to check the consistency of the index data. 660 *checHoTTexValue 661 // PushedLimit is used to skip the preceding and tailing handles when Limit is sunk into IndexLookUpReader. 662 PushedLimit *causetembedded.PushedDownLimit 663 } 664 665 // fetchHandles fetches a batch of handles from index data and builds the index lookup tasks. 666 // The tasks are sent to workCh to be further processed by blockWorker, and sent to e.resultCh 667 // at the same time to keep data ordered. 668 func (w *indexWorker) fetchHandles(ctx context.Context, result allegrosql.SelectResult) (count uint64, err error) { 669 defer func() { 670 if r := recover(); r != nil { 671 buf := make([]byte, 4096) 672 stackSize := runtime.Stack(buf, false) 673 buf = buf[:stackSize] 674 logutil.Logger(ctx).Error("indexWorker in IndexLookupInterlockingDirectorate panicked", zap.String("stack", string(buf))) 675 err4Panic := errors.Errorf("%v", r) 676 doneCh := make(chan error, 1) 677 doneCh <- err4Panic 678 w.resultCh <- &lookupBlockTask{ 679 doneCh: doneCh, 680 } 681 if err != nil { 682 err = errors.Trace(err4Panic) 683 } 684 } 685 }() 686 retTps := w.idxLookup.getRetTpsByHandle() 687 chk := chunk.NewChunkWithCapacity(retTps, w.idxLookup.maxChunkSize) 688 for { 689 handles, retChunk, scannedKeys, err := w.extractTaskHandles(ctx, chk, result, count) 690 if err != nil { 691 doneCh := make(chan error, 1) 692 doneCh <- err 693 w.resultCh <- &lookupBlockTask{ 694 doneCh: doneCh, 695 } 696 return count, err 697 } 698 count += scannedKeys 699 if len(handles) == 0 { 700 return count, nil 701 } 702 task := w.buildBlockTask(handles, retChunk) 703 select { 704 case <-ctx.Done(): 705 return count, nil 706 case <-w.finished: 707 return count, nil 708 case w.workCh <- task: 709 w.resultCh <- task 710 } 711 } 712 } 713 714 func (w *indexWorker) extractTaskHandles(ctx context.Context, chk *chunk.Chunk, idxResult allegrosql.SelectResult, count uint64) ( 715 handles []ekv.Handle, retChk *chunk.Chunk, scannedKeys uint64, err error) { 716 var handleOffset []int 717 for i := range w.idxLookup.handleDefCauss { 718 handleOffset = append(handleOffset, chk.NumDefCauss()-len(w.idxLookup.handleDefCauss)+i) 719 } 720 if len(handleOffset) == 0 { 721 handleOffset = []int{chk.NumDefCauss() - 1} 722 } 723 handles = make([]ekv.Handle, 0, w.batchSize) 724 // PushedLimit would always be nil for ChecHoTTex or CheckBlock, we add this check just for insurance. 725 checkLimit := (w.PushedLimit != nil) && (w.checHoTTexValue == nil) 726 for len(handles) < w.batchSize { 727 requiredEvents := w.batchSize - len(handles) 728 if checkLimit { 729 if w.PushedLimit.Offset+w.PushedLimit.Count <= scannedKeys+count { 730 return handles, nil, scannedKeys, nil 731 } 732 leftCnt := w.PushedLimit.Offset + w.PushedLimit.Count - scannedKeys - count 733 if uint64(requiredEvents) > leftCnt { 734 requiredEvents = int(leftCnt) 735 } 736 } 737 chk.SetRequiredEvents(requiredEvents, w.maxChunkSize) 738 err = errors.Trace(idxResult.Next(ctx, chk)) 739 if err != nil { 740 return handles, nil, scannedKeys, err 741 } 742 if chk.NumEvents() == 0 { 743 return handles, retChk, scannedKeys, nil 744 } 745 for i := 0; i < chk.NumEvents(); i++ { 746 scannedKeys++ 747 if checkLimit { 748 if (count + scannedKeys) <= w.PushedLimit.Offset { 749 // Skip the preceding Offset handles. 750 continue 751 } 752 if (count + scannedKeys) > (w.PushedLimit.Offset + w.PushedLimit.Count) { 753 // Skip the handles after Offset+Count. 754 return handles, nil, scannedKeys, nil 755 } 756 } 757 h, err := w.idxLookup.getHandle(chk.GetEvent(i), handleOffset, w.idxLookup.isCommonHandle(), getHandleFromIndex) 758 if err != nil { 759 return handles, retChk, scannedKeys, err 760 } 761 handles = append(handles, h) 762 } 763 if w.checHoTTexValue != nil { 764 if retChk == nil { 765 retChk = chunk.NewChunkWithCapacity(w.idxDefCausTps, w.batchSize) 766 } 767 retChk.Append(chk, 0, chk.NumEvents()) 768 } 769 } 770 w.batchSize *= 2 771 if w.batchSize > w.maxBatchSize { 772 w.batchSize = w.maxBatchSize 773 } 774 return handles, retChk, scannedKeys, nil 775 } 776 777 func (w *indexWorker) buildBlockTask(handles []ekv.Handle, retChk *chunk.Chunk) *lookupBlockTask { 778 var indexOrder *ekv.HandleMap 779 var duplicatedIndexOrder *ekv.HandleMap 780 if w.keepOrder { 781 // Save the index order. 782 indexOrder = ekv.NewHandleMap() 783 for i, h := range handles { 784 indexOrder.Set(h, i) 785 } 786 } 787 788 if w.checHoTTexValue != nil { 789 // Save the index order. 790 indexOrder = ekv.NewHandleMap() 791 duplicatedIndexOrder = ekv.NewHandleMap() 792 for i, h := range handles { 793 if _, ok := indexOrder.Get(h); ok { 794 duplicatedIndexOrder.Set(h, i) 795 } else { 796 indexOrder.Set(h, i) 797 } 798 } 799 } 800 801 task := &lookupBlockTask{ 802 handles: handles, 803 indexOrder: indexOrder, 804 duplicatedIndexOrder: duplicatedIndexOrder, 805 idxEvents: retChk, 806 } 807 808 task.doneCh = make(chan error, 1) 809 return task 810 } 811 812 // blockWorker is used by IndexLookUpInterlockingDirectorate to maintain causet lookup background goroutines. 813 type blockWorker struct { 814 idxLookup *IndexLookUpInterlockingDirectorate 815 workCh <-chan *lookupBlockTask 816 finished <-chan struct{} 817 buildTblReader func(ctx context.Context, handles []ekv.Handle) (InterlockingDirectorate, error) 818 keepOrder bool 819 handleIdx []int 820 821 // memTracker is used to track the memory usage of this interlock. 822 memTracker *memory.Tracker 823 824 // checHoTTexValue is used to check the consistency of the index data. 825 *checHoTTexValue 826 } 827 828 // pickAndInterDircTask picks tasks from workCh, and execute them. 829 func (w *blockWorker) pickAndInterDircTask(ctx context.Context) { 830 var task *lookupBlockTask 831 var ok bool 832 defer func() { 833 if r := recover(); r != nil { 834 buf := make([]byte, 4096) 835 stackSize := runtime.Stack(buf, false) 836 buf = buf[:stackSize] 837 logutil.Logger(ctx).Error("blockWorker in IndexLookUpInterlockingDirectorate panicked", zap.String("stack", string(buf))) 838 task.doneCh <- errors.Errorf("%v", r) 839 } 840 }() 841 for { 842 // Don't check ctx.Done() on purpose. If background worker get the signal and all 843 // exit immediately, stochastik's goroutine doesn't know this and still calling Next(), 844 // it may causet reading task.doneCh forever. 845 select { 846 case task, ok = <-w.workCh: 847 if !ok { 848 return 849 } 850 case <-w.finished: 851 return 852 } 853 err := w.executeTask(ctx, task) 854 task.doneCh <- err 855 } 856 } 857 858 func (e *IndexLookUpInterlockingDirectorate) getHandle(event chunk.Event, handleIdx []int, 859 isCommonHandle bool, tp getHandleType) (handle ekv.Handle, err error) { 860 if isCommonHandle { 861 var handleEncoded []byte 862 var datums []types.Causet 863 for i, idx := range handleIdx { 864 // If the new defCauslation is enabled and the handle contains non-binary string, 865 // the handle in the index is encoded as "sortKey". So we cannot restore its 866 // original value(the primary key) here. 867 // We use a trick to avoid encoding the "sortKey" again by changing the charset 868 // defCauslation to `binary`. 869 // TODO: Add the restore value to the secondary index to remove this trick. 870 rtp := e.handleDefCauss[i].RetType 871 if defCauslate.NewDefCauslationEnabled() && rtp.EvalType() == types.ETString && 872 !allegrosql.HasBinaryFlag(rtp.Flag) && tp == getHandleFromIndex { 873 rtp = rtp.Clone() 874 rtp.DefCauslate = charset.DefCauslationBin 875 datums = append(datums, event.GetCauset(idx, rtp)) 876 continue 877 } 878 datums = append(datums, event.GetCauset(idx, e.handleDefCauss[i].RetType)) 879 } 880 if tp == getHandleFromBlock { 881 blockcodec.TruncateIndexValues(e.causet.Meta(), e.primaryKeyIndex, datums) 882 } 883 handleEncoded, err = codec.EncodeKey(e.ctx.GetStochastikVars().StmtCtx, nil, datums...) 884 if err != nil { 885 return nil, err 886 } 887 handle, err = ekv.NewCommonHandle(handleEncoded) 888 if err != nil { 889 return nil, err 890 } 891 } else { 892 if len(handleIdx) == 0 { 893 handle = ekv.IntHandle(event.GetInt64(0)) 894 } else { 895 handle = ekv.IntHandle(event.GetInt64(handleIdx[0])) 896 } 897 } 898 return 899 } 900 901 func (w *blockWorker) compareData(ctx context.Context, task *lookupBlockTask, blockReader InterlockingDirectorate) error { 902 chk := newFirstChunk(blockReader) 903 tblInfo := w.idxLookup.causet.Meta() 904 vals := make([]types.Causet, 0, len(w.idxTblDefCauss)) 905 for { 906 err := Next(ctx, blockReader, chk) 907 if err != nil { 908 return errors.Trace(err) 909 } 910 if chk.NumEvents() == 0 { 911 task.indexOrder.Range(func(h ekv.Handle, val interface{}) bool { 912 idxEvent := task.idxEvents.GetEvent(val.(int)) 913 err = errors.Errorf("handle %#v, index:%#v != record:%#v", h, idxEvent.GetCauset(0, w.idxDefCausTps[0]), nil) 914 return false 915 }) 916 if err != nil { 917 return err 918 } 919 break 920 } 921 922 iter := chunk.NewIterator4Chunk(chk) 923 for event := iter.Begin(); event != iter.End(); event = iter.Next() { 924 handle, err := w.idxLookup.getHandle(event, w.handleIdx, w.idxLookup.isCommonHandle(), getHandleFromBlock) 925 if err != nil { 926 return err 927 } 928 v, ok := task.indexOrder.Get(handle) 929 if !ok { 930 v, _ = task.duplicatedIndexOrder.Get(handle) 931 } 932 offset, _ := v.(int) 933 task.indexOrder.Delete(handle) 934 idxEvent := task.idxEvents.GetEvent(offset) 935 vals = vals[:0] 936 for i, defCaus := range w.idxTblDefCauss { 937 vals = append(vals, event.GetCauset(i, &defCaus.FieldType)) 938 } 939 blockcodec.TruncateIndexValues(tblInfo, w.idxLookup.index, vals) 940 for i, val := range vals { 941 defCaus := w.idxTblDefCauss[i] 942 tp := &defCaus.FieldType 943 ret := chunk.Compare(idxEvent, i, &val) 944 if ret != 0 { 945 return errors.Errorf("defCaus %s, handle %#v, index:%#v != record:%#v", defCaus.Name, handle, idxEvent.GetCauset(i, tp), val) 946 } 947 } 948 } 949 } 950 951 return nil 952 } 953 954 // executeTask executes the causet look up tasks. We will construct a causet reader and send request by handles. 955 // Then we hold the returning rows and finish this task. 956 func (w *blockWorker) executeTask(ctx context.Context, task *lookupBlockTask) error { 957 blockReader, err := w.buildTblReader(ctx, task.handles) 958 if err != nil { 959 logutil.Logger(ctx).Error("build causet reader failed", zap.Error(err)) 960 return err 961 } 962 defer terror.Call(blockReader.Close) 963 964 if w.checHoTTexValue != nil { 965 return w.compareData(ctx, task, blockReader) 966 } 967 968 task.memTracker = w.memTracker 969 memUsage := int64(cap(task.handles) * 8) 970 task.memUsage = memUsage 971 task.memTracker.Consume(memUsage) 972 handleCnt := len(task.handles) 973 task.rows = make([]chunk.Event, 0, handleCnt) 974 for { 975 chk := newFirstChunk(blockReader) 976 err = Next(ctx, blockReader, chk) 977 if err != nil { 978 logutil.Logger(ctx).Error("causet reader fetch next chunk failed", zap.Error(err)) 979 return err 980 } 981 if chk.NumEvents() == 0 { 982 break 983 } 984 memUsage = chk.MemoryUsage() 985 task.memUsage += memUsage 986 task.memTracker.Consume(memUsage) 987 iter := chunk.NewIterator4Chunk(chk) 988 for event := iter.Begin(); event != iter.End(); event = iter.Next() { 989 task.rows = append(task.rows, event) 990 } 991 } 992 993 defer trace.StartRegion(ctx, "IndexLookUpBlockCompute").End() 994 memUsage = int64(cap(task.rows)) * int64(unsafe.Sizeof(chunk.Event{})) 995 task.memUsage += memUsage 996 task.memTracker.Consume(memUsage) 997 if w.keepOrder { 998 task.rowIdx = make([]int, 0, len(task.rows)) 999 for i := range task.rows { 1000 handle, err := w.idxLookup.getHandle(task.rows[i], w.handleIdx, w.idxLookup.isCommonHandle(), getHandleFromBlock) 1001 if err != nil { 1002 return err 1003 } 1004 rowIdx, _ := task.indexOrder.Get(handle) 1005 task.rowIdx = append(task.rowIdx, rowIdx.(int)) 1006 } 1007 memUsage = int64(cap(task.rowIdx) * 4) 1008 task.memUsage += memUsage 1009 task.memTracker.Consume(memUsage) 1010 sort.Sort(task) 1011 } 1012 1013 if handleCnt != len(task.rows) && !soliton.HasCancelled(ctx) { 1014 if len(w.idxLookup.tblCausets) == 1 { 1015 obtainedHandlesMap := ekv.NewHandleMap() 1016 for _, event := range task.rows { 1017 handle, err := w.idxLookup.getHandle(event, w.handleIdx, w.idxLookup.isCommonHandle(), getHandleFromBlock) 1018 if err != nil { 1019 return err 1020 } 1021 obtainedHandlesMap.Set(handle, true) 1022 } 1023 1024 logutil.Logger(ctx).Error("inconsistent index handles", zap.String("index", w.idxLookup.index.Name.O), 1025 zap.Int("index_cnt", handleCnt), zap.Int("block_cnt", len(task.rows)), 1026 zap.String("missing_handles", fmt.Sprint(GetLackHandles(task.handles, obtainedHandlesMap))), 1027 zap.String("total_handles", fmt.Sprint(task.handles))) 1028 1029 // causet scan in double read can never has conditions according to convertToIndexScan. 1030 // if this causet scan has no condition, the number of rows it returns must equal to the length of handles. 1031 return errors.Errorf("inconsistent index %s handle count %d isn't equal to value count %d", 1032 w.idxLookup.index.Name.O, handleCnt, len(task.rows)) 1033 } 1034 } 1035 1036 return nil 1037 } 1038 1039 // GetLackHandles gets the handles in expectedHandles but not in obtainedHandlesMap. 1040 func GetLackHandles(expectedHandles []ekv.Handle, obtainedHandlesMap *ekv.HandleMap) []ekv.Handle { 1041 diffCnt := len(expectedHandles) - obtainedHandlesMap.Len() 1042 diffHandles := make([]ekv.Handle, 0, diffCnt) 1043 var cnt int 1044 for _, handle := range expectedHandles { 1045 isExist := false 1046 if _, ok := obtainedHandlesMap.Get(handle); ok { 1047 obtainedHandlesMap.Delete(handle) 1048 isExist = true 1049 } 1050 if !isExist { 1051 diffHandles = append(diffHandles, handle) 1052 cnt++ 1053 if cnt == diffCnt { 1054 break 1055 } 1056 } 1057 } 1058 1059 return diffHandles 1060 } 1061 1062 func getPhysicalCausetIDs(plans []causetembedded.PhysicalCauset) []int { 1063 planIDs := make([]int, 0, len(plans)) 1064 for _, p := range plans { 1065 planIDs = append(planIDs, p.ID()) 1066 } 1067 return planIDs 1068 }