github.com/whtcorpsinc/milevadb-prod@v0.0.0-20211104133533-f57f4be3b597/causetstore/petri/schema_validator.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 petri
    15  
    16  import (
    17  	"sort"
    18  	"sync"
    19  	"time"
    20  
    21  	"github.com/whtcorpsinc/milevadb/schemareplicant"
    22  	"github.com/whtcorpsinc/milevadb/metrics"
    23  	"github.com/whtcorpsinc/milevadb/stochastikctx/variable"
    24  	"github.com/whtcorpsinc/milevadb/causetstore/einsteindb"
    25  	"github.com/whtcorpsinc/milevadb/causetstore/einsteindb/oracle"
    26  	"github.com/whtcorpsinc/milevadb/soliton/logutil"
    27  	"go.uber.org/zap"
    28  )
    29  
    30  type checkResult int
    31  
    32  const (
    33  	// ResultSucc means schemaValidator's check is passing.
    34  	ResultSucc checkResult = iota
    35  	// ResultFail means schemaValidator's check is fail.
    36  	ResultFail
    37  	// ResultUnknown means schemaValidator doesn't know the check would be success or fail.
    38  	ResultUnknown
    39  )
    40  
    41  // SchemaValidator is the interface for checking the validity of schemaReplicant version.
    42  type SchemaValidator interface {
    43  	// UFIDelate the schemaReplicant validator, add a new item, delete the expired deltaSchemaInfos.
    44  	// The latest schemaVer is valid within leaseGrantTime plus lease duration.
    45  	// Add the changed causet IDs to the new schemaReplicant information,
    46  	// which is produced when the oldSchemaVer is uFIDelated to the newSchemaVer.
    47  	UFIDelate(leaseGrantTime uint64, oldSchemaVer, newSchemaVer int64, change *einsteindb.RelatedSchemaChange)
    48  	// Check is it valid for a transaction to use schemaVer and related blocks, at timestamp txnTS.
    49  	Check(txnTS uint64, schemaVer int64, relatedPhysicalBlockIDs []int64) (*einsteindb.RelatedSchemaChange, checkResult)
    50  	// Stop stops checking the valid of transaction.
    51  	Stop()
    52  	// Restart restarts the schemaReplicant validator after it is stopped.
    53  	Restart()
    54  	// Reset resets SchemaValidator to initial state.
    55  	Reset()
    56  	// IsStarted indicates whether SchemaValidator is started.
    57  	IsStarted() bool
    58  }
    59  
    60  type deltaSchemaInfo struct {
    61  	schemaVersion  int64
    62  	relatedIDs     []int64
    63  	relatedCausetActions []uint64
    64  }
    65  
    66  type schemaValidator struct {
    67  	isStarted          bool
    68  	mux                sync.RWMutex
    69  	lease              time.Duration
    70  	latestSchemaVer    int64
    71  	latestSchemaReplicant   schemareplicant.SchemaReplicant
    72  	do                 *Petri
    73  	latestSchemaExpire time.Time
    74  	// deltaSchemaInfos is a queue that maintain the history of changes.
    75  	deltaSchemaInfos []deltaSchemaInfo
    76  }
    77  
    78  // NewSchemaValidator returns a SchemaValidator structure.
    79  func NewSchemaValidator(lease time.Duration, do *Petri) SchemaValidator {
    80  	return &schemaValidator{
    81  		isStarted:        true,
    82  		lease:            lease,
    83  		deltaSchemaInfos: make([]deltaSchemaInfo, 0, variable.DefMilevaDBMaxDeltaSchemaCount),
    84  		do:               do,
    85  	}
    86  }
    87  
    88  func (s *schemaValidator) IsStarted() bool {
    89  	s.mux.RLock()
    90  	isStarted := s.isStarted
    91  	s.mux.RUnlock()
    92  	return isStarted
    93  }
    94  
    95  func (s *schemaValidator) LatestSchemaVersion() int64 {
    96  	s.mux.RLock()
    97  	latestSchemaVer := s.latestSchemaVer
    98  	s.mux.RUnlock()
    99  	return latestSchemaVer
   100  }
   101  
   102  func (s *schemaValidator) Stop() {
   103  	logutil.BgLogger().Info("the schemaReplicant validator stops")
   104  	metrics.LoadSchemaCounter.WithLabelValues(metrics.SchemaValidatorStop).Inc()
   105  	s.mux.Lock()
   106  	defer s.mux.Unlock()
   107  	s.isStarted = false
   108  	s.latestSchemaVer = 0
   109  	s.deltaSchemaInfos = s.deltaSchemaInfos[:0]
   110  }
   111  
   112  func (s *schemaValidator) Restart() {
   113  	metrics.LoadSchemaCounter.WithLabelValues(metrics.SchemaValidatorRestart).Inc()
   114  	logutil.BgLogger().Info("the schemaReplicant validator restarts")
   115  	s.mux.Lock()
   116  	defer s.mux.Unlock()
   117  	s.isStarted = true
   118  }
   119  
   120  func (s *schemaValidator) Reset() {
   121  	metrics.LoadSchemaCounter.WithLabelValues(metrics.SchemaValidatorReset).Inc()
   122  	s.mux.Lock()
   123  	defer s.mux.Unlock()
   124  	s.isStarted = true
   125  	s.latestSchemaVer = 0
   126  	s.deltaSchemaInfos = s.deltaSchemaInfos[:0]
   127  }
   128  
   129  func (s *schemaValidator) UFIDelate(leaseGrantTS uint64, oldVer, currVer int64, change *einsteindb.RelatedSchemaChange) {
   130  	s.mux.Lock()
   131  	defer s.mux.Unlock()
   132  
   133  	if !s.isStarted {
   134  		logutil.BgLogger().Info("the schemaReplicant validator stopped before uFIDelating")
   135  		return
   136  	}
   137  
   138  	// Renew the lease.
   139  	s.latestSchemaVer = currVer
   140  	if s.do != nil {
   141  		s.latestSchemaReplicant = s.do.SchemaReplicant()
   142  	}
   143  	leaseGrantTime := oracle.GetTimeFromTS(leaseGrantTS)
   144  	leaseExpire := leaseGrantTime.Add(s.lease - time.Millisecond)
   145  	s.latestSchemaExpire = leaseExpire
   146  
   147  	// UFIDelate the schemaReplicant deltaItem information.
   148  	if currVer != oldVer {
   149  		s.enqueue(currVer, change)
   150  		var tblIDs []int64
   151  		var actionTypes []uint64
   152  		if change != nil {
   153  			tblIDs = change.PhyTblIDS
   154  			actionTypes = change.CausetActionTypes
   155  		}
   156  		logutil.BgLogger().Debug("uFIDelate schemaReplicant validator", zap.Int64("oldVer", oldVer),
   157  			zap.Int64("currVer", currVer), zap.Int64s("changedBlockIDs", tblIDs), zap.Uint64s("changedCausetActionTypes", actionTypes))
   158  	}
   159  }
   160  
   161  // isRelatedBlocksChanged returns the result whether relatedBlockIDs is changed
   162  // from usedVer to the latest schemaReplicant version.
   163  // NOTE, this function should be called under dagger!
   164  func (s *schemaValidator) isRelatedBlocksChanged(currVer int64, blockIDs []int64) (einsteindb.RelatedSchemaChange, bool) {
   165  	res := einsteindb.RelatedSchemaChange{}
   166  	if len(s.deltaSchemaInfos) == 0 {
   167  		metrics.LoadSchemaCounter.WithLabelValues(metrics.SchemaValidatorCacheEmpty).Inc()
   168  		logutil.BgLogger().Info("schemaReplicant change history is empty", zap.Int64("currVer", currVer))
   169  		return res, true
   170  	}
   171  	newerDeltas := s.findNewerDeltas(currVer)
   172  	if len(newerDeltas) == len(s.deltaSchemaInfos) {
   173  		metrics.LoadSchemaCounter.WithLabelValues(metrics.SchemaValidatorCacheMiss).Inc()
   174  		logutil.BgLogger().Info("the schemaReplicant version is much older than the latest version", zap.Int64("currVer", currVer),
   175  			zap.Int64("latestSchemaVer", s.latestSchemaVer), zap.Reflect("deltas", newerDeltas))
   176  		return res, true
   177  	}
   178  
   179  	changedTblMap := make(map[int64]uint64)
   180  	for _, item := range newerDeltas {
   181  		for i, tblID := range item.relatedIDs {
   182  			for _, relatedTblID := range blockIDs {
   183  				if tblID == relatedTblID {
   184  					changedTblMap[tblID] |= item.relatedCausetActions[i]
   185  				}
   186  			}
   187  		}
   188  	}
   189  	if len(changedTblMap) > 0 {
   190  		tblIds := make([]int64, 0, len(changedTblMap))
   191  		actionTypes := make([]uint64, 0, len(changedTblMap))
   192  		for id := range changedTblMap {
   193  			tblIds = append(tblIds, id)
   194  		}
   195  		sort.Slice(tblIds, func(i, j int) bool { return tblIds[i] < tblIds[j] })
   196  		for _, tblID := range tblIds {
   197  			actionTypes = append(actionTypes, changedTblMap[tblID])
   198  		}
   199  		res.PhyTblIDS = tblIds
   200  		res.CausetActionTypes = actionTypes
   201  		res.Amendable = true
   202  		return res, true
   203  	}
   204  	return res, false
   205  }
   206  
   207  func (s *schemaValidator) findNewerDeltas(currVer int64) []deltaSchemaInfo {
   208  	q := s.deltaSchemaInfos
   209  	pos := len(q)
   210  	for i := len(q) - 1; i >= 0 && q[i].schemaVersion > currVer; i-- {
   211  		pos = i
   212  	}
   213  	return q[pos:]
   214  }
   215  
   216  // Check checks schemaReplicant validity, returns true if use schemaVer and related blocks at txnTS is legal.
   217  func (s *schemaValidator) Check(txnTS uint64, schemaVer int64, relatedPhysicalBlockIDs []int64) (*einsteindb.RelatedSchemaChange, checkResult) {
   218  	s.mux.RLock()
   219  	defer s.mux.RUnlock()
   220  	if !s.isStarted {
   221  		logutil.BgLogger().Info("the schemaReplicant validator stopped before checking")
   222  		return nil, ResultUnknown
   223  	}
   224  	if s.lease == 0 {
   225  		return nil, ResultSucc
   226  	}
   227  
   228  	// Schema changed, result decided by whether related blocks change.
   229  	if schemaVer < s.latestSchemaVer {
   230  		// The DBS relatedPhysicalBlockIDs is empty.
   231  		if len(relatedPhysicalBlockIDs) == 0 {
   232  			logutil.BgLogger().Info("the related physical causet ID is empty", zap.Int64("schemaVer", schemaVer),
   233  				zap.Int64("latestSchemaVer", s.latestSchemaVer))
   234  			return nil, ResultFail
   235  		}
   236  
   237  		relatedChanges, changed := s.isRelatedBlocksChanged(schemaVer, relatedPhysicalBlockIDs)
   238  		if changed {
   239  			if relatedChanges.Amendable {
   240  				relatedChanges.LatestSchemaReplicant = s.latestSchemaReplicant
   241  				return &relatedChanges, ResultFail
   242  			}
   243  			return nil, ResultFail
   244  		}
   245  		return nil, ResultSucc
   246  	}
   247  
   248  	// Schema unchanged, maybe success or the schemaReplicant validator is unavailable.
   249  	t := oracle.GetTimeFromTS(txnTS)
   250  	if t.After(s.latestSchemaExpire) {
   251  		return nil, ResultUnknown
   252  	}
   253  	return nil, ResultSucc
   254  }
   255  
   256  func (s *schemaValidator) enqueue(schemaVersion int64, change *einsteindb.RelatedSchemaChange) {
   257  	maxCnt := int(variable.GetMaxDeltaSchemaCount())
   258  	if maxCnt <= 0 {
   259  		logutil.BgLogger().Info("the schemaReplicant validator enqueue", zap.Int("delta max count", maxCnt))
   260  		return
   261  	}
   262  
   263  	delta := deltaSchemaInfo{schemaVersion, []int64{}, []uint64{}}
   264  	if change != nil {
   265  		delta.relatedIDs = change.PhyTblIDS
   266  		delta.relatedCausetActions = change.CausetActionTypes
   267  	}
   268  	if len(s.deltaSchemaInfos) == 0 {
   269  		s.deltaSchemaInfos = append(s.deltaSchemaInfos, delta)
   270  		return
   271  	}
   272  
   273  	lastOffset := len(s.deltaSchemaInfos) - 1
   274  	// The first item we needn't to merge, because we hope to cover more versions.
   275  	if lastOffset != 0 && containIn(s.deltaSchemaInfos[lastOffset], delta) {
   276  		s.deltaSchemaInfos[lastOffset] = delta
   277  	} else {
   278  		s.deltaSchemaInfos = append(s.deltaSchemaInfos, delta)
   279  	}
   280  
   281  	if len(s.deltaSchemaInfos) > maxCnt {
   282  		logutil.BgLogger().Info("the schemaReplicant validator enqueue, queue is too long",
   283  			zap.Int("delta max count", maxCnt), zap.Int64("remove schemaReplicant version", s.deltaSchemaInfos[0].schemaVersion))
   284  		s.deltaSchemaInfos = s.deltaSchemaInfos[1:]
   285  	}
   286  }
   287  
   288  // containIn is checks if lasteDelta is included in curDelta considering causet id and action type.
   289  func containIn(lastDelta, curDelta deltaSchemaInfo) bool {
   290  	if len(lastDelta.relatedIDs) > len(curDelta.relatedIDs) {
   291  		return false
   292  	}
   293  
   294  	var isEqual bool
   295  	for i, lastTblID := range lastDelta.relatedIDs {
   296  		isEqual = false
   297  		for j, curTblID := range curDelta.relatedIDs {
   298  			if lastTblID == curTblID && lastDelta.relatedCausetActions[i] == curDelta.relatedCausetActions[j] {
   299  				isEqual = true
   300  				break
   301  			}
   302  		}
   303  		if !isEqual {
   304  			return false
   305  		}
   306  	}
   307  
   308  	return true
   309  }