github.com/matrixorigin/matrixone@v1.2.0/pkg/vm/engine/tae/common/stats.go (about)

     1  // Copyright 2022 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 common
    16  
    17  import (
    18  	"encoding/hex"
    19  	"fmt"
    20  	"math"
    21  	"math/rand"
    22  	"sync"
    23  	"sync/atomic"
    24  	"time"
    25  
    26  	"github.com/matrixorigin/matrixone/pkg/container/types"
    27  	"golang.org/x/exp/constraints"
    28  )
    29  
    30  const (
    31  	DefaultMinOsizeQualifiedMB   = 110   // MB
    32  	DefaultMaxOsizeObjMB         = 128   // MB
    33  	DefaultMinCNMergeSize        = 80000 // MB
    34  	DefaultCNMergeMemControlHint = 8192  // MB
    35  	DefaultMaxMergeObjN          = 16
    36  
    37  	Const1GBytes = 1 << 30
    38  	Const1MBytes = 1 << 20
    39  )
    40  
    41  var (
    42  	RuntimeMaxMergeObjN        atomic.Int32
    43  	RuntimeOsizeRowsQualified  atomic.Uint32
    44  	RuntimeMaxObjOsize         atomic.Uint32
    45  	RuntimeMinCNMergeSize      atomic.Uint64
    46  	RuntimeCNMergeMemControl   atomic.Uint64
    47  	RuntimeCNTakeOverAll       atomic.Bool
    48  	IsStandaloneBoost          atomic.Bool
    49  	ShouldStandaloneCNTakeOver atomic.Bool
    50  	Epsilon                    float64
    51  
    52  	RuntimeOverallFlushMemCap atomic.Uint64
    53  )
    54  
    55  func init() {
    56  	RuntimeMaxMergeObjN.Store(DefaultMaxMergeObjN)
    57  	RuntimeOsizeRowsQualified.Store(DefaultMinOsizeQualifiedMB * Const1MBytes)
    58  	RuntimeMaxObjOsize.Store(DefaultMaxOsizeObjMB * Const1MBytes)
    59  	Epsilon = math.Nextafter(1, 2) - 1
    60  }
    61  
    62  type Number interface {
    63  	constraints.Integer | constraints.Float
    64  }
    65  
    66  ///////
    67  /// statistics component
    68  ///////
    69  
    70  type TrendKind int
    71  type WorkloadKind int
    72  
    73  const (
    74  	TrendUnknown TrendKind = iota
    75  	TrendDecII
    76  	TrendDecI
    77  	TrendStable
    78  	TrendIncI
    79  	TrendIncII
    80  )
    81  
    82  const (
    83  	WorkUnknown WorkloadKind = iota
    84  	WorkQuiet
    85  	WorkApInsert
    86  	WorkApQuiet
    87  	WorkTpUpdate
    88  	WorkMixed
    89  )
    90  
    91  func (t TrendKind) String() string {
    92  	switch t {
    93  	case TrendDecII:
    94  		return "DecII"
    95  	case TrendDecI:
    96  		return "DecI"
    97  	case TrendStable:
    98  		return "Stable"
    99  	case TrendIncI:
   100  		return "IncI"
   101  	case TrendIncII:
   102  		return "IncII"
   103  	default:
   104  		return "UnknownTrend"
   105  	}
   106  }
   107  
   108  type HistorySampler[T Number] interface {
   109  	Append(x T)
   110  	V() T
   111  	QueryTrend() (TrendKind, TrendKind, TrendKind)
   112  	String() string
   113  }
   114  
   115  // general sampler
   116  type SampleIII[T Number] struct {
   117  	new, sample, v1, v2, v3 T
   118  
   119  	lastSampleT time.Time
   120  
   121  	// f1 = 1.0
   122  	f2, f3 float64
   123  
   124  	// config
   125  	d1, d2, d3 time.Duration
   126  }
   127  
   128  var F2 = 0.7
   129  var F3 = 0.4
   130  var D1 = 30 * time.Second
   131  var D2 = 2 * time.Minute
   132  var D3 = 5 * time.Minute
   133  
   134  // optimize for saving space
   135  type FixedSampleIII[T Number] struct {
   136  	new, sample, v1, v2, v3 T
   137  	lastSampleT             time.Time
   138  }
   139  
   140  func (s *FixedSampleIII[T]) tick(x T) {
   141  	s.new = x
   142  	// init
   143  	if s.lastSampleT.IsZero() {
   144  		s.lastSampleT = time.Now()
   145  		s.sample = x
   146  		s.v1 = x
   147  		s.v2 = x
   148  		s.v3 = x
   149  		return
   150  	}
   151  	now := time.Now()
   152  	span := now.Sub(s.lastSampleT)
   153  	if span < D1 {
   154  		return
   155  	}
   156  	// rotate and sample this point
   157  
   158  	// cnt history values need to be pushed back
   159  	// cnt := int(span / s.samplePeriod)
   160  	if span > D3 {
   161  		s.v3 = s.sample
   162  		s.v2 = s.sample
   163  		s.v1 = s.sample
   164  	} else if span > D2 {
   165  		s.v3 = moveAvg(s.v3, s.v2, F2)
   166  		s.v2 = s.sample
   167  		s.v1 = s.sample
   168  	} else {
   169  		s.v3 = moveAvg(s.v3, s.v2, F3)
   170  		s.v2 = moveAvg(s.v2, s.v1, F2)
   171  		s.v1 = s.sample
   172  	}
   173  
   174  	s.lastSampleT = now
   175  	s.sample = x
   176  }
   177  
   178  func (s *FixedSampleIII[T]) Append(x T) {
   179  	s.tick(x)
   180  }
   181  
   182  func (s *FixedSampleIII[T]) V() T {
   183  	return s.new
   184  }
   185  
   186  func (s *FixedSampleIII[T]) QueryTrend() (TrendKind, TrendKind, TrendKind) {
   187  	judgeTrend := func(vprev, vnow T) TrendKind {
   188  		if roundZero(vprev) {
   189  			if vnow > 0 {
   190  				return TrendIncI
   191  			} else if vnow < 0 {
   192  				return TrendDecI
   193  			} else {
   194  				return TrendStable
   195  			}
   196  		}
   197  		delta := float64(vnow - vprev)
   198  		deltaPercent := math.Abs(delta / float64(vprev))
   199  		if math.Signbit(delta) {
   200  			deltaPercent = -deltaPercent
   201  		}
   202  
   203  		if deltaPercent < -0.4 {
   204  			return TrendDecII
   205  		} else if deltaPercent < -0.01 {
   206  			return TrendDecI
   207  		} else if deltaPercent < 0.01 {
   208  			return TrendStable
   209  		} else if deltaPercent < 0.4 {
   210  			return TrendIncI
   211  		} else {
   212  			return TrendIncII
   213  		}
   214  	}
   215  	s.tick(s.new)
   216  	return judgeTrend(s.v1, s.new), judgeTrend(s.v2, s.new), judgeTrend(s.v3, s.new)
   217  }
   218  
   219  func (s *FixedSampleIII[T]) String() string {
   220  	x, m, l := s.QueryTrend()
   221  	return fmt.Sprintf(
   222  		"Sample(%v/%v/{%v,%v,%v}/%v,%v,%v)",
   223  		s.new, s.lastSampleT.Format("2006-01-02_15:04:05"),
   224  		s.v1, s.v2, s.v3,
   225  		x, m, l,
   226  	)
   227  }
   228  
   229  func NewSmapler35m[T Number]() FixedSampleIII[T] {
   230  	return FixedSampleIII[T]{}
   231  }
   232  
   233  func selectFactor(cnt int) float64 {
   234  	list := []int{2, 3, 4, 6, 7, 10, 13, 21, 44}
   235  	for i, v := range list {
   236  		if cnt <= v {
   237  			return float64(9-i) / 10.0
   238  		}
   239  	}
   240  	return 0.05
   241  }
   242  
   243  func NewSampleIII[T Number](sp, d2, d3 time.Duration) *SampleIII[T] {
   244  	return &SampleIII[T]{
   245  		d1: sp,
   246  		d2: d2,
   247  		d3: d3,
   248  		f2: selectFactor(int(d2 / sp)),
   249  		f3: selectFactor(int(d3 / sp)),
   250  	}
   251  }
   252  
   253  func (s *SampleIII[T]) tick(x T) {
   254  	s.new = x
   255  	// init
   256  	if s.lastSampleT.IsZero() {
   257  		s.lastSampleT = time.Now()
   258  		s.sample = x
   259  		s.v1 = x
   260  		s.v2 = x
   261  		s.v3 = x
   262  		return
   263  	}
   264  	now := time.Now()
   265  	span := now.Sub(s.lastSampleT)
   266  	if span < s.d1 {
   267  		return
   268  	}
   269  	// rotate and sample this point
   270  
   271  	// cnt history values need to be pushed back
   272  	// cnt := int(span / s.samplePeriod)
   273  	if span > s.d3 {
   274  		s.v3 = s.sample
   275  		s.v2 = s.sample
   276  		s.v1 = s.sample
   277  	} else if span > s.d2 {
   278  		s.v3 = moveAvg(s.v3, s.v2, s.f2)
   279  		s.v2 = s.sample
   280  		s.v1 = s.sample
   281  	} else {
   282  		s.v3 = moveAvg(s.v3, s.v2, s.f3)
   283  		s.v2 = moveAvg(s.v2, s.v1, s.f2)
   284  		s.v1 = s.sample
   285  	}
   286  
   287  	s.lastSampleT = now
   288  	s.sample = x
   289  }
   290  
   291  func (s *SampleIII[T]) Append(x T) {
   292  	s.tick(x)
   293  }
   294  
   295  func (s *SampleIII[T]) V() T {
   296  	return s.new
   297  }
   298  
   299  func (s *SampleIII[T]) QueryTrend() (TrendKind, TrendKind, TrendKind) {
   300  	judgeTrend := func(vprev, vnow T) TrendKind {
   301  		if roundZero(vprev) {
   302  			if vnow > 0 {
   303  				return TrendIncI
   304  			} else if vnow < 0 {
   305  				return TrendDecI
   306  			} else {
   307  				return TrendStable
   308  			}
   309  		}
   310  		delta := float64(vnow - vprev)
   311  		deltaPercent := math.Abs(delta / float64(vprev))
   312  		if math.Signbit(delta) {
   313  			deltaPercent = -deltaPercent
   314  		}
   315  
   316  		if deltaPercent < -0.4 {
   317  			return TrendDecII
   318  		} else if deltaPercent < -0.01 {
   319  			return TrendDecI
   320  		} else if deltaPercent < 0.01 {
   321  			return TrendStable
   322  		} else if deltaPercent < 0.4 {
   323  			return TrendIncI
   324  		} else {
   325  			return TrendIncII
   326  		}
   327  	}
   328  	s.tick(s.new)
   329  	return judgeTrend(s.v1, s.new), judgeTrend(s.v2, s.new), judgeTrend(s.v3, s.new)
   330  }
   331  
   332  func (s *SampleIII[T]) String() string {
   333  	x, m, l := s.QueryTrend()
   334  	return fmt.Sprintf(
   335  		"Sample(%v/%v/{%v@-%v,%v@-%v(%.2f),%v@-%v(%.2f)}/%v,%v,%v)",
   336  		s.new, s.lastSampleT.Format("2006-01-02_15:04:05"),
   337  		s.v1, s.d1,
   338  		s.v2, s.d2, s.f2,
   339  		s.v3, s.d3, s.f3,
   340  		x, m, l,
   341  	)
   342  }
   343  
   344  type MergeHistory struct {
   345  	LastTime time.Time
   346  	OSize    int
   347  	NObj     int
   348  	NBlk     int
   349  }
   350  
   351  func (h *MergeHistory) Add(osize, nobj, nblk int) {
   352  	h.OSize = osize
   353  	h.NObj = nobj
   354  	h.NBlk = nblk
   355  	h.LastTime = time.Now()
   356  }
   357  
   358  func (h *MergeHistory) IsLastBefore(d time.Duration) bool {
   359  	return h.LastTime.Before(time.Now().Add(-d))
   360  }
   361  
   362  func (h *MergeHistory) String() string {
   363  	return fmt.Sprintf(
   364  		"(%v) no%v nb%v osize%v",
   365  		h.LastTime.Format("2006-01-02_15:04:05"),
   366  		h.NObj, h.NBlk,
   367  		HumanReadableBytes(h.OSize),
   368  	)
   369  }
   370  
   371  ///
   372  /// Table statistics
   373  ///
   374  
   375  type TableCompactStat struct {
   376  	sync.RWMutex
   377  
   378  	Inited bool
   379  
   380  	// Configs
   381  
   382  	// how often to flush table tail
   383  	// this duration will be add some random value to avoid flush many tables at the same time
   384  	FlushGapDuration time.Duration
   385  	// if the size of table tail, in bytes, exceeds FlushMemCapacity, flush it immediately
   386  	FlushMemCapacity int
   387  
   388  	// Status
   389  
   390  	// dirty end range flushed by last flush txn. If we are waiting for a ckp [a, b], and all dirty tables' LastFlush are greater than b,
   391  	// the checkpoint is ready to collect data and write all down.
   392  	LastFlush types.TS
   393  	// FlushDeadline is the deadline to flush table tail
   394  	FlushDeadline time.Time
   395  
   396  	WorkloadGuess  WorkloadKind
   397  	WorkloadStreak int
   398  	RowCnt         FixedSampleIII[int]
   399  	RowDel         FixedSampleIII[int]
   400  	MergeHist      MergeHistory
   401  }
   402  
   403  func NewTableCompactStat() *TableCompactStat {
   404  	return &TableCompactStat{
   405  		RowCnt: FixedSampleIII[int]{},
   406  		RowDel: FixedSampleIII[int]{},
   407  	}
   408  }
   409  
   410  func (s *TableCompactStat) ResetDeadlineWithLock() {
   411  	// add random +/- 10%
   412  	factor := 1.0 + float64(rand.Intn(21)-10)/100.0
   413  	s.FlushDeadline = time.Now().Add(time.Duration(factor * float64(s.FlushGapDuration)))
   414  }
   415  
   416  func (s *TableCompactStat) InitWithLock(durationHint time.Duration) {
   417  	s.FlushGapDuration = durationHint * 5
   418  	s.FlushMemCapacity = 20 * 1024 * 1024
   419  	s.Inited = true
   420  }
   421  
   422  func (s *TableCompactStat) AddMerge(osize, nobj, nblk int) {
   423  	s.Lock()
   424  	defer s.Unlock()
   425  	s.MergeHist.Add(osize, nobj, nblk)
   426  }
   427  
   428  func (s *TableCompactStat) GetLastFlush() types.TS {
   429  	s.RLock()
   430  	defer s.RUnlock()
   431  	return s.LastFlush
   432  }
   433  
   434  func (s *TableCompactStat) GetLastMerge() *MergeHistory {
   435  	s.RLock()
   436  	defer s.RUnlock()
   437  	return &s.MergeHist
   438  }
   439  
   440  func (s *TableCompactStat) AddRowStat(rows, dels int) {
   441  	s.Lock()
   442  	defer s.Unlock()
   443  	s.RowCnt.Append(rows)
   444  	s.RowDel.Append(dels)
   445  
   446  	rs, rm, rl := s.RowCnt.QueryTrend()
   447  	ds, dm, dl := s.RowDel.QueryTrend()
   448  
   449  	guess := WorkUnknown
   450  	if IsApLikeDel(ds, dm, dl, dels) && s.RowCnt.V() > 1000*1000 {
   451  		if IsLongStable(rs, rm, rl) {
   452  			guess = WorkApQuiet
   453  		} else {
   454  			guess = WorkApInsert
   455  		}
   456  	}
   457  
   458  	// force a bigger merge
   459  	if s.WorkloadGuess == WorkApInsert && s.WorkloadStreak > 20 {
   460  		guess = WorkApQuiet
   461  	}
   462  
   463  	if s.WorkloadGuess == guess {
   464  		s.WorkloadStreak++
   465  	} else {
   466  		s.WorkloadGuess = guess
   467  		s.WorkloadStreak = 0
   468  	}
   469  }
   470  
   471  func (s *TableCompactStat) GetWorkloadGuess() WorkloadKind {
   472  	s.RLock()
   473  	defer s.RUnlock()
   474  	return s.WorkloadGuess
   475  }
   476  
   477  func IsApLikeDel(s, m, l TrendKind, val int) bool {
   478  	return s == TrendStable && m == TrendStable && l == TrendStable && val == 0
   479  }
   480  
   481  func IsLongStable(s, m, l TrendKind) bool {
   482  	return s == TrendStable && m == TrendStable && l <= TrendIncI && l >= TrendDecI
   483  }
   484  
   485  ////
   486  // Other utils
   487  ////
   488  
   489  func HumanReadableBytes(bytes int) string {
   490  	if bytes < 1024 {
   491  		return fmt.Sprintf("%dB", bytes)
   492  	}
   493  	if bytes < Const1MBytes {
   494  		return fmt.Sprintf("%.2fKB", float64(bytes)/1024)
   495  	}
   496  	if bytes < Const1GBytes {
   497  		return fmt.Sprintf("%.2fMB", float64(bytes)/1024/1024)
   498  	}
   499  	return fmt.Sprintf("%.2fGB", float64(bytes)/1024/1024/1024)
   500  }
   501  
   502  func ShortObjId(x types.Objectid) string {
   503  	var shortuuid [12]byte
   504  	hex.Encode(shortuuid[:], x[10:16])
   505  	return string(shortuuid[:])
   506  }
   507  
   508  func moveAvg[T Number](prev, now T, f float64) T {
   509  	return T((1-f)*float64(prev) + f*float64(now))
   510  }
   511  
   512  func roundZero[T Number](v T) bool {
   513  	return math.Abs(float64(v)) < Epsilon
   514  }