github.com/whtcorpsinc/milevadb-prod@v0.0.0-20211104133533-f57f4be3b597/soliton/chunk/row_container.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 chunk
    15  
    16  import (
    17  	"errors"
    18  	"sort"
    19  	"sync"
    20  	"time"
    21  
    22  	"github.com/whtcorpsinc/failpoint"
    23  	"github.com/whtcorpsinc/milevadb/types"
    24  	"github.com/whtcorpsinc/milevadb/soliton/disk"
    25  	"github.com/whtcorpsinc/milevadb/soliton/logutil"
    26  	"github.com/whtcorpsinc/milevadb/soliton/memory"
    27  	"go.uber.org/zap"
    28  )
    29  
    30  // RowContainer provides a place for many rows, so many that we might want to spill them into disk.
    31  type RowContainer struct {
    32  	m struct {
    33  		// RWMutex guarantees spill and get operator for rowContainer is mutually exclusive.
    34  		sync.RWMutex
    35  		// records stores the chunks in memory.
    36  		records *List
    37  		// recordsInDisk stores the chunks in disk.
    38  		recordsInDisk *ListInDisk
    39  		// spillError stores the error when spilling.
    40  		spillError error
    41  	}
    42  
    43  	fieldType []*types.FieldType
    44  	chunkSize int
    45  	numRow    int
    46  
    47  	memTracker  *memory.Tracker
    48  	diskTracker *disk.Tracker
    49  	actionSpill *SpillDiskCausetAction
    50  }
    51  
    52  // NewRowContainer creates a new RowContainer in memory.
    53  func NewRowContainer(fieldType []*types.FieldType, chunkSize int) *RowContainer {
    54  	li := NewList(fieldType, chunkSize, chunkSize)
    55  	rc := &RowContainer{fieldType: fieldType, chunkSize: chunkSize}
    56  	rc.m.records = li
    57  	rc.memTracker = li.memTracker
    58  	rc.diskTracker = disk.NewTracker(memory.LabelForRowContainer, -1)
    59  	return rc
    60  }
    61  
    62  // SpillToDisk spills data to disk. This function may be called in parallel.
    63  func (c *RowContainer) SpillToDisk() {
    64  	c.m.Lock()
    65  	defer c.m.Unlock()
    66  	if c.alreadySpilled() {
    67  		return
    68  	}
    69  	// c.actionSpill may be nil when testing SpillToDisk directly.
    70  	if c.actionSpill != nil {
    71  		if c.actionSpill.getStatus() == spilledYet {
    72  			// The rowContainer has been closed.
    73  			return
    74  		}
    75  		c.actionSpill.setStatus(spilling)
    76  		defer c.actionSpill.cond.Broadcast()
    77  		defer c.actionSpill.setStatus(spilledYet)
    78  	}
    79  	var err error
    80  	N := c.m.records.NumChunks()
    81  	c.m.recordsInDisk = NewListInDisk(c.m.records.FieldTypes())
    82  	c.m.recordsInDisk.diskTracker.AttachTo(c.diskTracker)
    83  	for i := 0; i < N; i++ {
    84  		chk := c.m.records.GetChunk(i)
    85  		err = c.m.recordsInDisk.Add(chk)
    86  		if err != nil {
    87  			c.m.spillError = err
    88  			return
    89  		}
    90  	}
    91  	c.m.records.Clear()
    92  	return
    93  }
    94  
    95  // Reset resets RowContainer.
    96  func (c *RowContainer) Reset() error {
    97  	c.m.Lock()
    98  	defer c.m.Unlock()
    99  	if c.alreadySpilled() {
   100  		err := c.m.recordsInDisk.Close()
   101  		c.m.recordsInDisk = nil
   102  		if err != nil {
   103  			return err
   104  		}
   105  		c.actionSpill.Reset()
   106  	} else {
   107  		c.m.records.Reset()
   108  	}
   109  	return nil
   110  }
   111  
   112  // alreadySpilled indicates that records have spilled out into disk.
   113  func (c *RowContainer) alreadySpilled() bool {
   114  	return c.m.recordsInDisk != nil
   115  }
   116  
   117  // AlreadySpilledSafeForTest indicates that records have spilled out into disk. It's thread-safe.
   118  // The function is only used for test.
   119  func (c *RowContainer) AlreadySpilledSafeForTest() bool {
   120  	c.m.RLock()
   121  	defer c.m.RUnlock()
   122  	return c.m.recordsInDisk != nil
   123  }
   124  
   125  // NumRow returns the number of rows in the container
   126  func (c *RowContainer) NumRow() int {
   127  	c.m.RLock()
   128  	defer c.m.RUnlock()
   129  	if c.alreadySpilled() {
   130  		return c.m.recordsInDisk.Len()
   131  	}
   132  	return c.m.records.Len()
   133  }
   134  
   135  // NumRowsOfChunk returns the number of rows of a chunk in the ListInDisk.
   136  func (c *RowContainer) NumRowsOfChunk(chkID int) int {
   137  	c.m.RLock()
   138  	defer c.m.RUnlock()
   139  	if c.alreadySpilled() {
   140  		return c.m.recordsInDisk.NumRowsOfChunk(chkID)
   141  	}
   142  	return c.m.records.NumRowsOfChunk(chkID)
   143  }
   144  
   145  // NumChunks returns the number of chunks in the container.
   146  func (c *RowContainer) NumChunks() int {
   147  	c.m.RLock()
   148  	defer c.m.RUnlock()
   149  	if c.alreadySpilled() {
   150  		return c.m.recordsInDisk.NumChunks()
   151  	}
   152  	return c.m.records.NumChunks()
   153  }
   154  
   155  // Add appends a chunk into the RowContainer.
   156  func (c *RowContainer) Add(chk *Chunk) (err error) {
   157  	c.m.RLock()
   158  	defer c.m.RUnlock()
   159  	failpoint.Inject("testRowContainerDeadLock", func(val failpoint.Value) {
   160  		if val.(bool) {
   161  			time.Sleep(time.Second)
   162  		}
   163  	})
   164  	if c.alreadySpilled() {
   165  		if c.m.spillError != nil {
   166  			return c.m.spillError
   167  		}
   168  		err = c.m.recordsInDisk.Add(chk)
   169  	} else {
   170  		c.m.records.Add(chk)
   171  	}
   172  	return
   173  }
   174  
   175  // AllocChunk allocates a new chunk from RowContainer.
   176  func (c *RowContainer) AllocChunk() (chk *Chunk) {
   177  	return c.m.records.allocChunk()
   178  }
   179  
   180  // GetChunk returns chkIdx th chunk of in memory records.
   181  func (c *RowContainer) GetChunk(chkIdx int) (*Chunk, error) {
   182  	c.m.RLock()
   183  	defer c.m.RUnlock()
   184  	if !c.alreadySpilled() {
   185  		return c.m.records.GetChunk(chkIdx), nil
   186  	}
   187  	if c.m.spillError != nil {
   188  		return nil, c.m.spillError
   189  	}
   190  	return c.m.recordsInDisk.GetChunk(chkIdx)
   191  }
   192  
   193  // GetRow returns the event the ptr pointed to.
   194  func (c *RowContainer) GetRow(ptr RowPtr) (Row, error) {
   195  	c.m.RLock()
   196  	defer c.m.RUnlock()
   197  	if c.alreadySpilled() {
   198  		if c.m.spillError != nil {
   199  			return Row{}, c.m.spillError
   200  		}
   201  		return c.m.recordsInDisk.GetRow(ptr)
   202  	}
   203  	return c.m.records.GetRow(ptr), nil
   204  }
   205  
   206  // GetMemTracker returns the memory tracker in records, panics if the RowContainer has already spilled.
   207  func (c *RowContainer) GetMemTracker() *memory.Tracker {
   208  	return c.memTracker
   209  }
   210  
   211  // GetDiskTracker returns the underlying disk usage tracker in recordsInDisk.
   212  func (c *RowContainer) GetDiskTracker() *disk.Tracker {
   213  	return c.diskTracker
   214  }
   215  
   216  // Close close the RowContainer
   217  func (c *RowContainer) Close() (err error) {
   218  	c.m.RLock()
   219  	defer c.m.RUnlock()
   220  	if c.actionSpill != nil {
   221  		// Set status to spilledYet to avoid spilling.
   222  		c.actionSpill.setStatus(spilledYet)
   223  		c.actionSpill.cond.Broadcast()
   224  	}
   225  	if c.alreadySpilled() {
   226  		err = c.m.recordsInDisk.Close()
   227  		c.m.recordsInDisk = nil
   228  	}
   229  	c.m.records.Clear()
   230  	return
   231  }
   232  
   233  // CausetActionSpill returns a SpillDiskCausetAction for spilling over to disk.
   234  func (c *RowContainer) CausetActionSpill() *SpillDiskCausetAction {
   235  	if c.actionSpill == nil {
   236  		c.actionSpill = &SpillDiskCausetAction{
   237  			c:    c,
   238  			cond: spillStatusCond{sync.NewCond(new(sync.Mutex)), notSpilled}}
   239  	}
   240  	return c.actionSpill
   241  }
   242  
   243  // CausetActionSpillForTest returns a SpillDiskCausetAction for spilling over to disk for test.
   244  func (c *RowContainer) CausetActionSpillForTest() *SpillDiskCausetAction {
   245  	c.actionSpill = &SpillDiskCausetAction{
   246  		c: c,
   247  		testSyncInputFunc: func() {
   248  			c.actionSpill.testWg.Add(1)
   249  		},
   250  		testSyncOutputFunc: func() {
   251  			c.actionSpill.testWg.Done()
   252  		},
   253  		cond: spillStatusCond{sync.NewCond(new(sync.Mutex)), notSpilled},
   254  	}
   255  	return c.actionSpill
   256  }
   257  
   258  // SpillDiskCausetAction implements memory.SuperCowOrNoCausetOnExceed for chunk.List. If
   259  // the memory quota of a query is exceeded, SpillDiskCausetAction.CausetAction is
   260  // triggered.
   261  type SpillDiskCausetAction struct {
   262  	c              *RowContainer
   263  	fallbackCausetAction memory.SuperCowOrNoCausetOnExceed
   264  	m              sync.Mutex
   265  	once           sync.Once
   266  	cond           spillStatusCond
   267  
   268  	// test function only used for test sync.
   269  	testSyncInputFunc  func()
   270  	testSyncOutputFunc func()
   271  	testWg             sync.WaitGroup
   272  }
   273  
   274  type spillStatusCond struct {
   275  	*sync.Cond
   276  	// status indicates different stages for the CausetAction
   277  	// notSpilled indicates the rowContainer is not spilled.
   278  	// spilling indicates the rowContainer is spilling.
   279  	// spilledYet indicates thr rowContainer is spilled.
   280  	status spillStatus
   281  }
   282  
   283  type spillStatus uint32
   284  
   285  const (
   286  	notSpilled spillStatus = iota
   287  	spilling
   288  	spilledYet
   289  )
   290  
   291  func (a *SpillDiskCausetAction) setStatus(status spillStatus) {
   292  	a.cond.L.Lock()
   293  	defer a.cond.L.Unlock()
   294  	a.cond.status = status
   295  }
   296  
   297  func (a *SpillDiskCausetAction) getStatus() spillStatus {
   298  	a.cond.L.Lock()
   299  	defer a.cond.L.Unlock()
   300  	return a.cond.status
   301  }
   302  
   303  // CausetAction sends a signal to trigger spillToDisk method of RowContainer
   304  // and if it is already triggered before, call its fallbackCausetAction.
   305  func (a *SpillDiskCausetAction) CausetAction(t *memory.Tracker) {
   306  	a.m.Lock()
   307  	defer a.m.Unlock()
   308  
   309  	if a.getStatus() == notSpilled {
   310  		a.once.Do(func() {
   311  			logutil.BgLogger().Info("memory exceeds quota, spill to disk now.",
   312  				zap.Int64("consumed", t.BytesConsumed()), zap.Int64("quota", t.GetBytesLimit()))
   313  			if a.testSyncInputFunc != nil {
   314  				a.testSyncInputFunc()
   315  				c := a.c
   316  				go func() {
   317  					c.SpillToDisk()
   318  					a.testSyncOutputFunc()
   319  				}()
   320  				return
   321  			}
   322  			go a.c.SpillToDisk()
   323  		})
   324  		return
   325  	}
   326  
   327  	a.cond.L.Lock()
   328  	for a.cond.status == spilling {
   329  		a.cond.Wait()
   330  	}
   331  	a.cond.L.Unlock()
   332  
   333  	if !t.CheckExceed() {
   334  		return
   335  	}
   336  	if a.fallbackCausetAction != nil {
   337  		a.fallbackCausetAction.CausetAction(t)
   338  	}
   339  }
   340  
   341  // Reset resets the status for SpillDiskCausetAction.
   342  func (a *SpillDiskCausetAction) Reset() {
   343  	a.m.Lock()
   344  	defer a.m.Unlock()
   345  	a.setStatus(notSpilled)
   346  	a.once = sync.Once{}
   347  }
   348  
   349  // SetFallback sets the fallback action.
   350  func (a *SpillDiskCausetAction) SetFallback(fallback memory.SuperCowOrNoCausetOnExceed) {
   351  	a.fallbackCausetAction = fallback
   352  }
   353  
   354  // SetLogHook sets the hook, it does nothing just to form the memory.SuperCowOrNoCausetOnExceed interface.
   355  func (a *SpillDiskCausetAction) SetLogHook(hook func(uint64)) {}
   356  
   357  // WaitForTest waits all goroutine have gone.
   358  func (a *SpillDiskCausetAction) WaitForTest() {
   359  	a.testWg.Wait()
   360  }
   361  
   362  // ErrCannotAddBecauseSorted indicate that the SortedRowContainer is sorted and prohibit inserting data.
   363  var ErrCannotAddBecauseSorted = errors.New("can not add because sorted")
   364  
   365  // SortedRowContainer provides a place for many rows, so many that we might want to sort and spill them into disk.
   366  type SortedRowContainer struct {
   367  	*RowContainer
   368  	ptrM struct {
   369  		sync.RWMutex
   370  		// rowPtrs causetstore the chunk index and event index for each event.
   371  		// rowPtrs != nil indicates the pointer is initialized and sorted.
   372  		// It will get an ErrCannotAddBecauseSorted when trying to insert data if rowPtrs != nil.
   373  		rowPtrs []RowPtr
   374  	}
   375  
   376  	ByItemsDesc []bool
   377  	// keyDeferredCausets is the defCausumn index of the by items.
   378  	keyDeferredCausets []int
   379  	// keyCmpFuncs is used to compare each ByItem.
   380  	keyCmpFuncs []CompareFunc
   381  
   382  	actionSpill *SortAndSpillDiskCausetAction
   383  }
   384  
   385  // NewSortedRowContainer creates a new SortedRowContainer in memory.
   386  func NewSortedRowContainer(fieldType []*types.FieldType, chunkSize int, ByItemsDesc []bool,
   387  	keyDeferredCausets []int, keyCmpFuncs []CompareFunc) *SortedRowContainer {
   388  	return &SortedRowContainer{RowContainer: NewRowContainer(fieldType, chunkSize),
   389  		ByItemsDesc: ByItemsDesc, keyDeferredCausets: keyDeferredCausets, keyCmpFuncs: keyCmpFuncs}
   390  }
   391  
   392  // Close close the SortedRowContainer
   393  func (c *SortedRowContainer) Close() error {
   394  	c.ptrM.Lock()
   395  	defer c.ptrM.Unlock()
   396  	c.GetMemTracker().Consume(int64(-8 * cap(c.ptrM.rowPtrs)))
   397  	c.ptrM.rowPtrs = nil
   398  	return c.RowContainer.Close()
   399  }
   400  
   401  func (c *SortedRowContainer) lessRow(rowI, rowJ Row) bool {
   402  	for i, defCausIdx := range c.keyDeferredCausets {
   403  		cmpFunc := c.keyCmpFuncs[i]
   404  		cmp := cmpFunc(rowI, defCausIdx, rowJ, defCausIdx)
   405  		if c.ByItemsDesc[i] {
   406  			cmp = -cmp
   407  		}
   408  		if cmp < 0 {
   409  			return true
   410  		} else if cmp > 0 {
   411  			return false
   412  		}
   413  	}
   414  	return false
   415  }
   416  
   417  // keyDeferredCausetsLess is the less function for key defCausumns.
   418  func (c *SortedRowContainer) keyDeferredCausetsLess(i, j int) bool {
   419  	rowI := c.m.records.GetRow(c.ptrM.rowPtrs[i])
   420  	rowJ := c.m.records.GetRow(c.ptrM.rowPtrs[j])
   421  	return c.lessRow(rowI, rowJ)
   422  }
   423  
   424  // Sort inits pointers and sorts the records.
   425  func (c *SortedRowContainer) Sort() {
   426  	c.ptrM.Lock()
   427  	defer c.ptrM.Unlock()
   428  	if c.ptrM.rowPtrs != nil {
   429  		return
   430  	}
   431  	c.ptrM.rowPtrs = make([]RowPtr, 0, c.NumRow())
   432  	for chkIdx := 0; chkIdx < c.NumChunks(); chkIdx++ {
   433  		rowChk, err := c.GetChunk(chkIdx)
   434  		// err must be nil, because the chunk is in memory.
   435  		if err != nil {
   436  			panic(err)
   437  		}
   438  		for rowIdx := 0; rowIdx < rowChk.NumRows(); rowIdx++ {
   439  			c.ptrM.rowPtrs = append(c.ptrM.rowPtrs, RowPtr{ChkIdx: uint32(chkIdx), RowIdx: uint32(rowIdx)})
   440  		}
   441  	}
   442  	sort.Slice(c.ptrM.rowPtrs, c.keyDeferredCausetsLess)
   443  	c.GetMemTracker().Consume(int64(8 * c.numRow))
   444  }
   445  
   446  func (c *SortedRowContainer) sortAndSpillToDisk() {
   447  	c.Sort()
   448  	c.RowContainer.SpillToDisk()
   449  	return
   450  }
   451  
   452  // Add appends a chunk into the SortedRowContainer.
   453  func (c *SortedRowContainer) Add(chk *Chunk) (err error) {
   454  	c.ptrM.RLock()
   455  	defer c.ptrM.RUnlock()
   456  	if c.ptrM.rowPtrs != nil {
   457  		return ErrCannotAddBecauseSorted
   458  	}
   459  	return c.RowContainer.Add(chk)
   460  }
   461  
   462  // GetSortedRow returns the event the idx pointed to.
   463  func (c *SortedRowContainer) GetSortedRow(idx int) (Row, error) {
   464  	c.ptrM.RLock()
   465  	defer c.ptrM.RUnlock()
   466  	ptr := c.ptrM.rowPtrs[idx]
   467  	return c.RowContainer.GetRow(ptr)
   468  }
   469  
   470  // CausetActionSpill returns a SortAndSpillDiskCausetAction for sorting and spilling over to disk.
   471  func (c *SortedRowContainer) CausetActionSpill() *SortAndSpillDiskCausetAction {
   472  	if c.actionSpill == nil {
   473  		c.actionSpill = &SortAndSpillDiskCausetAction{
   474  			c:               c,
   475  			SpillDiskCausetAction: c.RowContainer.CausetActionSpill(),
   476  		}
   477  	}
   478  	return c.actionSpill
   479  }
   480  
   481  // CausetActionSpillForTest returns a SortAndSpillDiskCausetAction for sorting and spilling over to disk for test.
   482  func (c *SortedRowContainer) CausetActionSpillForTest() *SortAndSpillDiskCausetAction {
   483  	c.actionSpill = &SortAndSpillDiskCausetAction{
   484  		c:               c,
   485  		SpillDiskCausetAction: c.RowContainer.CausetActionSpillForTest(),
   486  	}
   487  	return c.actionSpill
   488  }
   489  
   490  // SortAndSpillDiskCausetAction implements memory.SuperCowOrNoCausetOnExceed for chunk.List. If
   491  // the memory quota of a query is exceeded, SortAndSpillDiskCausetAction.CausetAction is
   492  // triggered.
   493  type SortAndSpillDiskCausetAction struct {
   494  	c *SortedRowContainer
   495  	*SpillDiskCausetAction
   496  }
   497  
   498  // CausetAction sends a signal to trigger sortAndSpillToDisk method of RowContainer
   499  // and if it is already triggered before, call its fallbackCausetAction.
   500  func (a *SortAndSpillDiskCausetAction) CausetAction(t *memory.Tracker) {
   501  	a.m.Lock()
   502  	defer a.m.Unlock()
   503  	// Guarantee that each partition size is at least 10% of the threshold, to avoid opening too many files.
   504  	if a.getStatus() == notSpilled && a.c.GetMemTracker().BytesConsumed() > t.GetBytesLimit()/10 {
   505  		a.once.Do(func() {
   506  			logutil.BgLogger().Info("memory exceeds quota, spill to disk now.",
   507  				zap.Int64("consumed", t.BytesConsumed()), zap.Int64("quota", t.GetBytesLimit()))
   508  			if a.testSyncInputFunc != nil {
   509  				a.testSyncInputFunc()
   510  				c := a.c
   511  				go func() {
   512  					c.sortAndSpillToDisk()
   513  					a.testSyncOutputFunc()
   514  				}()
   515  				return
   516  			}
   517  			go a.c.sortAndSpillToDisk()
   518  		})
   519  		return
   520  	}
   521  
   522  	a.cond.L.Lock()
   523  	for a.cond.status == spilling {
   524  		a.cond.Wait()
   525  	}
   526  	a.cond.L.Unlock()
   527  
   528  	if !t.CheckExceed() {
   529  		return
   530  	}
   531  	if a.fallbackCausetAction != nil {
   532  		a.fallbackCausetAction.CausetAction(t)
   533  	}
   534  }
   535  
   536  // SetFallback sets the fallback action.
   537  func (a *SortAndSpillDiskCausetAction) SetFallback(fallback memory.SuperCowOrNoCausetOnExceed) {
   538  	a.fallbackCausetAction = fallback
   539  }
   540  
   541  // SetLogHook sets the hook, it does nothing just to form the memory.SuperCowOrNoCausetOnExceed interface.
   542  func (a *SortAndSpillDiskCausetAction) SetLogHook(hook func(uint64)) {}
   543  
   544  // WaitForTest waits all goroutine have gone.
   545  func (a *SortAndSpillDiskCausetAction) WaitForTest() {
   546  	a.testWg.Wait()
   547  }