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 }