github.com/insionng/yougam@v0.0.0-20170714101924-2bc18d833463/libraries/pingcap/tidb/store/tikv/txn_committer.go (about) 1 // Copyright 2016 PingCAP, 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 tikv 15 16 import ( 17 "bytes" 18 19 "github.com/insionng/yougam/libraries/golang/protobuf/proto" 20 "github.com/insionng/yougam/libraries/juju/errors" 21 "github.com/insionng/yougam/libraries/ngaut/log" 22 pb "github.com/insionng/yougam/libraries/pingcap/kvproto/pkg/kvrpcpb" 23 "github.com/insionng/yougam/libraries/pingcap/tidb/kv" 24 "github.com/insionng/yougam/libraries/pingcap/tidb/terror" 25 ) 26 27 type txnCommitter struct { 28 store *tikvStore 29 startTS uint64 30 keys [][]byte 31 mutations map[string]*pb.Mutation 32 writtenKeys [][]byte 33 commitTS uint64 34 committed bool 35 } 36 37 func newTxnCommitter(txn *tikvTxn) (*txnCommitter, error) { 38 var keys [][]byte 39 mutations := make(map[string]*pb.Mutation) 40 err := txn.us.WalkBuffer(func(k kv.Key, v []byte) error { 41 if len(v) > 0 { 42 mutations[string(k)] = &pb.Mutation{ 43 Op: pb.Op_Put.Enum(), 44 Key: k, 45 Value: v, 46 } 47 } else { 48 mutations[string(k)] = &pb.Mutation{ 49 Op: pb.Op_Del.Enum(), 50 Key: k, 51 } 52 } 53 keys = append(keys, k) 54 return nil 55 }) 56 if err != nil { 57 return nil, errors.Trace(err) 58 } 59 // Transactions without Put/Del, only Locks are readonly. 60 // We can skip commit directly. 61 if len(keys) == 0 { 62 return nil, nil 63 } 64 for _, lockKey := range txn.lockKeys { 65 if _, ok := mutations[string(lockKey)]; !ok { 66 mutations[string(lockKey)] = &pb.Mutation{ 67 Op: pb.Op_Lock.Enum(), 68 Key: lockKey, 69 } 70 keys = append(keys, lockKey) 71 } 72 } 73 return &txnCommitter{ 74 store: txn.store, 75 startTS: txn.StartTS(), 76 keys: keys, 77 mutations: mutations, 78 }, nil 79 } 80 81 func (c *txnCommitter) primary() []byte { 82 return c.keys[0] 83 } 84 85 func (c *txnCommitter) iterKeysByRegion(keys [][]byte, f func(RegionVerID, [][]byte) error) error { 86 groups := make(map[RegionVerID][][]byte) 87 var primaryRegionID RegionVerID 88 for _, k := range keys { 89 region, err := c.store.regionCache.GetRegion(k) 90 if err != nil { 91 return errors.Trace(err) 92 } 93 id := region.VerID() 94 if bytes.Compare(k, c.primary()) == 0 { 95 primaryRegionID = id 96 } 97 groups[id] = append(groups[id], k) 98 } 99 100 // Make sure the group that contains primary key goes first. 101 if primaryRegionID.id != 0 { 102 if err := f(primaryRegionID, groups[primaryRegionID]); err != nil { 103 return errors.Trace(err) 104 } 105 delete(groups, primaryRegionID) 106 } 107 for id, g := range groups { 108 if err := f(id, g); err != nil { 109 return errors.Trace(err) 110 } 111 } 112 return nil 113 } 114 115 func (c *txnCommitter) keyValueSize(key []byte) int { 116 size := c.keySize(key) 117 if mutation := c.mutations[string(key)]; mutation != nil { 118 size += len(mutation.Value) 119 } 120 return size 121 } 122 123 func (c *txnCommitter) keySize(key []byte) int { 124 return len(key) 125 } 126 127 func (c *txnCommitter) prewriteSingleRegion(regionID RegionVerID, keys [][]byte) error { 128 mutations := make([]*pb.Mutation, len(keys)) 129 for i, k := range keys { 130 mutations[i] = c.mutations[string(k)] 131 } 132 req := &pb.Request{ 133 Type: pb.MessageType_CmdPrewrite.Enum(), 134 CmdPrewriteReq: &pb.CmdPrewriteRequest{ 135 Mutations: mutations, 136 PrimaryLock: c.primary(), 137 StartVersion: proto.Uint64(c.startTS), 138 }, 139 } 140 141 var backoffErr error 142 for backoff := txnLockBackoff(); backoffErr == nil; backoffErr = backoff() { 143 resp, err := c.store.SendKVReq(req, regionID) 144 if err != nil { 145 return errors.Trace(err) 146 } 147 if regionErr := resp.GetRegionError(); regionErr != nil { 148 // re-split keys and prewrite again. 149 // TODO: The recursive maybe not able to exit if TiKV & 150 // PD are implemented incorrectly. A possible fix is 151 // introducing a 'max backoff time'. 152 err = c.prewriteKeys(keys) 153 return errors.Trace(err) 154 } 155 prewriteResp := resp.GetCmdPrewriteResp() 156 if prewriteResp == nil { 157 return errors.Trace(errBodyMissing) 158 } 159 keyErrs := prewriteResp.GetErrors() 160 if len(keyErrs) == 0 { 161 // We need to cleanup all written keys if transaction aborts. 162 c.writtenKeys = append(c.writtenKeys, keys...) 163 return nil 164 } 165 for _, keyErr := range keyErrs { 166 lockInfo, err := extractLockInfoFromKeyErr(keyErr) 167 if err != nil { 168 // It could be `Retryable` or `Abort`. 169 return errors.Trace(err) 170 } 171 lock := newLock(c.store, lockInfo.GetPrimaryLock(), lockInfo.GetLockVersion(), lockInfo.GetKey(), c.startTS) 172 _, err = lock.cleanup() 173 if err != nil && terror.ErrorNotEqual(err, errInnerRetryable) { 174 return errors.Trace(err) 175 } 176 } 177 } 178 return errors.Annotate(backoffErr, txnRetryableMark) 179 } 180 181 func (c *txnCommitter) commitSingleRegion(regionID RegionVerID, keys [][]byte) error { 182 req := &pb.Request{ 183 Type: pb.MessageType_CmdCommit.Enum(), 184 CmdCommitReq: &pb.CmdCommitRequest{ 185 StartVersion: proto.Uint64(c.startTS), 186 Keys: keys, 187 CommitVersion: proto.Uint64(c.commitTS), 188 }, 189 } 190 191 resp, err := c.store.SendKVReq(req, regionID) 192 if err != nil { 193 return errors.Trace(err) 194 } 195 if regionErr := resp.GetRegionError(); regionErr != nil { 196 // re-split keys and commit again. 197 err = c.commitKeys(keys) 198 return errors.Trace(err) 199 } 200 commitResp := resp.GetCmdCommitResp() 201 if commitResp == nil { 202 return errors.Trace(errBodyMissing) 203 } 204 if keyErr := commitResp.GetError(); keyErr != nil { 205 err = errors.Errorf("commit failed: %v", keyErr.String()) 206 if c.committed { 207 // No secondary key could be rolled back after it's primary key is committed. 208 // There must be a serious bug somewhere. 209 log.Errorf("txn failed commit key after primary key committed: %v", err) 210 return errors.Trace(err) 211 } 212 // The transaction maybe rolled back by concurrent transactions. 213 log.Warnf("txn failed commit primary key: %v, retry later", err) 214 return errors.Annotate(err, txnRetryableMark) 215 } 216 217 // Group that contains primary key is always the first. 218 // We mark transaction's status committed when we receive the first success response. 219 c.committed = true 220 return nil 221 } 222 223 func (c *txnCommitter) cleanupSingleRegion(regionID RegionVerID, keys [][]byte) error { 224 req := &pb.Request{ 225 Type: pb.MessageType_CmdBatchRollback.Enum(), 226 CmdBatchRollbackReq: &pb.CmdBatchRollbackRequest{ 227 Keys: keys, 228 StartVersion: proto.Uint64(c.startTS), 229 }, 230 } 231 resp, err := c.store.SendKVReq(req, regionID) 232 if err != nil { 233 return errors.Trace(err) 234 } 235 if regionErr := resp.GetRegionError(); regionErr != nil { 236 err = c.cleanupKeys(keys) 237 return errors.Trace(err) 238 } 239 rollbackResp := resp.GetCmdBatchRollbackResp() 240 if rollbackResp == nil { 241 return errors.Trace(errBodyMissing) 242 } 243 if keyErr := rollbackResp.GetError(); keyErr != nil { 244 err = errors.Errorf("cleanup failed: %s", keyErr) 245 log.Errorf("txn failed cleanup key: %v", err) 246 return errors.Trace(err) 247 } 248 return nil 249 } 250 251 func (c *txnCommitter) prewriteKeys(keys [][]byte) error { 252 return c.iterKeysByRegion(keys, batchIterFn(c.prewriteSingleRegion, c.keyValueSize)) 253 } 254 255 func (c *txnCommitter) commitKeys(keys [][]byte) error { 256 return c.iterKeysByRegion(keys, batchIterFn(c.commitSingleRegion, c.keySize)) 257 } 258 259 func (c *txnCommitter) cleanupKeys(keys [][]byte) error { 260 return c.iterKeysByRegion(keys, batchIterFn(c.cleanupSingleRegion, c.keySize)) 261 } 262 263 func (c *txnCommitter) Commit() error { 264 err := c.prewriteKeys(c.keys) 265 if err != nil { 266 log.Warnf("txn commit failed on prewrite: %v", err) 267 c.cleanupKeys(c.writtenKeys) 268 return errors.Trace(err) 269 } 270 271 commitTS, err := c.store.oracle.GetTimestamp() 272 if err != nil { 273 return errors.Trace(err) 274 } 275 c.commitTS = commitTS 276 277 err = c.commitKeys(c.keys) 278 if err != nil { 279 if !c.committed { 280 c.cleanupKeys(c.writtenKeys) 281 return errors.Trace(err) 282 } 283 log.Warnf("txn commit succeed with error: %v", err) 284 } 285 return nil 286 } 287 288 // TiKV recommends each RPC packet should be less than ~1MB. We keep each packet's 289 // Key+Value size below 512KB. 290 const txnCommitBatchSize = 512 * 1024 291 292 // batchIterfn wraps an iteration function and returns a new one that iterates 293 // keys by batch size. 294 func batchIterFn(f func(RegionVerID, [][]byte) error, sizeFn func([]byte) int) func(RegionVerID, [][]byte) error { 295 return func(id RegionVerID, keys [][]byte) error { 296 var start, end int 297 for start = 0; start < len(keys); start = end { 298 var size int 299 for end = start; end < len(keys) && size < txnCommitBatchSize; end++ { 300 size += sizeFn(keys[end]) 301 } 302 if err := f(id, keys[start:end]); err != nil { 303 return errors.Trace(err) 304 } 305 } 306 return nil 307 } 308 }