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  )