github.com/whtcorpsinc/milevadb-prod@v0.0.0-20211104133533-f57f4be3b597/interlock/shuffle.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  	"sync"
    19  
    20  	"github.com/whtcorpsinc/errors"
    21  	"github.com/whtcorpsinc/failpoint"
    22  	"github.com/whtcorpsinc/milevadb/memex"
    23  	"github.com/whtcorpsinc/milevadb/stochastikctx"
    24  	"github.com/whtcorpsinc/milevadb/soliton/chunk"
    25  	"github.com/whtcorpsinc/milevadb/soliton/execdetails"
    26  	"github.com/whtcorpsinc/milevadb/soliton/logutil"
    27  	"github.com/twmb/murmur3"
    28  	"go.uber.org/zap"
    29  )
    30  
    31  // ShuffleInterDirc is the interlock to run other interlocks in a parallel manner.
    32  //  1. It fetches chunks from `DataSource`.
    33  //  2. It splits tuples from `DataSource` into N partitions (Only "split by hash" is implemented so far).
    34  //  3. It invokes N workers in parallel, assign each partition as input to each worker and execute child interlocks.
    35  //  4. It defCauslects outputs from each worker, then sends outputs to its parent.
    36  //
    37  //                                +-------------+
    38  //                        +-------| Main Thread |
    39  //                        |       +------+------+
    40  //                        |              ^
    41  //                        |              |
    42  //                        |              +
    43  //                        v             +++
    44  //                 outputHolderCh       | | outputCh (1 x Concurrency)
    45  //                        v             +++
    46  //                        |              ^
    47  //                        |              |
    48  //                        |      +-------+-------+
    49  //                        v      |               |
    50  //                 +--------------+             +--------------+
    51  //          +----- |    worker    |   .......   |    worker    |  worker (N Concurrency): child interlock, eg. WindowInterDirc (+SortInterDirc)
    52  //          |      +------------+-+             +-+------------+
    53  //          |                 ^                 ^
    54  //          |                 |                 |
    55  //          |                +-+  +-+  ......  +-+
    56  //          |                | |  | |          | |
    57  //          |                ...  ...          ...  inputCh (Concurrency x 1)
    58  //          v                | |  | |          | |
    59  //    inputHolderCh          +++  +++          +++
    60  //          v                 ^    ^            ^
    61  //          |                 |    |            |
    62  //          |          +------o----+            |
    63  //          |          |      +-----------------+-----+
    64  //          |          |                              |
    65  //          |      +---+------------+------------+----+-----------+
    66  //          |      |              Partition Splitter              |
    67  //          |      +--------------+-+------------+-+--------------+
    68  //          |                             ^
    69  //          |                             |
    70  //          |             +---------------v-----------------+
    71  //          +---------->  |    fetch data from DataSource   |
    72  //                        +---------------------------------+
    73  //
    74  ////////////////////////////////////////////////////////////////////////////////////////
    75  type ShuffleInterDirc struct {
    76  	baseInterlockingDirectorate
    77  	concurrency int
    78  	workers     []*shuffleWorker
    79  
    80  	prepared bool
    81  	executed bool
    82  
    83  	splitter   partitionSplitter
    84  	dataSource InterlockingDirectorate
    85  
    86  	finishCh chan struct{}
    87  	outputCh chan *shuffleOutput
    88  }
    89  
    90  type shuffleOutput struct {
    91  	chk        *chunk.Chunk
    92  	err        error
    93  	giveBackCh chan *chunk.Chunk
    94  }
    95  
    96  // Open implements the InterlockingDirectorate Open interface.
    97  func (e *ShuffleInterDirc) Open(ctx context.Context) error {
    98  	if err := e.dataSource.Open(ctx); err != nil {
    99  		return err
   100  	}
   101  	if err := e.baseInterlockingDirectorate.Open(ctx); err != nil {
   102  		return err
   103  	}
   104  
   105  	e.prepared = false
   106  	e.finishCh = make(chan struct{}, 1)
   107  	e.outputCh = make(chan *shuffleOutput, e.concurrency)
   108  
   109  	for _, w := range e.workers {
   110  		w.finishCh = e.finishCh
   111  
   112  		w.inputCh = make(chan *chunk.Chunk, 1)
   113  		w.inputHolderCh = make(chan *chunk.Chunk, 1)
   114  		w.outputCh = e.outputCh
   115  		w.outputHolderCh = make(chan *chunk.Chunk, 1)
   116  
   117  		if err := w.childInterDirc.Open(ctx); err != nil {
   118  			return err
   119  		}
   120  
   121  		w.inputHolderCh <- newFirstChunk(e.dataSource)
   122  		w.outputHolderCh <- newFirstChunk(e)
   123  	}
   124  
   125  	return nil
   126  }
   127  
   128  // Close implements the InterlockingDirectorate Close interface.
   129  func (e *ShuffleInterDirc) Close() error {
   130  	if !e.prepared {
   131  		for _, w := range e.workers {
   132  			close(w.inputHolderCh)
   133  			close(w.inputCh)
   134  			close(w.outputHolderCh)
   135  		}
   136  		close(e.outputCh)
   137  	}
   138  	close(e.finishCh)
   139  	for _, w := range e.workers {
   140  		for range w.inputCh {
   141  		}
   142  	}
   143  	for range e.outputCh { // workers exit before `e.outputCh` is closed.
   144  	}
   145  	e.executed = false
   146  
   147  	if e.runtimeStats != nil {
   148  		runtimeStats := &execdetails.RuntimeStatsWithConcurrencyInfo{}
   149  		runtimeStats.SetConcurrencyInfo(execdetails.NewConcurrencyInfo("ShuffleConcurrency", e.concurrency))
   150  		e.ctx.GetStochastikVars().StmtCtx.RuntimeStatsDefCausl.RegisterStats(e.id, runtimeStats)
   151  	}
   152  
   153  	err := e.dataSource.Close()
   154  	err1 := e.baseInterlockingDirectorate.Close()
   155  	if err != nil {
   156  		return errors.Trace(err)
   157  	}
   158  	return errors.Trace(err1)
   159  }
   160  
   161  func (e *ShuffleInterDirc) prepare4ParallelInterDirc(ctx context.Context) {
   162  	go e.fetchDataAndSplit(ctx)
   163  
   164  	waitGroup := &sync.WaitGroup{}
   165  	waitGroup.Add(len(e.workers))
   166  	for _, w := range e.workers {
   167  		go w.run(ctx, waitGroup)
   168  	}
   169  
   170  	go e.waitWorkerAndCloseOutput(waitGroup)
   171  }
   172  
   173  func (e *ShuffleInterDirc) waitWorkerAndCloseOutput(waitGroup *sync.WaitGroup) {
   174  	waitGroup.Wait()
   175  	close(e.outputCh)
   176  }
   177  
   178  // Next implements the InterlockingDirectorate Next interface.
   179  func (e *ShuffleInterDirc) Next(ctx context.Context, req *chunk.Chunk) error {
   180  	req.Reset()
   181  	if !e.prepared {
   182  		e.prepare4ParallelInterDirc(ctx)
   183  		e.prepared = true
   184  	}
   185  
   186  	failpoint.Inject("shuffleError", func(val failpoint.Value) {
   187  		if val.(bool) {
   188  			failpoint.Return(errors.New("ShuffleInterDirc.Next error"))
   189  		}
   190  	})
   191  
   192  	if e.executed {
   193  		return nil
   194  	}
   195  
   196  	result, ok := <-e.outputCh
   197  	if !ok {
   198  		e.executed = true
   199  		return nil
   200  	}
   201  	if result.err != nil {
   202  		return result.err
   203  	}
   204  	req.SwapDeferredCausets(result.chk) // `shuffleWorker` will not send an empty `result.chk` to `e.outputCh`.
   205  	result.giveBackCh <- result.chk
   206  
   207  	return nil
   208  }
   209  
   210  func recoveryShuffleInterDirc(output chan *shuffleOutput, r interface{}) {
   211  	err := errors.Errorf("%v", r)
   212  	output <- &shuffleOutput{err: errors.Errorf("%v", r)}
   213  	logutil.BgLogger().Error("shuffle panicked", zap.Error(err), zap.Stack("stack"))
   214  }
   215  
   216  func (e *ShuffleInterDirc) fetchDataAndSplit(ctx context.Context) {
   217  	var (
   218  		err           error
   219  		workerIndices []int
   220  	)
   221  	results := make([]*chunk.Chunk, len(e.workers))
   222  	chk := newFirstChunk(e.dataSource)
   223  
   224  	defer func() {
   225  		if r := recover(); r != nil {
   226  			recoveryShuffleInterDirc(e.outputCh, r)
   227  		}
   228  		for _, w := range e.workers {
   229  			close(w.inputCh)
   230  		}
   231  	}()
   232  
   233  	for {
   234  		err = Next(ctx, e.dataSource, chk)
   235  		if err != nil {
   236  			e.outputCh <- &shuffleOutput{err: err}
   237  			return
   238  		}
   239  		if chk.NumEvents() == 0 {
   240  			break
   241  		}
   242  
   243  		workerIndices, err = e.splitter.split(e.ctx, chk, workerIndices)
   244  		if err != nil {
   245  			e.outputCh <- &shuffleOutput{err: err}
   246  			return
   247  		}
   248  		numEvents := chk.NumEvents()
   249  		for i := 0; i < numEvents; i++ {
   250  			workerIdx := workerIndices[i]
   251  			w := e.workers[workerIdx]
   252  
   253  			if results[workerIdx] == nil {
   254  				select {
   255  				case <-e.finishCh:
   256  					return
   257  				case results[workerIdx] = <-w.inputHolderCh:
   258  					break
   259  				}
   260  			}
   261  			results[workerIdx].AppendEvent(chk.GetEvent(i))
   262  			if results[workerIdx].IsFull() {
   263  				w.inputCh <- results[workerIdx]
   264  				results[workerIdx] = nil
   265  			}
   266  		}
   267  	}
   268  	for i, w := range e.workers {
   269  		if results[i] != nil {
   270  			w.inputCh <- results[i]
   271  			results[i] = nil
   272  		}
   273  	}
   274  }
   275  
   276  var _ InterlockingDirectorate = &shuffleWorker{}
   277  
   278  // shuffleWorker is the multi-thread worker executing child interlocks within "partition".
   279  type shuffleWorker struct {
   280  	baseInterlockingDirectorate
   281  	childInterDirc InterlockingDirectorate
   282  
   283  	finishCh <-chan struct{}
   284  	executed bool
   285  
   286  	// Workers get inputs from dataFetcherThread by `inputCh`,
   287  	//   and output results to main thread by `outputCh`.
   288  	// `inputHolderCh` and `outputHolderCh` are "Chunk Holder" channels of `inputCh` and `outputCh` respectively,
   289  	//   which give the `*Chunk` back, to implement the data transport in a streaming manner.
   290  	inputCh        chan *chunk.Chunk
   291  	inputHolderCh  chan *chunk.Chunk
   292  	outputCh       chan *shuffleOutput
   293  	outputHolderCh chan *chunk.Chunk
   294  }
   295  
   296  // Open implements the InterlockingDirectorate Open interface.
   297  func (e *shuffleWorker) Open(ctx context.Context) error {
   298  	if err := e.baseInterlockingDirectorate.Open(ctx); err != nil {
   299  		return err
   300  	}
   301  	e.executed = false
   302  	return nil
   303  }
   304  
   305  // Close implements the InterlockingDirectorate Close interface.
   306  func (e *shuffleWorker) Close() error {
   307  	return errors.Trace(e.baseInterlockingDirectorate.Close())
   308  }
   309  
   310  // Next implements the InterlockingDirectorate Next interface.
   311  // It is called by `Tail` interlock within "shuffle", to fetch data from `DataSource` by `inputCh`.
   312  func (e *shuffleWorker) Next(ctx context.Context, req *chunk.Chunk) error {
   313  	req.Reset()
   314  	if e.executed {
   315  		return nil
   316  	}
   317  	select {
   318  	case <-e.finishCh:
   319  		e.executed = true
   320  		return nil
   321  	case result, ok := <-e.inputCh:
   322  		if !ok || result.NumEvents() == 0 {
   323  			e.executed = true
   324  			return nil
   325  		}
   326  		req.SwapDeferredCausets(result)
   327  		e.inputHolderCh <- result
   328  		return nil
   329  	}
   330  }
   331  
   332  func (e *shuffleWorker) run(ctx context.Context, waitGroup *sync.WaitGroup) {
   333  	defer func() {
   334  		if r := recover(); r != nil {
   335  			recoveryShuffleInterDirc(e.outputCh, r)
   336  		}
   337  		waitGroup.Done()
   338  	}()
   339  
   340  	for {
   341  		select {
   342  		case <-e.finishCh:
   343  			return
   344  		case chk := <-e.outputHolderCh:
   345  			if err := Next(ctx, e.childInterDirc, chk); err != nil {
   346  				e.outputCh <- &shuffleOutput{err: err}
   347  				return
   348  			}
   349  
   350  			// Should not send an empty `chk` to `e.outputCh`.
   351  			if chk.NumEvents() == 0 {
   352  				return
   353  			}
   354  			e.outputCh <- &shuffleOutput{chk: chk, giveBackCh: e.outputHolderCh}
   355  		}
   356  	}
   357  }
   358  
   359  var _ partitionSplitter = &partitionHashSplitter{}
   360  
   361  type partitionSplitter interface {
   362  	split(ctx stochastikctx.Context, input *chunk.Chunk, workerIndices []int) ([]int, error)
   363  }
   364  
   365  type partitionHashSplitter struct {
   366  	byItems    []memex.Expression
   367  	numWorkers int
   368  	hashKeys   [][]byte
   369  }
   370  
   371  func (s *partitionHashSplitter) split(ctx stochastikctx.Context, input *chunk.Chunk, workerIndices []int) ([]int, error) {
   372  	var err error
   373  	s.hashKeys, err = getGroupKey(ctx, input, s.hashKeys, s.byItems)
   374  	if err != nil {
   375  		return workerIndices, err
   376  	}
   377  	workerIndices = workerIndices[:0]
   378  	numEvents := input.NumEvents()
   379  	for i := 0; i < numEvents; i++ {
   380  		workerIndices = append(workerIndices, int(murmur3.Sum32(s.hashKeys[i]))%s.numWorkers)
   381  	}
   382  	return workerIndices, nil
   383  }