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  }