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  }