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  }