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 }