github.com/whtcorpsinc/milevadb-prod@v0.0.0-20211104133533-f57f4be3b597/causetstore/milevadb-server/einsteindb/commit.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 einsteindb
    15  
    16  import (
    17  	"encoding/hex"
    18  
    19  	"github.com/opentracing/opentracing-go"
    20  	"github.com/prometheus/client_golang/prometheus"
    21  	pb "github.com/whtcorpsinc/ekvproto/pkg/ekvrpcpb"
    22  	"github.com/whtcorpsinc/errors"
    23  	"github.com/whtcorpsinc/milevadb/causetstore/einsteindb/einsteindbrpc"
    24  	"github.com/whtcorpsinc/milevadb/metrics"
    25  	"github.com/whtcorpsinc/milevadb/soliton/logutil"
    26  	"go.uber.org/zap"
    27  )
    28  
    29  type actionCommit struct{ retry bool }
    30  
    31  var _ twoPhaseCommitCausetAction = actionCommit{}
    32  
    33  var einsteindbSecondaryLockCleanupFailureCounterCommit = metrics.EinsteinDBSecondaryLockCleanupFailureCounter.WithLabelValues("commit")
    34  var EinsteinDBTxnRegionsNumHistogramCommit = metrics.EinsteinDBTxnRegionsNumHistogram.WithLabelValues(metricsTag("commit"))
    35  
    36  func (actionCommit) String() string {
    37  	return "commit"
    38  }
    39  
    40  func (actionCommit) EinsteinDBTxnRegionsNumHistogram() prometheus.Observer {
    41  	return EinsteinDBTxnRegionsNumHistogramCommit
    42  }
    43  
    44  func (actionCommit) handleSingleBatch(c *twoPhaseCommitter, bo *Backoffer, batch batchMutations) error {
    45  	req := einsteindbrpc.NewRequest(einsteindbrpc.CmdCommit, &pb.CommitRequest{
    46  		StartVersion:  c.startTS,
    47  		Keys:          batch.mutations.keys,
    48  		CommitVersion: c.commitTS,
    49  	}, pb.Context{Priority: c.priority, SyncLog: c.syncLog})
    50  
    51  	sender := NewRegionRequestSender(c.causetstore.regionCache, c.causetstore.client)
    52  	resp, err := sender.SendReq(bo, req, batch.region, readTimeoutShort)
    53  
    54  	// If we fail to receive response for the request that commits primary key, it will be undetermined whether this
    55  	// transaction has been successfully committed.
    56  	// Under this circumstance,  we can not declare the commit is complete (may lead to data lost), nor can we throw
    57  	// an error (may lead to the duplicated key error when upper level restarts the transaction). Currently the best
    58  	// solution is to populate this error and let upper layer drop the connection to the corresponding allegrosql client.
    59  	if batch.isPrimary && sender.rpcError != nil {
    60  		c.setUndeterminedErr(errors.Trace(sender.rpcError))
    61  	}
    62  
    63  	if err != nil {
    64  		return errors.Trace(err)
    65  	}
    66  	regionErr, err := resp.GetRegionError()
    67  	if err != nil {
    68  		return errors.Trace(err)
    69  	}
    70  	if regionErr != nil {
    71  		err = bo.Backoff(BoRegionMiss, errors.New(regionErr.String()))
    72  		if err != nil {
    73  			return errors.Trace(err)
    74  		}
    75  		// re-split keys and commit again.
    76  		err = c.doCausetActionOnMutations(bo, actionCommit{retry: true}, batch.mutations)
    77  		return errors.Trace(err)
    78  	}
    79  	if resp.Resp == nil {
    80  		return errors.Trace(ErrBodyMissing)
    81  	}
    82  	commitResp := resp.Resp.(*pb.CommitResponse)
    83  	// Here we can make sure einsteindb has processed the commit primary key request. So
    84  	// we can clean undetermined error.
    85  	if batch.isPrimary {
    86  		c.setUndeterminedErr(nil)
    87  	}
    88  	if keyErr := commitResp.GetError(); keyErr != nil {
    89  		if rejected := keyErr.GetCommitTsExpired(); rejected != nil {
    90  			logutil.Logger(bo.ctx).Info("2PC commitTS rejected by EinsteinDB, retry with a newer commitTS",
    91  				zap.Uint64("txnStartTS", c.startTS),
    92  				zap.Stringer("info", logutil.Hex(rejected)))
    93  
    94  			// UFIDelate commit ts and retry.
    95  			commitTS, err := c.causetstore.getTimestampWithRetry(bo)
    96  			if err != nil {
    97  				logutil.Logger(bo.ctx).Warn("2PC get commitTS failed",
    98  					zap.Error(err),
    99  					zap.Uint64("txnStartTS", c.startTS))
   100  				return errors.Trace(err)
   101  			}
   102  
   103  			c.mu.Lock()
   104  			c.commitTS = commitTS
   105  			c.mu.Unlock()
   106  			return c.commitMutations(bo, batch.mutations)
   107  		}
   108  
   109  		c.mu.RLock()
   110  		defer c.mu.RUnlock()
   111  		err = extractKeyErr(keyErr)
   112  		if c.mu.committed {
   113  			// No secondary key could be rolled back after it's primary key is committed.
   114  			// There must be a serious bug somewhere.
   115  			hexBatchKeys := func(keys [][]byte) []string {
   116  				var res []string
   117  				for _, k := range keys {
   118  					res = append(res, hex.EncodeToString(k))
   119  				}
   120  				return res
   121  			}
   122  			logutil.Logger(bo.ctx).Error("2PC failed commit key after primary key committed",
   123  				zap.Error(err),
   124  				zap.Uint64("txnStartTS", c.startTS),
   125  				zap.Uint64("commitTS", c.commitTS),
   126  				zap.Strings("keys", hexBatchKeys(batch.mutations.keys)))
   127  			return errors.Trace(err)
   128  		}
   129  		// The transaction maybe rolled back by concurrent transactions.
   130  		logutil.Logger(bo.ctx).Debug("2PC failed commit primary key",
   131  			zap.Error(err),
   132  			zap.Uint64("txnStartTS", c.startTS))
   133  		return err
   134  	}
   135  
   136  	c.mu.Lock()
   137  	defer c.mu.Unlock()
   138  	// Group that contains primary key is always the first.
   139  	// We mark transaction's status committed when we receive the first success response.
   140  	c.mu.committed = true
   141  	return nil
   142  }
   143  
   144  func (c *twoPhaseCommitter) commitMutations(bo *Backoffer, mutations CommitterMutations) error {
   145  	if span := opentracing.SpanFromContext(bo.ctx); span != nil && span.Tracer() != nil {
   146  		span1 := span.Tracer().StartSpan("twoPhaseCommitter.commitMutations", opentracing.ChildOf(span.Context()))
   147  		defer span1.Finish()
   148  		bo.ctx = opentracing.ContextWithSpan(bo.ctx, span1)
   149  	}
   150  
   151  	return c.doCausetActionOnMutations(bo, actionCommit{}, mutations)
   152  }