github.com/whtcorpsinc/MilevaDB-Prod@v0.0.0-20211104133533-f57f4be3b597/causetstore/stochastik/milevadb.go (about) 1 // Copyright 2020 The ql Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSES/QL-LICENSE file. 4 5 // Copyright 2020 WHTCORPS INC, Inc. 6 // 7 // Licensed under the Apache License, Version 2.0 (the "License"); 8 // you may not use this file except in compliance with the License. 9 // You may obtain a copy of the License at 10 // 11 // http://www.apache.org/licenses/LICENSE-2.0 12 // 13 // Unless required by applicable law or agreed to in writing, software 14 // distributed under the License is distributed on an "AS IS" BASIS, 15 // See the License for the specific language governing permissions and 16 // limitations under the License. 17 18 package stochastik 19 20 import ( 21 "context" 22 "sync" 23 "sync/atomic" 24 "time" 25 26 "github.com/whtcorpsinc/BerolinaSQL" 27 "github.com/whtcorpsinc/BerolinaSQL/allegrosql" 28 "github.com/whtcorpsinc/BerolinaSQL/ast" 29 "github.com/whtcorpsinc/BerolinaSQL/terror" 30 "github.com/whtcorpsinc/errors" 31 "github.com/whtcorpsinc/milevadb/config" 32 "github.com/whtcorpsinc/milevadb/ekv" 33 "github.com/whtcorpsinc/milevadb/errno" 34 "github.com/whtcorpsinc/milevadb/interlock" 35 "github.com/whtcorpsinc/milevadb/petri" 36 "github.com/whtcorpsinc/milevadb/soliton" 37 "github.com/whtcorpsinc/milevadb/soliton/chunk" 38 "github.com/whtcorpsinc/milevadb/soliton/logutil" 39 "github.com/whtcorpsinc/milevadb/soliton/sqlexec" 40 "github.com/whtcorpsinc/milevadb/stochastikctx" 41 "github.com/whtcorpsinc/milevadb/stochastikctx/variable" 42 "go.uber.org/zap" 43 ) 44 45 type petriMap struct { 46 petris map[string]*petri.Petri 47 mu sync.Mutex 48 } 49 50 func (dm *petriMap) Get(causetstore ekv.CausetStorage) (d *petri.Petri, err error) { 51 dm.mu.Lock() 52 defer dm.mu.Unlock() 53 54 // If this is the only petri instance, and the caller doesn't provide causetstore. 55 if len(dm.petris) == 1 && causetstore == nil { 56 for _, r := range dm.petris { 57 return r, nil 58 } 59 } 60 61 key := causetstore.UUID() 62 d = dm.petris[key] 63 if d != nil { 64 return 65 } 66 67 dbsLease := time.Duration(atomic.LoadInt64(&schemaLease)) 68 statisticLease := time.Duration(atomic.LoadInt64(&statsLease)) 69 idxUsageSyncLease := time.Duration(atomic.LoadInt64(&indexUsageSyncLease)) 70 err = soliton.RunWithRetry(soliton.DefaultMaxRetries, soliton.RetryInterval, func() (retry bool, err1 error) { 71 logutil.BgLogger().Info("new petri", 72 zap.String("causetstore", causetstore.UUID()), 73 zap.Stringer("dbs lease", dbsLease), 74 zap.Stringer("stats lease", statisticLease), 75 zap.Stringer("index usage sync lease", idxUsageSyncLease)) 76 factory := createStochastikFunc(causetstore) 77 sysFactory := createStochastikWithPetriFunc(causetstore) 78 d = petri.NewPetri(causetstore, dbsLease, statisticLease, idxUsageSyncLease, factory) 79 err1 = d.Init(dbsLease, sysFactory) 80 if err1 != nil { 81 // If we don't clean it, there are some dirty data when retrying the function of Init. 82 d.Close() 83 logutil.BgLogger().Error("[dbs] init petri failed", 84 zap.Error(err1)) 85 } 86 return true, err1 87 }) 88 if err != nil { 89 return nil, err 90 } 91 dm.petris[key] = d 92 93 return 94 } 95 96 func (dm *petriMap) Delete(causetstore ekv.CausetStorage) { 97 dm.mu.Lock() 98 delete(dm.petris, causetstore.UUID()) 99 dm.mu.Unlock() 100 } 101 102 var ( 103 domap = &petriMap{ 104 petris: map[string]*petri.Petri{}, 105 } 106 // causetstore.UUID()-> IfBootstrapped 107 storeBootstrapped = make(map[string]bool) 108 storeBootstrappedLock sync.Mutex 109 110 // schemaLease is the time for re-uFIDelating remote schemaReplicant. 111 // In online DBS, we must wait 2 * SchemaLease time to guarantee 112 // all servers get the neweset schemaReplicant. 113 // Default schemaReplicant lease time is 1 second, you can change it with a proper time, 114 // but you must know that too little may cause badly performance degradation. 115 // For production, you should set a big schemaReplicant lease, like 300s+. 116 schemaLease = int64(1 * time.Second) 117 118 // statsLease is the time for reload stats causet. 119 statsLease = int64(3 * time.Second) 120 121 // indexUsageSyncLease is the time for index usage synchronization. 122 indexUsageSyncLease = int64(60 * time.Second) 123 ) 124 125 // ResetStoreForWithEinsteinDBTest is only used in the test code. 126 // TODO: Remove domap and storeBootstrapped. Use causetstore.SetOption() to do it. 127 func ResetStoreForWithEinsteinDBTest(causetstore ekv.CausetStorage) { 128 domap.Delete(causetstore) 129 unsetStoreBootstrapped(causetstore.UUID()) 130 } 131 132 func setStoreBootstrapped(storeUUID string) { 133 storeBootstrappedLock.Lock() 134 defer storeBootstrappedLock.Unlock() 135 storeBootstrapped[storeUUID] = true 136 } 137 138 // unsetStoreBootstrapped delete causetstore uuid from stored bootstrapped map. 139 // currently this function only used for test. 140 func unsetStoreBootstrapped(storeUUID string) { 141 storeBootstrappedLock.Lock() 142 defer storeBootstrappedLock.Unlock() 143 delete(storeBootstrapped, storeUUID) 144 } 145 146 // SetSchemaLease changes the default schemaReplicant lease time for DBS. 147 // This function is very dangerous, don't use it if you really know what you do. 148 // SetSchemaLease only affects not local storage after bootstrapped. 149 func SetSchemaLease(lease time.Duration) { 150 atomic.StoreInt64(&schemaLease, int64(lease)) 151 } 152 153 // SetStatsLease changes the default stats lease time for loading stats info. 154 func SetStatsLease(lease time.Duration) { 155 atomic.StoreInt64(&statsLease, int64(lease)) 156 } 157 158 // SetIndexUsageSyncLease changes the default index usage sync lease time for loading info. 159 func SetIndexUsageSyncLease(lease time.Duration) { 160 atomic.StoreInt64(&indexUsageSyncLease, int64(lease)) 161 } 162 163 // DisableStats4Test disables the stats for tests. 164 func DisableStats4Test() { 165 SetStatsLease(-1) 166 } 167 168 // Parse parses a query string to raw ast.StmtNode. 169 func Parse(ctx stochastikctx.Context, src string) ([]ast.StmtNode, error) { 170 logutil.BgLogger().Debug("compiling", zap.String("source", src)) 171 charset, defCauslation := ctx.GetStochastikVars().GetCharsetInfo() 172 p := BerolinaSQL.New() 173 p.EnableWindowFunc(ctx.GetStochastikVars().EnableWindowFunction) 174 p.SetALLEGROSQLMode(ctx.GetStochastikVars().ALLEGROSQLMode) 175 stmts, warns, err := p.Parse(src, charset, defCauslation) 176 for _, warn := range warns { 177 ctx.GetStochastikVars().StmtCtx.AppendWarning(warn) 178 } 179 if err != nil { 180 logutil.BgLogger().Warn("compiling", 181 zap.String("source", src), 182 zap.Error(err)) 183 return nil, err 184 } 185 return stmts, nil 186 } 187 188 func recordAbortTxnDuration(sessVars *variable.StochastikVars) { 189 duration := time.Since(sessVars.TxnCtx.CreateTime).Seconds() 190 if sessVars.TxnCtx.IsPessimistic { 191 transactionDurationPessimisticAbort.Observe(duration) 192 } else { 193 transactionDurationOptimisticAbort.Observe(duration) 194 } 195 } 196 197 func finishStmt(ctx context.Context, se *stochastik, meetsErr error, allegrosql sqlexec.Statement) error { 198 err := autoCommitAfterStmt(ctx, se, meetsErr, allegrosql) 199 if se.txn.pending() { 200 // After run memex finish, txn state is still pending means the 201 // memex never need a Txn(), such as: 202 // 203 // set @@milevadb_general_log = 1 204 // set @@autocommit = 0 205 // select 1 206 // 207 // Reset txn state to invalid to dispose the pending start ts. 208 se.txn.changeToInvalid() 209 } 210 if err != nil { 211 return err 212 } 213 return checkStmtLimit(ctx, se) 214 } 215 216 func autoCommitAfterStmt(ctx context.Context, se *stochastik, meetsErr error, allegrosql sqlexec.Statement) error { 217 sessVars := se.stochastikVars 218 if meetsErr != nil { 219 if !sessVars.InTxn() { 220 logutil.BgLogger().Info("rollbackTxn for dbs/autocommit failed") 221 se.RollbackTxn(ctx) 222 recordAbortTxnDuration(sessVars) 223 } else if se.txn.Valid() && se.txn.IsPessimistic() && interlock.ErrDeadlock.Equal(meetsErr) { 224 logutil.BgLogger().Info("rollbackTxn for deadlock", zap.Uint64("txn", se.txn.StartTS())) 225 se.RollbackTxn(ctx) 226 recordAbortTxnDuration(sessVars) 227 } 228 return meetsErr 229 } 230 231 if !sessVars.InTxn() { 232 if err := se.CommitTxn(ctx); err != nil { 233 if _, ok := allegrosql.(*interlock.InterDircStmt).StmtNode.(*ast.CommitStmt); ok { 234 err = errors.Annotatef(err, "previous memex: %s", se.GetStochastikVars().PrevStmt) 235 } 236 return err 237 } 238 return nil 239 } 240 return nil 241 } 242 243 func checkStmtLimit(ctx context.Context, se *stochastik) error { 244 // If the user insert, insert, insert ... but never commit, MilevaDB would OOM. 245 // So we limit the memex count in a transaction here. 246 var err error 247 sessVars := se.GetStochastikVars() 248 history := GetHistory(se) 249 if history.Count() > int(config.GetGlobalConfig().Performance.StmtCountLimit) { 250 if !sessVars.BatchCommit { 251 se.RollbackTxn(ctx) 252 return errors.Errorf("memex count %d exceeds the transaction limitation, autocommit = %t", 253 history.Count(), sessVars.IsAutocommit()) 254 } 255 err = se.NewTxn(ctx) 256 // The transaction does not committed yet, we need to keep it in transaction. 257 // The last history could not be "commit"/"rollback" memex. 258 // It means it is impossible to start a new transaction at the end of the transaction. 259 // Because after the server executed "commit"/"rollback" memex, the stochastik is out of the transaction. 260 sessVars.SetStatusFlag(allegrosql.ServerStatusInTrans, true) 261 } 262 return err 263 } 264 265 // GetHistory get all stmtHistory in current txn. Exported only for test. 266 func GetHistory(ctx stochastikctx.Context) *StmtHistory { 267 hist, ok := ctx.GetStochastikVars().TxnCtx.History.(*StmtHistory) 268 if ok { 269 return hist 270 } 271 hist = new(StmtHistory) 272 ctx.GetStochastikVars().TxnCtx.History = hist 273 return hist 274 } 275 276 // GetRows4Test gets all the rows from a RecordSet, only used for test. 277 func GetRows4Test(ctx context.Context, sctx stochastikctx.Context, rs sqlexec.RecordSet) ([]chunk.Row, error) { 278 if rs == nil { 279 return nil, nil 280 } 281 var rows []chunk.Row 282 req := rs.NewChunk() 283 // Must reuse `req` for imitating server.(*clientConn).writeChunks 284 for { 285 err := rs.Next(ctx, req) 286 if err != nil { 287 return nil, err 288 } 289 if req.NumRows() == 0 { 290 break 291 } 292 293 iter := chunk.NewIterator4Chunk(req.CopyConstruct()) 294 for event := iter.Begin(); event != iter.End(); event = iter.Next() { 295 rows = append(rows, event) 296 } 297 } 298 return rows, nil 299 } 300 301 // ResultSetToStringSlice changes the RecordSet to [][]string. 302 func ResultSetToStringSlice(ctx context.Context, s Stochastik, rs sqlexec.RecordSet) ([][]string, error) { 303 rows, err := GetRows4Test(ctx, s, rs) 304 if err != nil { 305 return nil, err 306 } 307 err = rs.Close() 308 if err != nil { 309 return nil, err 310 } 311 sRows := make([][]string, len(rows)) 312 for i := range rows { 313 event := rows[i] 314 iRow := make([]string, event.Len()) 315 for j := 0; j < event.Len(); j++ { 316 if event.IsNull(j) { 317 iRow[j] = "<nil>" 318 } else { 319 d := event.GetCauset(j, &rs.Fields()[j].DeferredCauset.FieldType) 320 iRow[j], err = d.ToString() 321 if err != nil { 322 return nil, err 323 } 324 } 325 } 326 sRows[i] = iRow 327 } 328 return sRows, nil 329 } 330 331 // Stochastik errors. 332 var ( 333 ErrForUFIDelateCantRetry = terror.ClassStochastik.New(errno.ErrForUFIDelateCantRetry, errno.MyALLEGROSQLErrName[errno.ErrForUFIDelateCantRetry]) 334 )