github.com/whtcorpsinc/milevadb-prod@v0.0.0-20211104133533-f57f4be3b597/interlock/join.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 "bytes" 18 "context" 19 "fmt" 20 "runtime/trace" 21 "strconv" 22 "sync" 23 "sync/atomic" 24 "time" 25 26 "github.com/whtcorpsinc/BerolinaSQL/terror" 27 "github.com/whtcorpsinc/errors" 28 "github.com/whtcorpsinc/failpoint" 29 causetembedded "github.com/whtcorpsinc/milevadb/causet/embedded" 30 "github.com/whtcorpsinc/milevadb/config" 31 "github.com/whtcorpsinc/milevadb/memex" 32 "github.com/whtcorpsinc/milevadb/soliton" 33 "github.com/whtcorpsinc/milevadb/soliton/bitmap" 34 "github.com/whtcorpsinc/milevadb/soliton/chunk" 35 "github.com/whtcorpsinc/milevadb/soliton/codec" 36 "github.com/whtcorpsinc/milevadb/soliton/disk" 37 "github.com/whtcorpsinc/milevadb/soliton/execdetails" 38 "github.com/whtcorpsinc/milevadb/soliton/memory" 39 "github.com/whtcorpsinc/milevadb/stochastikctx" 40 "github.com/whtcorpsinc/milevadb/types" 41 ) 42 43 var ( 44 _ InterlockingDirectorate = &HashJoinInterDirc{} 45 _ InterlockingDirectorate = &NestedLoopApplyInterDirc{} 46 ) 47 48 // HashJoinInterDirc implements the hash join algorithm. 49 type HashJoinInterDirc struct { 50 baseInterlockingDirectorate 51 52 probeSideInterDirc InterlockingDirectorate 53 buildSideInterDirc InterlockingDirectorate 54 buildSideEstCount float64 55 outerFilter memex.CNFExprs 56 probeKeys []*memex.DeferredCauset 57 buildKeys []*memex.DeferredCauset 58 isNullEQ []bool 59 probeTypes []*types.FieldType 60 buildTypes []*types.FieldType 61 62 // concurrency is the number of partition, build and join workers. 63 concurrency uint 64 rowContainer *hashEventContainer 65 buildFinished chan error 66 67 // closeCh add a dagger for closing interlock. 68 closeCh chan struct{} 69 joinType causetembedded.JoinType 70 requiredEvents int64 71 72 // We build individual joiner for each join worker when use chunk-based 73 // execution, to avoid the concurrency of joiner.chk and joiner.selected. 74 joiners []joiner 75 76 probeChkResourceCh chan *probeChkResource 77 probeResultChs []chan *chunk.Chunk 78 joinChkResourceCh []chan *chunk.Chunk 79 joinResultCh chan *hashjoinWorkerResult 80 81 memTracker *memory.Tracker // track memory usage. 82 diskTracker *disk.Tracker // track disk usage. 83 84 outerMatchedStatus []*bitmap.ConcurrentBitmap 85 useOuterToBuild bool 86 87 prepared bool 88 isOuterJoin bool 89 90 // joinWorkerWaitGroup is for sync multiple join workers. 91 joinWorkerWaitGroup sync.WaitGroup 92 finished atomic.Value 93 94 stats *hashJoinRuntimeStats 95 } 96 97 // probeChkResource stores the result of the join probe side fetch worker, 98 // `dest` is for Chunk reuse: after join workers process the probe side chunk which is read from `dest`, 99 // they'll causetstore the used chunk as `chk`, and then the probe side fetch worker will put new data into `chk` and write `chk` into dest. 100 type probeChkResource struct { 101 chk *chunk.Chunk 102 dest chan<- *chunk.Chunk 103 } 104 105 // hashjoinWorkerResult stores the result of join workers, 106 // `src` is for Chunk reuse: the main goroutine will get the join result chunk `chk`, 107 // and push `chk` into `src` after processing, join worker goroutines get the empty chunk from `src` 108 // and push new data into this chunk. 109 type hashjoinWorkerResult struct { 110 chk *chunk.Chunk 111 err error 112 src chan<- *chunk.Chunk 113 } 114 115 // Close implements the InterlockingDirectorate Close interface. 116 func (e *HashJoinInterDirc) Close() error { 117 close(e.closeCh) 118 e.finished.CausetStore(true) 119 if e.prepared { 120 if e.buildFinished != nil { 121 for range e.buildFinished { 122 } 123 } 124 if e.joinResultCh != nil { 125 for range e.joinResultCh { 126 } 127 } 128 if e.probeChkResourceCh != nil { 129 close(e.probeChkResourceCh) 130 for range e.probeChkResourceCh { 131 } 132 } 133 for i := range e.probeResultChs { 134 for range e.probeResultChs[i] { 135 } 136 } 137 for i := range e.joinChkResourceCh { 138 close(e.joinChkResourceCh[i]) 139 for range e.joinChkResourceCh[i] { 140 } 141 } 142 e.probeChkResourceCh = nil 143 e.joinChkResourceCh = nil 144 terror.Call(e.rowContainer.Close) 145 } 146 e.outerMatchedStatus = e.outerMatchedStatus[:0] 147 148 if e.stats != nil && e.rowContainer != nil { 149 e.stats.hashStat = e.rowContainer.stat 150 } 151 err := e.baseInterlockingDirectorate.Close() 152 return err 153 } 154 155 // Open implements the InterlockingDirectorate Open interface. 156 func (e *HashJoinInterDirc) Open(ctx context.Context) error { 157 if err := e.baseInterlockingDirectorate.Open(ctx); err != nil { 158 return err 159 } 160 161 e.prepared = false 162 e.memTracker = memory.NewTracker(e.id, -1) 163 e.memTracker.AttachTo(e.ctx.GetStochastikVars().StmtCtx.MemTracker) 164 165 e.diskTracker = disk.NewTracker(e.id, -1) 166 e.diskTracker.AttachTo(e.ctx.GetStochastikVars().StmtCtx.DiskTracker) 167 168 e.closeCh = make(chan struct{}) 169 e.finished.CausetStore(false) 170 e.joinWorkerWaitGroup = sync.WaitGroup{} 171 172 if e.probeTypes == nil { 173 e.probeTypes = retTypes(e.probeSideInterDirc) 174 } 175 if e.buildTypes == nil { 176 e.buildTypes = retTypes(e.buildSideInterDirc) 177 } 178 if e.runtimeStats != nil { 179 e.stats = &hashJoinRuntimeStats{ 180 concurrent: cap(e.joiners), 181 } 182 e.ctx.GetStochastikVars().StmtCtx.RuntimeStatsDefCausl.RegisterStats(e.id, e.stats) 183 } 184 return nil 185 } 186 187 // fetchProbeSideChunks get chunks from fetches chunks from the big causet in a background goroutine 188 // and sends the chunks to multiple channels which will be read by multiple join workers. 189 func (e *HashJoinInterDirc) fetchProbeSideChunks(ctx context.Context) { 190 hasWaitedForBuild := false 191 for { 192 if e.finished.Load().(bool) { 193 return 194 } 195 196 var probeSideResource *probeChkResource 197 var ok bool 198 select { 199 case <-e.closeCh: 200 return 201 case probeSideResource, ok = <-e.probeChkResourceCh: 202 if !ok { 203 return 204 } 205 } 206 probeSideResult := probeSideResource.chk 207 if e.isOuterJoin { 208 required := int(atomic.LoadInt64(&e.requiredEvents)) 209 probeSideResult.SetRequiredEvents(required, e.maxChunkSize) 210 } 211 err := Next(ctx, e.probeSideInterDirc, probeSideResult) 212 if err != nil { 213 e.joinResultCh <- &hashjoinWorkerResult{ 214 err: err, 215 } 216 return 217 } 218 if !hasWaitedForBuild { 219 if probeSideResult.NumEvents() == 0 && !e.useOuterToBuild { 220 e.finished.CausetStore(true) 221 return 222 } 223 emptyBuild, buildErr := e.wait4BuildSide() 224 if buildErr != nil { 225 e.joinResultCh <- &hashjoinWorkerResult{ 226 err: buildErr, 227 } 228 return 229 } else if emptyBuild { 230 return 231 } 232 hasWaitedForBuild = true 233 } 234 235 if probeSideResult.NumEvents() == 0 { 236 return 237 } 238 239 probeSideResource.dest <- probeSideResult 240 } 241 } 242 243 func (e *HashJoinInterDirc) wait4BuildSide() (emptyBuild bool, err error) { 244 select { 245 case <-e.closeCh: 246 return true, nil 247 case err := <-e.buildFinished: 248 if err != nil { 249 return false, err 250 } 251 } 252 if e.rowContainer.Len() == uint64(0) && (e.joinType == causetembedded.InnerJoin || e.joinType == causetembedded.SemiJoin) { 253 return true, nil 254 } 255 return false, nil 256 } 257 258 // fetchBuildSideEvents fetches all rows from build side interlock, and append them 259 // to e.buildSideResult. 260 func (e *HashJoinInterDirc) fetchBuildSideEvents(ctx context.Context, chkCh chan<- *chunk.Chunk, doneCh <-chan struct{}) { 261 defer close(chkCh) 262 var err error 263 for { 264 if e.finished.Load().(bool) { 265 return 266 } 267 chk := chunk.NewChunkWithCapacity(e.buildSideInterDirc.base().retFieldTypes, e.ctx.GetStochastikVars().MaxChunkSize) 268 err = Next(ctx, e.buildSideInterDirc, chk) 269 if err != nil { 270 e.buildFinished <- errors.Trace(err) 271 return 272 } 273 failpoint.Inject("errorFetchBuildSideEventsMockOOMPanic", nil) 274 if chk.NumEvents() == 0 { 275 return 276 } 277 select { 278 case <-doneCh: 279 return 280 case <-e.closeCh: 281 return 282 case chkCh <- chk: 283 } 284 } 285 } 286 287 func (e *HashJoinInterDirc) initializeForProbe() { 288 // e.probeResultChs is for transmitting the chunks which causetstore the data of 289 // probeSideInterDirc, it'll be written by probe side worker goroutine, and read by join 290 // workers. 291 e.probeResultChs = make([]chan *chunk.Chunk, e.concurrency) 292 for i := uint(0); i < e.concurrency; i++ { 293 e.probeResultChs[i] = make(chan *chunk.Chunk, 1) 294 } 295 296 // e.probeChkResourceCh is for transmitting the used probeSideInterDirc chunks from 297 // join workers to probeSideInterDirc worker. 298 e.probeChkResourceCh = make(chan *probeChkResource, e.concurrency) 299 for i := uint(0); i < e.concurrency; i++ { 300 e.probeChkResourceCh <- &probeChkResource{ 301 chk: newFirstChunk(e.probeSideInterDirc), 302 dest: e.probeResultChs[i], 303 } 304 } 305 306 // e.joinChkResourceCh is for transmitting the reused join result chunks 307 // from the main thread to join worker goroutines. 308 e.joinChkResourceCh = make([]chan *chunk.Chunk, e.concurrency) 309 for i := uint(0); i < e.concurrency; i++ { 310 e.joinChkResourceCh[i] = make(chan *chunk.Chunk, 1) 311 e.joinChkResourceCh[i] <- newFirstChunk(e) 312 } 313 314 // e.joinResultCh is for transmitting the join result chunks to the main 315 // thread. 316 e.joinResultCh = make(chan *hashjoinWorkerResult, e.concurrency+1) 317 } 318 319 func (e *HashJoinInterDirc) fetchAndProbeHashBlock(ctx context.Context) { 320 e.initializeForProbe() 321 e.joinWorkerWaitGroup.Add(1) 322 go soliton.WithRecovery(func() { 323 defer trace.StartRegion(ctx, "HashJoinProbeSideFetcher").End() 324 e.fetchProbeSideChunks(ctx) 325 }, e.handleProbeSideFetcherPanic) 326 327 probeKeyDefCausIdx := make([]int, len(e.probeKeys)) 328 for i := range e.probeKeys { 329 probeKeyDefCausIdx[i] = e.probeKeys[i].Index 330 } 331 332 // Start e.concurrency join workers to probe hash causet and join build side and 333 // probe side rows. 334 for i := uint(0); i < e.concurrency; i++ { 335 e.joinWorkerWaitGroup.Add(1) 336 workID := i 337 go soliton.WithRecovery(func() { 338 defer trace.StartRegion(ctx, "HashJoinWorker").End() 339 e.runJoinWorker(workID, probeKeyDefCausIdx) 340 }, e.handleJoinWorkerPanic) 341 } 342 go soliton.WithRecovery(e.waitJoinWorkersAndCloseResultChan, nil) 343 } 344 345 func (e *HashJoinInterDirc) handleProbeSideFetcherPanic(r interface{}) { 346 for i := range e.probeResultChs { 347 close(e.probeResultChs[i]) 348 } 349 if r != nil { 350 e.joinResultCh <- &hashjoinWorkerResult{err: errors.Errorf("%v", r)} 351 } 352 e.joinWorkerWaitGroup.Done() 353 } 354 355 func (e *HashJoinInterDirc) handleJoinWorkerPanic(r interface{}) { 356 if r != nil { 357 e.joinResultCh <- &hashjoinWorkerResult{err: errors.Errorf("%v", r)} 358 } 359 e.joinWorkerWaitGroup.Done() 360 } 361 362 // Concurrently handling unmatched rows from the hash causet 363 func (e *HashJoinInterDirc) handleUnmatchedEventsFromHashBlock(workerID uint) { 364 ok, joinResult := e.getNewJoinResult(workerID) 365 if !ok { 366 return 367 } 368 numChks := e.rowContainer.NumChunks() 369 for i := int(workerID); i < numChks; i += int(e.concurrency) { 370 chk, err := e.rowContainer.GetChunk(i) 371 if err != nil { 372 // Catching the error and send it 373 joinResult.err = err 374 e.joinResultCh <- joinResult 375 return 376 } 377 for j := 0; j < chk.NumEvents(); j++ { 378 if !e.outerMatchedStatus[i].UnsafeIsSet(j) { // process unmatched outer rows 379 e.joiners[workerID].onMissMatch(false, chk.GetEvent(j), joinResult.chk) 380 } 381 if joinResult.chk.IsFull() { 382 e.joinResultCh <- joinResult 383 ok, joinResult = e.getNewJoinResult(workerID) 384 if !ok { 385 return 386 } 387 } 388 } 389 } 390 391 if joinResult == nil { 392 return 393 } else if joinResult.err != nil || (joinResult.chk != nil && joinResult.chk.NumEvents() > 0) { 394 e.joinResultCh <- joinResult 395 } 396 } 397 398 func (e *HashJoinInterDirc) waitJoinWorkersAndCloseResultChan() { 399 e.joinWorkerWaitGroup.Wait() 400 if e.useOuterToBuild { 401 // Concurrently handling unmatched rows from the hash causet at the tail 402 for i := uint(0); i < e.concurrency; i++ { 403 var workerID = i 404 e.joinWorkerWaitGroup.Add(1) 405 go soliton.WithRecovery(func() { e.handleUnmatchedEventsFromHashBlock(workerID) }, e.handleJoinWorkerPanic) 406 } 407 e.joinWorkerWaitGroup.Wait() 408 } 409 close(e.joinResultCh) 410 } 411 412 func (e *HashJoinInterDirc) runJoinWorker(workerID uint, probeKeyDefCausIdx []int) { 413 probeTime := int64(0) 414 if e.stats != nil { 415 start := time.Now() 416 defer func() { 417 t := time.Since(start) 418 atomic.AddInt64(&e.stats.probe, probeTime) 419 atomic.AddInt64(&e.stats.fetchAndProbe, int64(t)) 420 e.stats.setMaxFetchAndProbeTime(int64(t)) 421 }() 422 } 423 424 var ( 425 probeSideResult *chunk.Chunk 426 selected = make([]bool, 0, chunk.InitialCapacity) 427 ) 428 ok, joinResult := e.getNewJoinResult(workerID) 429 if !ok { 430 return 431 } 432 433 // Read and filter probeSideResult, and join the probeSideResult with the build side rows. 434 emptyProbeSideResult := &probeChkResource{ 435 dest: e.probeResultChs[workerID], 436 } 437 hCtx := &hashContext{ 438 allTypes: e.probeTypes, 439 keyDefCausIdx: probeKeyDefCausIdx, 440 } 441 for ok := true; ok; { 442 if e.finished.Load().(bool) { 443 break 444 } 445 select { 446 case <-e.closeCh: 447 return 448 case probeSideResult, ok = <-e.probeResultChs[workerID]: 449 } 450 if !ok { 451 break 452 } 453 start := time.Now() 454 if e.useOuterToBuild { 455 ok, joinResult = e.join2ChunkForOuterHashJoin(workerID, probeSideResult, hCtx, joinResult) 456 } else { 457 ok, joinResult = e.join2Chunk(workerID, probeSideResult, hCtx, joinResult, selected) 458 } 459 probeTime += int64(time.Since(start)) 460 if !ok { 461 break 462 } 463 probeSideResult.Reset() 464 emptyProbeSideResult.chk = probeSideResult 465 e.probeChkResourceCh <- emptyProbeSideResult 466 } 467 // note joinResult.chk may be nil when getNewJoinResult fails in loops 468 if joinResult == nil { 469 return 470 } else if joinResult.err != nil || (joinResult.chk != nil && joinResult.chk.NumEvents() > 0) { 471 e.joinResultCh <- joinResult 472 } else if joinResult.chk != nil && joinResult.chk.NumEvents() == 0 { 473 e.joinChkResourceCh[workerID] <- joinResult.chk 474 } 475 } 476 477 func (e *HashJoinInterDirc) joinMatchedProbeSideEvent2ChunkForOuterHashJoin(workerID uint, probeKey uint64, probeSideEvent chunk.Event, hCtx *hashContext, 478 joinResult *hashjoinWorkerResult) (bool, *hashjoinWorkerResult) { 479 buildSideEvents, rowsPtrs, err := e.rowContainer.GetMatchedEventsAndPtrs(probeKey, probeSideEvent, hCtx) 480 if err != nil { 481 joinResult.err = err 482 return false, joinResult 483 } 484 if len(buildSideEvents) == 0 { 485 return true, joinResult 486 } 487 488 iter := chunk.NewIterator4Slice(buildSideEvents) 489 var outerMatchStatus []outerEventStatusFlag 490 rowIdx := 0 491 for iter.Begin(); iter.Current() != iter.End(); { 492 outerMatchStatus, err = e.joiners[workerID].tryToMatchOuters(iter, probeSideEvent, joinResult.chk, outerMatchStatus) 493 if err != nil { 494 joinResult.err = err 495 return false, joinResult 496 } 497 for i := range outerMatchStatus { 498 if outerMatchStatus[i] == outerEventMatched { 499 e.outerMatchedStatus[rowsPtrs[rowIdx+i].ChkIdx].Set(int(rowsPtrs[rowIdx+i].EventIdx)) 500 } 501 } 502 rowIdx += len(outerMatchStatus) 503 if joinResult.chk.IsFull() { 504 e.joinResultCh <- joinResult 505 ok, joinResult := e.getNewJoinResult(workerID) 506 if !ok { 507 return false, joinResult 508 } 509 } 510 } 511 return true, joinResult 512 } 513 func (e *HashJoinInterDirc) joinMatchedProbeSideEvent2Chunk(workerID uint, probeKey uint64, probeSideEvent chunk.Event, hCtx *hashContext, 514 joinResult *hashjoinWorkerResult) (bool, *hashjoinWorkerResult) { 515 buildSideEvents, _, err := e.rowContainer.GetMatchedEventsAndPtrs(probeKey, probeSideEvent, hCtx) 516 if err != nil { 517 joinResult.err = err 518 return false, joinResult 519 } 520 if len(buildSideEvents) == 0 { 521 e.joiners[workerID].onMissMatch(false, probeSideEvent, joinResult.chk) 522 return true, joinResult 523 } 524 iter := chunk.NewIterator4Slice(buildSideEvents) 525 hasMatch, hasNull := false, false 526 for iter.Begin(); iter.Current() != iter.End(); { 527 matched, isNull, err := e.joiners[workerID].tryToMatchInners(probeSideEvent, iter, joinResult.chk) 528 if err != nil { 529 joinResult.err = err 530 return false, joinResult 531 } 532 hasMatch = hasMatch || matched 533 hasNull = hasNull || isNull 534 535 if joinResult.chk.IsFull() { 536 e.joinResultCh <- joinResult 537 ok, joinResult := e.getNewJoinResult(workerID) 538 if !ok { 539 return false, joinResult 540 } 541 } 542 } 543 if !hasMatch { 544 e.joiners[workerID].onMissMatch(hasNull, probeSideEvent, joinResult.chk) 545 } 546 return true, joinResult 547 } 548 549 func (e *HashJoinInterDirc) getNewJoinResult(workerID uint) (bool, *hashjoinWorkerResult) { 550 joinResult := &hashjoinWorkerResult{ 551 src: e.joinChkResourceCh[workerID], 552 } 553 ok := true 554 select { 555 case <-e.closeCh: 556 ok = false 557 case joinResult.chk, ok = <-e.joinChkResourceCh[workerID]: 558 } 559 return ok, joinResult 560 } 561 562 func (e *HashJoinInterDirc) join2Chunk(workerID uint, probeSideChk *chunk.Chunk, hCtx *hashContext, joinResult *hashjoinWorkerResult, 563 selected []bool) (ok bool, _ *hashjoinWorkerResult) { 564 var err error 565 selected, err = memex.VectorizedFilter(e.ctx, e.outerFilter, chunk.NewIterator4Chunk(probeSideChk), selected) 566 if err != nil { 567 joinResult.err = err 568 return false, joinResult 569 } 570 571 hCtx.initHash(probeSideChk.NumEvents()) 572 for keyIdx, i := range hCtx.keyDefCausIdx { 573 ignoreNull := len(e.isNullEQ) > keyIdx && e.isNullEQ[keyIdx] 574 err = codec.HashChunkSelected(e.rowContainer.sc, hCtx.hashVals, probeSideChk, hCtx.allTypes[i], i, hCtx.buf, hCtx.hasNull, selected, ignoreNull) 575 if err != nil { 576 joinResult.err = err 577 return false, joinResult 578 } 579 } 580 581 for i := range selected { 582 if !selected[i] || hCtx.hasNull[i] { // process unmatched probe side rows 583 e.joiners[workerID].onMissMatch(false, probeSideChk.GetEvent(i), joinResult.chk) 584 } else { // process matched probe side rows 585 probeKey, probeEvent := hCtx.hashVals[i].Sum64(), probeSideChk.GetEvent(i) 586 ok, joinResult = e.joinMatchedProbeSideEvent2Chunk(workerID, probeKey, probeEvent, hCtx, joinResult) 587 if !ok { 588 return false, joinResult 589 } 590 } 591 if joinResult.chk.IsFull() { 592 e.joinResultCh <- joinResult 593 ok, joinResult = e.getNewJoinResult(workerID) 594 if !ok { 595 return false, joinResult 596 } 597 } 598 } 599 return true, joinResult 600 } 601 602 // join2ChunkForOuterHashJoin joins chunks when using the outer to build a hash causet (refer to outer hash join) 603 func (e *HashJoinInterDirc) join2ChunkForOuterHashJoin(workerID uint, probeSideChk *chunk.Chunk, hCtx *hashContext, joinResult *hashjoinWorkerResult) (ok bool, _ *hashjoinWorkerResult) { 604 hCtx.initHash(probeSideChk.NumEvents()) 605 for _, i := range hCtx.keyDefCausIdx { 606 err := codec.HashChunkDeferredCausets(e.rowContainer.sc, hCtx.hashVals, probeSideChk, hCtx.allTypes[i], i, hCtx.buf, hCtx.hasNull) 607 if err != nil { 608 joinResult.err = err 609 return false, joinResult 610 } 611 } 612 for i := 0; i < probeSideChk.NumEvents(); i++ { 613 probeKey, probeEvent := hCtx.hashVals[i].Sum64(), probeSideChk.GetEvent(i) 614 ok, joinResult = e.joinMatchedProbeSideEvent2ChunkForOuterHashJoin(workerID, probeKey, probeEvent, hCtx, joinResult) 615 if !ok { 616 return false, joinResult 617 } 618 if joinResult.chk.IsFull() { 619 e.joinResultCh <- joinResult 620 ok, joinResult = e.getNewJoinResult(workerID) 621 if !ok { 622 return false, joinResult 623 } 624 } 625 } 626 return true, joinResult 627 } 628 629 // Next implements the InterlockingDirectorate Next interface. 630 // hash join constructs the result following these steps: 631 // step 1. fetch data from build side child and build a hash causet; 632 // step 2. fetch data from probe child in a background goroutine and probe the hash causet in multiple join workers. 633 func (e *HashJoinInterDirc) Next(ctx context.Context, req *chunk.Chunk) (err error) { 634 if !e.prepared { 635 e.buildFinished = make(chan error, 1) 636 go soliton.WithRecovery(func() { 637 defer trace.StartRegion(ctx, "HashJoinHashBlockBuilder").End() 638 e.fetchAndBuildHashBlock(ctx) 639 }, e.handleFetchAndBuildHashBlockPanic) 640 e.fetchAndProbeHashBlock(ctx) 641 e.prepared = true 642 } 643 if e.isOuterJoin { 644 atomic.StoreInt64(&e.requiredEvents, int64(req.RequiredEvents())) 645 } 646 req.Reset() 647 648 result, ok := <-e.joinResultCh 649 if !ok { 650 return nil 651 } 652 if result.err != nil { 653 e.finished.CausetStore(true) 654 return result.err 655 } 656 req.SwapDeferredCausets(result.chk) 657 result.src <- result.chk 658 return nil 659 } 660 661 func (e *HashJoinInterDirc) handleFetchAndBuildHashBlockPanic(r interface{}) { 662 if r != nil { 663 e.buildFinished <- errors.Errorf("%v", r) 664 } 665 close(e.buildFinished) 666 } 667 668 func (e *HashJoinInterDirc) fetchAndBuildHashBlock(ctx context.Context) { 669 if e.stats != nil { 670 start := time.Now() 671 defer func() { 672 e.stats.fetchAndBuildHashBlock = time.Since(start) 673 }() 674 } 675 // buildSideResultCh transfers build side chunk from build side fetch to build hash causet. 676 buildSideResultCh := make(chan *chunk.Chunk, 1) 677 doneCh := make(chan struct{}) 678 fetchBuildSideEventsOk := make(chan error, 1) 679 go soliton.WithRecovery( 680 func() { 681 defer trace.StartRegion(ctx, "HashJoinBuildSideFetcher").End() 682 e.fetchBuildSideEvents(ctx, buildSideResultCh, doneCh) 683 }, 684 func(r interface{}) { 685 if r != nil { 686 fetchBuildSideEventsOk <- errors.Errorf("%v", r) 687 } 688 close(fetchBuildSideEventsOk) 689 }, 690 ) 691 692 // TODO: Parallel build hash causet. Currently not support because `unsafeHashBlock` is not thread-safe. 693 err := e.buildHashBlockForList(buildSideResultCh) 694 if err != nil { 695 e.buildFinished <- errors.Trace(err) 696 close(doneCh) 697 } 698 // Wait fetchBuildSideEvents be finished. 699 // 1. if buildHashBlockForList fails 700 // 2. if probeSideResult.NumEvents() == 0, fetchProbeSideChunks will not wait for the build side. 701 for range buildSideResultCh { 702 } 703 // Check whether err is nil to avoid sending redundant error into buildFinished. 704 if err == nil { 705 if err = <-fetchBuildSideEventsOk; err != nil { 706 e.buildFinished <- err 707 } 708 } 709 } 710 711 // buildHashBlockForList builds hash causet from `list`. 712 func (e *HashJoinInterDirc) buildHashBlockForList(buildSideResultCh <-chan *chunk.Chunk) error { 713 buildKeyDefCausIdx := make([]int, len(e.buildKeys)) 714 for i := range e.buildKeys { 715 buildKeyDefCausIdx[i] = e.buildKeys[i].Index 716 } 717 hCtx := &hashContext{ 718 allTypes: e.buildTypes, 719 keyDefCausIdx: buildKeyDefCausIdx, 720 } 721 var err error 722 var selected []bool 723 e.rowContainer = newHashEventContainer(e.ctx, int(e.buildSideEstCount), hCtx) 724 e.rowContainer.GetMemTracker().AttachTo(e.memTracker) 725 e.rowContainer.GetMemTracker().SetLabel(memory.LabelForBuildSideResult) 726 e.rowContainer.GetDiskTracker().AttachTo(e.diskTracker) 727 e.rowContainer.GetDiskTracker().SetLabel(memory.LabelForBuildSideResult) 728 if config.GetGlobalConfig().OOMUseTmpStorage { 729 actionSpill := e.rowContainer.CausetActionSpill() 730 failpoint.Inject("testEventContainerSpill", func(val failpoint.Value) { 731 if val.(bool) { 732 actionSpill = e.rowContainer.rowContainer.CausetActionSpillForTest() 733 defer actionSpill.(*chunk.SpillDiskCausetAction).WaitForTest() 734 } 735 }) 736 e.ctx.GetStochastikVars().StmtCtx.MemTracker.FallbackOldAndSetNewCausetAction(actionSpill) 737 } 738 for chk := range buildSideResultCh { 739 if e.finished.Load().(bool) { 740 return nil 741 } 742 if !e.useOuterToBuild { 743 err = e.rowContainer.PutChunk(chk, e.isNullEQ) 744 } else { 745 var bitMap = bitmap.NewConcurrentBitmap(chk.NumEvents()) 746 e.outerMatchedStatus = append(e.outerMatchedStatus, bitMap) 747 e.memTracker.Consume(bitMap.BytesConsumed()) 748 if len(e.outerFilter) == 0 { 749 err = e.rowContainer.PutChunk(chk, e.isNullEQ) 750 } else { 751 selected, err = memex.VectorizedFilter(e.ctx, e.outerFilter, chunk.NewIterator4Chunk(chk), selected) 752 if err != nil { 753 return err 754 } 755 err = e.rowContainer.PutChunkSelected(chk, selected, e.isNullEQ) 756 } 757 } 758 if err != nil { 759 return err 760 } 761 } 762 return nil 763 } 764 765 // NestedLoopApplyInterDirc is the interlock for apply. 766 type NestedLoopApplyInterDirc struct { 767 baseInterlockingDirectorate 768 769 ctx stochastikctx.Context 770 innerEvents []chunk.Event 771 cursor int 772 innerInterDirc InterlockingDirectorate 773 outerInterDirc InterlockingDirectorate 774 innerFilter memex.CNFExprs 775 outerFilter memex.CNFExprs 776 777 joiner joiner 778 779 cache *applyCache 780 canUseCache bool 781 cacheHitCounter int 782 cacheAccessCounter int 783 784 outerSchema []*memex.CorrelatedDeferredCauset 785 786 outerChunk *chunk.Chunk 787 outerChunkCursor int 788 outerSelected []bool 789 innerList *chunk.List 790 innerChunk *chunk.Chunk 791 innerSelected []bool 792 innerIter chunk.Iterator 793 outerEvent *chunk.Event 794 hasMatch bool 795 hasNull bool 796 797 outer bool 798 799 memTracker *memory.Tracker // track memory usage. 800 } 801 802 // Close implements the InterlockingDirectorate interface. 803 func (e *NestedLoopApplyInterDirc) Close() error { 804 e.innerEvents = nil 805 e.memTracker = nil 806 if e.runtimeStats != nil { 807 runtimeStats := newJoinRuntimeStats() 808 e.ctx.GetStochastikVars().StmtCtx.RuntimeStatsDefCausl.RegisterStats(e.id, runtimeStats) 809 if e.canUseCache { 810 var hitRatio float64 811 if e.cacheAccessCounter > 0 { 812 hitRatio = float64(e.cacheHitCounter) / float64(e.cacheAccessCounter) 813 } 814 runtimeStats.setCacheInfo(true, hitRatio) 815 } else { 816 runtimeStats.setCacheInfo(false, 0) 817 } 818 } 819 return e.outerInterDirc.Close() 820 } 821 822 // Open implements the InterlockingDirectorate interface. 823 func (e *NestedLoopApplyInterDirc) Open(ctx context.Context) error { 824 err := e.outerInterDirc.Open(ctx) 825 if err != nil { 826 return err 827 } 828 e.cursor = 0 829 e.innerEvents = e.innerEvents[:0] 830 e.outerChunk = newFirstChunk(e.outerInterDirc) 831 e.innerChunk = newFirstChunk(e.innerInterDirc) 832 e.innerList = chunk.NewList(retTypes(e.innerInterDirc), e.initCap, e.maxChunkSize) 833 834 e.memTracker = memory.NewTracker(e.id, -1) 835 e.memTracker.AttachTo(e.ctx.GetStochastikVars().StmtCtx.MemTracker) 836 837 e.innerList.GetMemTracker().SetLabel(memory.LabelForInnerList) 838 e.innerList.GetMemTracker().AttachTo(e.memTracker) 839 840 if e.canUseCache { 841 e.cache, err = newApplyCache(e.ctx) 842 if err != nil { 843 return err 844 } 845 e.cacheHitCounter = 0 846 e.cacheAccessCounter = 0 847 e.cache.GetMemTracker().AttachTo(e.memTracker) 848 } 849 return nil 850 } 851 852 func (e *NestedLoopApplyInterDirc) fetchSelectedOuterEvent(ctx context.Context, chk *chunk.Chunk) (*chunk.Event, error) { 853 outerIter := chunk.NewIterator4Chunk(e.outerChunk) 854 for { 855 if e.outerChunkCursor >= e.outerChunk.NumEvents() { 856 err := Next(ctx, e.outerInterDirc, e.outerChunk) 857 if err != nil { 858 return nil, err 859 } 860 if e.outerChunk.NumEvents() == 0 { 861 return nil, nil 862 } 863 e.outerSelected, err = memex.VectorizedFilter(e.ctx, e.outerFilter, outerIter, e.outerSelected) 864 if err != nil { 865 return nil, err 866 } 867 e.outerChunkCursor = 0 868 } 869 outerEvent := e.outerChunk.GetEvent(e.outerChunkCursor) 870 selected := e.outerSelected[e.outerChunkCursor] 871 e.outerChunkCursor++ 872 if selected { 873 return &outerEvent, nil 874 } else if e.outer { 875 e.joiner.onMissMatch(false, outerEvent, chk) 876 if chk.IsFull() { 877 return nil, nil 878 } 879 } 880 } 881 } 882 883 // fetchAllInners reads all data from the inner causet and stores them in a List. 884 func (e *NestedLoopApplyInterDirc) fetchAllInners(ctx context.Context) error { 885 err := e.innerInterDirc.Open(ctx) 886 defer terror.Call(e.innerInterDirc.Close) 887 if err != nil { 888 return err 889 } 890 891 if e.canUseCache { 892 // create a new one since it may be in the cache 893 e.innerList = chunk.NewList(retTypes(e.innerInterDirc), e.initCap, e.maxChunkSize) 894 } else { 895 e.innerList.Reset() 896 } 897 innerIter := chunk.NewIterator4Chunk(e.innerChunk) 898 for { 899 err := Next(ctx, e.innerInterDirc, e.innerChunk) 900 if err != nil { 901 return err 902 } 903 if e.innerChunk.NumEvents() == 0 { 904 return nil 905 } 906 907 e.innerSelected, err = memex.VectorizedFilter(e.ctx, e.innerFilter, innerIter, e.innerSelected) 908 if err != nil { 909 return err 910 } 911 for event := innerIter.Begin(); event != innerIter.End(); event = innerIter.Next() { 912 if e.innerSelected[event.Idx()] { 913 e.innerList.AppendEvent(event) 914 } 915 } 916 } 917 } 918 919 // Next implements the InterlockingDirectorate interface. 920 func (e *NestedLoopApplyInterDirc) Next(ctx context.Context, req *chunk.Chunk) (err error) { 921 req.Reset() 922 for { 923 if e.innerIter == nil || e.innerIter.Current() == e.innerIter.End() { 924 if e.outerEvent != nil && !e.hasMatch { 925 e.joiner.onMissMatch(e.hasNull, *e.outerEvent, req) 926 } 927 e.outerEvent, err = e.fetchSelectedOuterEvent(ctx, req) 928 if e.outerEvent == nil || err != nil { 929 return err 930 } 931 e.hasMatch = false 932 e.hasNull = false 933 934 if e.canUseCache { 935 var key []byte 936 for _, defCaus := range e.outerSchema { 937 *defCaus.Data = e.outerEvent.GetCauset(defCaus.Index, defCaus.RetType) 938 key, err = codec.EncodeKey(e.ctx.GetStochastikVars().StmtCtx, key, *defCaus.Data) 939 if err != nil { 940 return err 941 } 942 } 943 e.cacheAccessCounter++ 944 value, err := e.cache.Get(key) 945 if err != nil { 946 return err 947 } 948 if value != nil { 949 e.innerList = value 950 e.cacheHitCounter++ 951 } else { 952 err = e.fetchAllInners(ctx) 953 if err != nil { 954 return err 955 } 956 if _, err := e.cache.Set(key, e.innerList); err != nil { 957 return err 958 } 959 } 960 } else { 961 for _, defCaus := range e.outerSchema { 962 *defCaus.Data = e.outerEvent.GetCauset(defCaus.Index, defCaus.RetType) 963 } 964 err = e.fetchAllInners(ctx) 965 if err != nil { 966 return err 967 } 968 } 969 e.innerIter = chunk.NewIterator4List(e.innerList) 970 e.innerIter.Begin() 971 } 972 973 matched, isNull, err := e.joiner.tryToMatchInners(*e.outerEvent, e.innerIter, req) 974 e.hasMatch = e.hasMatch || matched 975 e.hasNull = e.hasNull || isNull 976 977 if err != nil || req.IsFull() { 978 return err 979 } 980 } 981 } 982 983 // cacheInfo is used to save the concurrency information of the interlock operator 984 type cacheInfo struct { 985 hitRatio float64 986 useCache bool 987 } 988 989 type joinRuntimeStats struct { 990 *execdetails.RuntimeStatsWithConcurrencyInfo 991 992 applyCache bool 993 cache cacheInfo 994 hasHashStat bool 995 hashStat hashStatistic 996 } 997 998 func newJoinRuntimeStats() *joinRuntimeStats { 999 stats := &joinRuntimeStats{ 1000 RuntimeStatsWithConcurrencyInfo: &execdetails.RuntimeStatsWithConcurrencyInfo{}, 1001 } 1002 return stats 1003 } 1004 1005 // setCacheInfo sets the cache information. Only used for apply interlock. 1006 func (e *joinRuntimeStats) setCacheInfo(useCache bool, hitRatio float64) { 1007 e.Lock() 1008 e.applyCache = true 1009 e.cache.useCache = useCache 1010 e.cache.hitRatio = hitRatio 1011 e.Unlock() 1012 } 1013 1014 func (e *joinRuntimeStats) setHashStat(hashStat hashStatistic) { 1015 e.Lock() 1016 e.hasHashStat = true 1017 e.hashStat = hashStat 1018 e.Unlock() 1019 } 1020 1021 func (e *joinRuntimeStats) String() string { 1022 buf := bytes.NewBuffer(make([]byte, 0, 16)) 1023 buf.WriteString(e.RuntimeStatsWithConcurrencyInfo.String()) 1024 if e.applyCache { 1025 if e.cache.useCache { 1026 buf.WriteString(fmt.Sprintf(", cache:ON, cacheHitRatio:%.3f%%", e.cache.hitRatio*100)) 1027 } else { 1028 buf.WriteString(fmt.Sprintf(", cache:OFF")) 1029 } 1030 } 1031 if e.hasHashStat { 1032 buf.WriteString(", " + e.hashStat.String()) 1033 } 1034 return buf.String() 1035 } 1036 1037 // Tp implements the RuntimeStats interface. 1038 func (e *joinRuntimeStats) Tp() int { 1039 return execdetails.TpJoinRuntimeStats 1040 } 1041 1042 type hashJoinRuntimeStats struct { 1043 fetchAndBuildHashBlock time.Duration 1044 hashStat hashStatistic 1045 fetchAndProbe int64 1046 probe int64 1047 concurrent int 1048 maxFetchAndProbe int64 1049 } 1050 1051 func (e *hashJoinRuntimeStats) setMaxFetchAndProbeTime(t int64) { 1052 for { 1053 value := atomic.LoadInt64(&e.maxFetchAndProbe) 1054 if t <= value { 1055 return 1056 } 1057 if atomic.CompareAndSwapInt64(&e.maxFetchAndProbe, value, t) { 1058 return 1059 } 1060 } 1061 } 1062 1063 // Tp implements the RuntimeStats interface. 1064 func (e *hashJoinRuntimeStats) Tp() int { 1065 return execdetails.TpHashJoinRuntimeStats 1066 } 1067 1068 func (e *hashJoinRuntimeStats) String() string { 1069 buf := bytes.NewBuffer(make([]byte, 0, 128)) 1070 if e.fetchAndBuildHashBlock > 0 { 1071 buf.WriteString("build_hash_block:{total:") 1072 buf.WriteString(e.fetchAndBuildHashBlock.String()) 1073 buf.WriteString(", fetch:") 1074 buf.WriteString((e.fetchAndBuildHashBlock - e.hashStat.buildBlockElapse).String()) 1075 buf.WriteString(", build:") 1076 buf.WriteString(e.hashStat.buildBlockElapse.String()) 1077 buf.WriteString("}") 1078 } 1079 if e.probe > 0 { 1080 buf.WriteString(", probe:{concurrency:") 1081 buf.WriteString(strconv.Itoa(e.concurrent)) 1082 buf.WriteString(", total:") 1083 buf.WriteString(time.Duration(e.fetchAndProbe).String()) 1084 buf.WriteString(", max:") 1085 buf.WriteString(time.Duration(atomic.LoadInt64(&e.maxFetchAndProbe)).String()) 1086 buf.WriteString(", probe:") 1087 buf.WriteString(time.Duration(e.probe).String()) 1088 buf.WriteString(", fetch:") 1089 buf.WriteString(time.Duration(e.fetchAndProbe - e.probe).String()) 1090 if e.hashStat.probeDefCauslision > 0 { 1091 buf.WriteString(", probe_defCauslision:") 1092 buf.WriteString(strconv.Itoa(e.hashStat.probeDefCauslision)) 1093 } 1094 buf.WriteString("}") 1095 } 1096 return buf.String() 1097 } 1098 1099 func (e *hashJoinRuntimeStats) Clone() execdetails.RuntimeStats { 1100 return &hashJoinRuntimeStats{ 1101 fetchAndBuildHashBlock: e.fetchAndBuildHashBlock, 1102 hashStat: e.hashStat, 1103 fetchAndProbe: e.fetchAndProbe, 1104 probe: e.probe, 1105 concurrent: e.concurrent, 1106 maxFetchAndProbe: e.maxFetchAndProbe, 1107 } 1108 } 1109 1110 func (e *hashJoinRuntimeStats) Merge(rs execdetails.RuntimeStats) { 1111 tmp, ok := rs.(*hashJoinRuntimeStats) 1112 if !ok { 1113 return 1114 } 1115 e.fetchAndBuildHashBlock += tmp.fetchAndBuildHashBlock 1116 e.hashStat.buildBlockElapse += tmp.hashStat.buildBlockElapse 1117 e.hashStat.probeDefCauslision += tmp.hashStat.probeDefCauslision 1118 e.fetchAndProbe += tmp.fetchAndProbe 1119 e.probe += tmp.probe 1120 if e.maxFetchAndProbe < tmp.maxFetchAndProbe { 1121 e.maxFetchAndProbe = tmp.maxFetchAndProbe 1122 } 1123 }