github.com/matrixorigin/matrixone@v0.7.0/pkg/vm/engine/tae/db/scannerop.go (about)

     1  // Copyright 2021 Matrix Origin
     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  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package db
    16  
    17  import (
    18  	"container/heap"
    19  	"fmt"
    20  	"sort"
    21  	"time"
    22  
    23  	"github.com/matrixorigin/matrixone/pkg/common/moerr"
    24  	"go.uber.org/zap"
    25  
    26  	"github.com/matrixorigin/matrixone/pkg/logutil"
    27  	"github.com/matrixorigin/matrixone/pkg/vm/engine/tae/catalog"
    28  	"github.com/matrixorigin/matrixone/pkg/vm/engine/tae/common"
    29  	"github.com/matrixorigin/matrixone/pkg/vm/engine/tae/iface/txnif"
    30  	"github.com/matrixorigin/matrixone/pkg/vm/engine/tae/options"
    31  	"github.com/matrixorigin/matrixone/pkg/vm/engine/tae/tables/jobs"
    32  	"github.com/matrixorigin/matrixone/pkg/vm/engine/tae/tasks"
    33  )
    34  
    35  type ScannerOp interface {
    36  	catalog.Processor
    37  	PreExecute() error
    38  	PostExecute() error
    39  }
    40  
    41  const (
    42  	constMergeRightNow     = int(options.DefaultBlockMaxRows) * int(options.DefaultBlocksPerSegment)
    43  	constMergeWaitDuration = 3 * time.Minute
    44  	constMergeMinBlks      = 3
    45  	constMergeMinRows      = 3000
    46  	constHeapCapacity      = 300
    47  )
    48  
    49  // min heap item
    50  type mItem struct {
    51  	row   int
    52  	entry *catalog.BlockEntry
    53  }
    54  
    55  type itemSet []*mItem
    56  
    57  func (is itemSet) Len() int { return len(is) }
    58  
    59  func (is itemSet) Less(i, j int) bool {
    60  	return is[i].row < is[j].row
    61  }
    62  
    63  func (is itemSet) Swap(i, j int) {
    64  	is[i], is[j] = is[j], is[i]
    65  }
    66  
    67  func (is *itemSet) Push(x any) {
    68  	item := x.(*mItem)
    69  	*is = append(*is, item)
    70  }
    71  
    72  func (is *itemSet) Pop() any {
    73  	old := *is
    74  	n := len(old)
    75  	item := old[n-1]
    76  	old[n-1] = nil // avoid memory leak
    77  	*is = old[0 : n-1]
    78  	return item
    79  }
    80  
    81  func (is *itemSet) Clear() {
    82  	old := *is
    83  	*is = old[:0]
    84  }
    85  
    86  // mergedBlkBuilder founds out blocks to be merged via maintaining a min heap holding
    87  // up to default 300 items.
    88  type mergedBlkBuilder struct {
    89  	blocks itemSet
    90  	cap    int
    91  }
    92  
    93  func (h *mergedBlkBuilder) reset() {
    94  	h.blocks.Clear()
    95  }
    96  
    97  func (h *mergedBlkBuilder) push(item *mItem) {
    98  	heap.Push(&h.blocks, item)
    99  	if h.blocks.Len() > h.cap {
   100  		heap.Pop(&h.blocks)
   101  	}
   102  }
   103  
   104  // copy out the items in the heap
   105  func (h *mergedBlkBuilder) finish() []*catalog.BlockEntry {
   106  	ret := make([]*catalog.BlockEntry, h.blocks.Len())
   107  	for i, item := range h.blocks {
   108  		ret[i] = item.entry
   109  	}
   110  	return ret
   111  }
   112  
   113  // deletableSegBuilder founds deletable segemnts of a table.
   114  // if a segment has no any non-dropped blocks, it can be deleted. except the
   115  // segment has the max segment id, appender may creates block in it.
   116  type deletableSegBuilder struct {
   117  	segHasNonDropBlk bool
   118  	maxSegId         uint64
   119  	segCandids       []*catalog.SegmentEntry // appendable
   120  	nsegCandids      []*catalog.SegmentEntry // non-appendable
   121  }
   122  
   123  func (d *deletableSegBuilder) reset() {
   124  	d.segHasNonDropBlk = false
   125  	d.maxSegId = 0
   126  	d.segCandids = d.segCandids[:0]
   127  	d.nsegCandids = d.nsegCandids[:0]
   128  }
   129  
   130  func (d *deletableSegBuilder) resetForNewSeg() {
   131  	d.segHasNonDropBlk = false
   132  }
   133  
   134  // call this when a non dropped block was found when iterating blocks of a segment,
   135  // which make the builder skip this segment
   136  func (d *deletableSegBuilder) hintNonDropBlock() {
   137  	d.segHasNonDropBlk = true
   138  }
   139  
   140  func (d *deletableSegBuilder) push(entry *catalog.SegmentEntry) {
   141  	isAppendable := entry.IsAppendable()
   142  	if isAppendable && d.maxSegId < entry.ID {
   143  		d.maxSegId = entry.ID
   144  	}
   145  	if d.segHasNonDropBlk {
   146  		return
   147  	}
   148  	// all blocks has been dropped
   149  	if isAppendable {
   150  		d.segCandids = append(d.segCandids, entry)
   151  	} else {
   152  		d.nsegCandids = append(d.nsegCandids, entry)
   153  	}
   154  }
   155  
   156  // copy out segment entries expect the one with max segment id.
   157  func (d *deletableSegBuilder) finish() []*catalog.SegmentEntry {
   158  	sort.Slice(d.segCandids, func(i, j int) bool { return d.segCandids[i].ID < d.segCandids[j].ID })
   159  	if last := len(d.segCandids) - 1; last >= 0 && d.segCandids[last].ID == d.maxSegId {
   160  		d.segCandids = d.segCandids[:last]
   161  	}
   162  	if len(d.segCandids) == 0 && len(d.nsegCandids) == 0 {
   163  		return nil
   164  	}
   165  	ret := make([]*catalog.SegmentEntry, len(d.segCandids)+len(d.nsegCandids))
   166  	copy(ret[:len(d.segCandids)], d.segCandids)
   167  	copy(ret[len(d.segCandids):], d.nsegCandids)
   168  	if cnt := len(d.nsegCandids); cnt != 0 {
   169  		logutil.Info("Mergeblocks deletable nseg", zap.Int("cnt", cnt))
   170  	}
   171  	return ret
   172  }
   173  
   174  type stat struct {
   175  	ttl          time.Time
   176  	lastTotalRow int
   177  }
   178  
   179  func (st *stat) String() string {
   180  	return fmt.Sprintf("row%d[%s]", st.lastTotalRow, st.ttl)
   181  }
   182  
   183  // mergeLimiter consider update rate and time to decide to merge or not.
   184  type mergeLimiter struct {
   185  	stats map[uint64]*stat
   186  }
   187  
   188  // merge immediately if it has enough rows, skip if:
   189  // 1. has only a few rows or blocks
   190  // 2. is actively updating, which means total rows changes obviously compared with last time
   191  // in other cases, wait some time to merge
   192  func (ml *mergeLimiter) canMerge(tid uint64, totalRow int, blks int) bool {
   193  	if totalRow > constMergeRightNow {
   194  		logutil.Infof("Mergeblocks %d merge right now: %d rows %d blks", tid, totalRow, blks)
   195  		delete(ml.stats, tid)
   196  		return true
   197  	}
   198  	if blks < constMergeMinBlks || totalRow < constMergeMinRows {
   199  		return false
   200  	}
   201  
   202  	if st, ok := ml.stats[tid]; !ok {
   203  		ml.stats[tid] = &stat{
   204  			ttl:          ml.ttl(totalRow),
   205  			lastTotalRow: totalRow,
   206  		}
   207  		return false
   208  	} else if d := totalRow - st.lastTotalRow; d > 5 || d < -5 {
   209  		// a lot of things happened in the past scan interval...
   210  		st.ttl = ml.ttl(totalRow)
   211  		st.lastTotalRow = totalRow
   212  		logutil.Infof("Mergeblocks delta %d on table %d, resched to %v", d, tid, st.ttl)
   213  		return false
   214  	} else {
   215  		// this table is quiet finally, check ttl
   216  		return st.ttl.Before(time.Now())
   217  	}
   218  }
   219  
   220  func (ml *mergeLimiter) ttl(totalRow int) time.Time {
   221  	return time.Now().Add(time.Duration(
   222  		(float32(constMergeWaitDuration) / float32(constMergeRightNow)) *
   223  			(float32(constMergeRightNow) - float32(totalRow))))
   224  }
   225  
   226  // prune old stat entry
   227  func (ml *mergeLimiter) pruneStale() {
   228  	staleIds := make([]uint64, 0)
   229  	t := time.Now().Add(-10 * time.Minute)
   230  	for id, st := range ml.stats {
   231  		if st.ttl.Before(t) {
   232  			staleIds = append(staleIds, id)
   233  		}
   234  	}
   235  	for _, id := range staleIds {
   236  		delete(ml.stats, id)
   237  	}
   238  }
   239  
   240  func (ml *mergeLimiter) String() string {
   241  	return fmt.Sprintf("%v", ml.stats)
   242  }
   243  
   244  type MergeTaskBuilder struct {
   245  	db *DB
   246  	*catalog.LoopProcessor
   247  	runCnt      int
   248  	tableRowCnt int
   249  	tid         uint64
   250  	limiter     *mergeLimiter
   251  	segBuilder  *deletableSegBuilder
   252  	blkBuilder  *mergedBlkBuilder
   253  }
   254  
   255  func newMergeTaskBuiler(db *DB) *MergeTaskBuilder {
   256  	op := &MergeTaskBuilder{
   257  		db:            db,
   258  		LoopProcessor: new(catalog.LoopProcessor),
   259  		limiter: &mergeLimiter{
   260  			stats: make(map[uint64]*stat),
   261  		},
   262  		segBuilder: &deletableSegBuilder{
   263  			segCandids:  make([]*catalog.SegmentEntry, 0),
   264  			nsegCandids: make([]*catalog.SegmentEntry, 0),
   265  		},
   266  		blkBuilder: &mergedBlkBuilder{
   267  			blocks: make(itemSet, 0, constHeapCapacity),
   268  			cap:    constHeapCapacity,
   269  		},
   270  	}
   271  
   272  	op.TableFn = op.onTable
   273  	op.BlockFn = op.onBlock
   274  	op.SegmentFn = op.onSegment
   275  	op.PostSegmentFn = op.onPostSegment
   276  	return op
   277  }
   278  
   279  func (s *MergeTaskBuilder) trySchedMergeTask() {
   280  	if s.tid == 0 {
   281  		return
   282  	}
   283  	// compactable blks
   284  	mergedBlks := s.blkBuilder.finish()
   285  	// deletable segs
   286  	mergedSegs := s.segBuilder.finish()
   287  	hasDelSeg := len(mergedSegs) > 0
   288  	hasMergeBlk := s.limiter.canMerge(s.tid, s.tableRowCnt, len(mergedBlks))
   289  	if !hasDelSeg && !hasMergeBlk {
   290  		return
   291  	}
   292  
   293  	segScopes := make([]common.ID, len(mergedSegs))
   294  	for i, s := range mergedSegs {
   295  		segScopes[i] = *s.AsCommonID()
   296  	}
   297  
   298  	// remove stale segments only
   299  	if hasDelSeg && !hasMergeBlk {
   300  		factory := func(ctx *tasks.Context, txn txnif.AsyncTxn) (tasks.Task, error) {
   301  			return jobs.NewDelSegTask(ctx, txn, mergedSegs), nil
   302  		}
   303  		_, err := s.db.Scheduler.ScheduleMultiScopedTxnTask(nil, tasks.DataCompactionTask, segScopes, factory)
   304  		if err != nil {
   305  			logutil.Infof("[Mergeblocks] Schedule del seg errinfo=%v", err)
   306  			return
   307  		}
   308  		logutil.Infof("[Mergeblocks] Scheduled | del %d seg", len(mergedSegs))
   309  		return
   310  	}
   311  
   312  	scopes := make([]common.ID, len(mergedBlks))
   313  	for i, blk := range mergedBlks {
   314  		scopes[i] = *blk.AsCommonID()
   315  	}
   316  
   317  	factory := func(ctx *tasks.Context, txn txnif.AsyncTxn) (tasks.Task, error) {
   318  		return jobs.NewMergeBlocksTask(ctx, txn, mergedBlks, mergedSegs, nil, s.db.Scheduler)
   319  	}
   320  	_, err := s.db.Scheduler.ScheduleMultiScopedTxnTask(nil, tasks.DataCompactionTask, scopes, factory)
   321  	if err != nil {
   322  		logutil.Infof("[Mergeblocks] Schedule errinfo=%v", err)
   323  	} else {
   324  		logutil.Infof("[Mergeblocks] Scheduled | Scopes=[%d],[%d]%s",
   325  			len(segScopes), len(scopes),
   326  			common.BlockIDArraryString(scopes[:constMergeMinBlks]))
   327  	}
   328  }
   329  
   330  func (s *MergeTaskBuilder) resetForTable(tid uint64) {
   331  	s.tableRowCnt = 0
   332  	s.tid = tid
   333  	s.segBuilder.reset()
   334  	s.blkBuilder.reset()
   335  }
   336  
   337  func (s *MergeTaskBuilder) PreExecute() error {
   338  	// clean stale stats for every 10min (default)
   339  	if s.runCnt++; s.runCnt >= 120 {
   340  		s.runCnt = 0
   341  		s.limiter.pruneStale()
   342  	}
   343  
   344  	// print stats for every 50s (default)
   345  	if s.runCnt%10 == 0 {
   346  		logutil.Infof("Mergeblocks stats: %s", s.limiter.String())
   347  	}
   348  	return nil
   349  }
   350  func (s *MergeTaskBuilder) PostExecute() error {
   351  	s.trySchedMergeTask()
   352  	s.resetForTable(0)
   353  	return nil
   354  }
   355  
   356  func (s *MergeTaskBuilder) onTable(tableEntry *catalog.TableEntry) (err error) {
   357  	s.trySchedMergeTask()
   358  	s.resetForTable(tableEntry.ID)
   359  	if !tableEntry.IsActive() {
   360  		err = moerr.GetOkStopCurrRecur()
   361  	}
   362  	return
   363  }
   364  
   365  func (s *MergeTaskBuilder) onSegment(segmentEntry *catalog.SegmentEntry) (err error) {
   366  	if !segmentEntry.IsActive() || (!segmentEntry.IsAppendable() && segmentEntry.IsSorted()) {
   367  		return moerr.GetOkStopCurrRecur()
   368  	}
   369  	if !segmentEntry.IsAppendable() {
   370  		logutil.Info("Mergeblocks scan nseg", zap.Uint64("seg", segmentEntry.ID))
   371  	}
   372  	// handle appendable segs and unsorted non-appendable segs(which was written by cn)
   373  	s.segBuilder.resetForNewSeg()
   374  	return
   375  }
   376  
   377  func (s *MergeTaskBuilder) onPostSegment(segmentEntry *catalog.SegmentEntry) (err error) {
   378  	s.segBuilder.push(segmentEntry)
   379  	return nil
   380  }
   381  
   382  func (s *MergeTaskBuilder) onBlock(entry *catalog.BlockEntry) (err error) {
   383  	if !entry.IsActive() {
   384  		return
   385  	}
   386  	s.segBuilder.hintNonDropBlock()
   387  
   388  	entry.RLock()
   389  	defer entry.RUnlock()
   390  
   391  	// Skip uncommitted entries and appendable block
   392  	if !entry.IsCommitted() ||
   393  		!catalog.ActiveWithNoTxnFilter(entry.MetaBaseEntry) ||
   394  		!catalog.NonAppendableBlkFilter(entry) {
   395  		return
   396  	}
   397  
   398  	rows := entry.GetBlockData().Rows()
   399  	s.tableRowCnt += rows
   400  	s.blkBuilder.push(&mItem{row: rows, entry: entry})
   401  	return nil
   402  }