github.com/insionng/yougam@v0.0.0-20170714101924-2bc18d833463/libraries/pingcap/tidb/store/tikv/snapshot.go (about)

     1  // Copyright 2015 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  	"github.com/insionng/yougam/libraries/golang/protobuf/proto"
    18  	"github.com/insionng/yougam/libraries/juju/errors"
    19  	"github.com/insionng/yougam/libraries/ngaut/log"
    20  	pb "github.com/insionng/yougam/libraries/pingcap/kvproto/pkg/kvrpcpb"
    21  	"github.com/insionng/yougam/libraries/pingcap/tidb/kv"
    22  	"github.com/insionng/yougam/libraries/pingcap/tidb/terror"
    23  )
    24  
    25  var (
    26  	_ kv.Snapshot = (*tikvSnapshot)(nil)
    27  )
    28  
    29  const (
    30  	scanBatchSize = 100
    31  	maxGetCount   = 3
    32  	batchGetSize  = 100
    33  )
    34  
    35  // tikvSnapshot implements MvccSnapshot interface.
    36  type tikvSnapshot struct {
    37  	store   *tikvStore
    38  	version kv.Version
    39  }
    40  
    41  // newTiKVSnapshot creates a snapshot of an TiKV store.
    42  func newTiKVSnapshot(store *tikvStore, ver kv.Version) *tikvSnapshot {
    43  	return &tikvSnapshot{
    44  		store:   store,
    45  		version: ver,
    46  	}
    47  }
    48  
    49  // makeBatchGetReqs splits each key into corresponding region.
    50  func (s *tikvSnapshot) makeBatchGetReqs(keys []kv.Key) (map[RegionVerID]*batchGetRegion, error) {
    51  	startTS := s.version.Ver
    52  	multiBatchGet := map[RegionVerID]*batchGetRegion{}
    53  	for _, k := range keys {
    54  		region, err := s.store.regionCache.GetRegion(k)
    55  		if err != nil {
    56  			return nil, errors.Trace(err)
    57  		}
    58  		regionID := region.VerID()
    59  		singleBatchGet, ok := multiBatchGet[regionID]
    60  		if !ok {
    61  			singleBatchGet = &batchGetRegion{
    62  				CmdBatchGetRequest: &pb.CmdBatchGetRequest{
    63  					Version: proto.Uint64(startTS),
    64  				},
    65  				region: regionID,
    66  			}
    67  			multiBatchGet[regionID] = singleBatchGet
    68  		}
    69  		cmdBatchGetReq := singleBatchGet.CmdBatchGetRequest
    70  		cmdBatchGetReq.Keys = append(cmdBatchGetReq.Keys, k)
    71  	}
    72  	return multiBatchGet, nil
    73  }
    74  
    75  // doBatchGet sends BatchGet RPC request. If any key is locked, use tikvSnapshot.Get() to retry.
    76  func (s *tikvSnapshot) doBatchGet(singleBatchGet *batchGetRegion) (map[string][]byte, error) {
    77  	cmdBatchGetReq := singleBatchGet.CmdBatchGetRequest
    78  	keys := cmdBatchGetReq.GetKeys()
    79  	if len(keys) == 0 {
    80  		return nil, nil
    81  	}
    82  	req := &pb.Request{
    83  		Type:           pb.MessageType_CmdBatchGet.Enum(),
    84  		CmdBatchGetReq: cmdBatchGetReq,
    85  	}
    86  	resp, err := s.store.SendKVReq(req, singleBatchGet.region)
    87  	if err != nil {
    88  		return nil, errors.Trace(err)
    89  	}
    90  	if regionErr := resp.GetRegionError(); regionErr != nil {
    91  		//TODO: retry internally
    92  		return nil, errors.Annotate(errors.New(regionErr.String()), txnRetryableMark)
    93  	}
    94  	cmdBatchGetResp := resp.GetCmdBatchGetResp()
    95  	if cmdBatchGetResp == nil {
    96  		return nil, errors.Trace(errBodyMissing)
    97  	}
    98  	pairs := cmdBatchGetResp.GetPairs()
    99  	m := make(map[string][]byte, len(pairs))
   100  	for _, pair := range pairs {
   101  		keyErr := pair.GetError()
   102  		if keyErr == nil {
   103  			if val := pair.GetValue(); len(val) > 0 {
   104  				m[string(pair.GetKey())] = val
   105  			}
   106  			continue
   107  		}
   108  		lockInfo, err := extractLockInfoFromKeyErr(keyErr)
   109  		if err != nil {
   110  			return nil, errors.Trace(err)
   111  		}
   112  		val, err := s.Get(lockInfo.GetKey())
   113  		if err != nil {
   114  			if terror.ErrorEqual(err, kv.ErrNotExist) {
   115  				continue
   116  			}
   117  			return nil, errors.Trace(err)
   118  		}
   119  		m[string(lockInfo.GetKey())] = val
   120  	}
   121  	return m, nil
   122  }
   123  
   124  // BatchGet gets all the keys' value from kv-server and returns a map contains key/value pairs.
   125  // The map will not contain nonexistent keys.
   126  func (s *tikvSnapshot) BatchGet(keys []kv.Key) (map[string][]byte, error) {
   127  	m := make(map[string][]byte, len(keys))
   128  
   129  	multiBatchGet, err := s.makeBatchGetReqs(keys)
   130  	if err != nil {
   131  		return nil, errors.Trace(err)
   132  	}
   133  	for _, singleBatchGet := range multiBatchGet {
   134  		keys := singleBatchGet.GetKeys()
   135  		for startIdx := 0; startIdx < len(keys); startIdx += batchGetSize {
   136  			endIdx := startIdx + batchGetSize
   137  			if endIdx > len(keys) {
   138  				endIdx = len(keys)
   139  			}
   140  			newSingleBatchGet := &batchGetRegion{
   141  				CmdBatchGetRequest: &pb.CmdBatchGetRequest{
   142  					Keys:    keys[startIdx:endIdx],
   143  					Version: proto.Uint64(singleBatchGet.GetVersion()),
   144  				},
   145  				region: singleBatchGet.region,
   146  			}
   147  			res, err := s.doBatchGet(newSingleBatchGet)
   148  			if err != nil {
   149  				return nil, errors.Trace(err)
   150  			}
   151  			m, err = mergeResult(m, res)
   152  			if err != nil {
   153  				return nil, errors.Trace(err)
   154  			}
   155  		}
   156  	}
   157  
   158  	return m, nil
   159  }
   160  
   161  // Get gets the value for key k from snapshot.
   162  func (s *tikvSnapshot) Get(k kv.Key) ([]byte, error) {
   163  	req := &pb.Request{
   164  		Type: pb.MessageType_CmdGet.Enum(),
   165  		CmdGetReq: &pb.CmdGetRequest{
   166  			Key:     k,
   167  			Version: proto.Uint64(s.version.Ver),
   168  		},
   169  	}
   170  
   171  	var (
   172  		backoffErr    error
   173  		regionBackoff = regionMissBackoff()
   174  		txnBackoff    = txnLockBackoff()
   175  	)
   176  	for backoffErr == nil {
   177  		region, err := s.store.regionCache.GetRegion(k)
   178  		if err != nil {
   179  			return nil, errors.Trace(err)
   180  		}
   181  		resp, err := s.store.SendKVReq(req, region.VerID())
   182  		if err != nil {
   183  			return nil, errors.Trace(err)
   184  		}
   185  		if regionErr := resp.GetRegionError(); regionErr != nil {
   186  			backoffErr = regionBackoff()
   187  			continue
   188  		}
   189  		cmdGetResp := resp.GetCmdGetResp()
   190  		if cmdGetResp == nil {
   191  			return nil, errors.Trace(errBodyMissing)
   192  		}
   193  		val := cmdGetResp.GetValue()
   194  		if keyErr := cmdGetResp.GetError(); keyErr != nil {
   195  			val, err = s.handleKeyError(keyErr)
   196  			if err != nil {
   197  				if terror.ErrorEqual(err, errInnerRetryable) {
   198  					backoffErr = txnBackoff()
   199  					continue
   200  				}
   201  				return nil, errors.Trace(err)
   202  			}
   203  		}
   204  		if len(val) == 0 {
   205  			return nil, kv.ErrNotExist
   206  		}
   207  		return val, nil
   208  	}
   209  	return nil, errors.Annotate(backoffErr, txnRetryableMark)
   210  }
   211  
   212  // Seek return a list of key-value pair after `k`.
   213  func (s *tikvSnapshot) Seek(k kv.Key) (kv.Iterator, error) {
   214  	scanner, err := newScanner(s, k, scanBatchSize)
   215  	return scanner, errors.Trace(err)
   216  }
   217  
   218  // SeekReverse creates a reversed Iterator positioned on the first entry which key is less than k.
   219  func (s *tikvSnapshot) SeekReverse(k kv.Key) (kv.Iterator, error) {
   220  	return nil, kv.ErrNotImplemented
   221  }
   222  
   223  // Release unimplement.
   224  func (s *tikvSnapshot) Release() {
   225  }
   226  
   227  func extractLockInfoFromKeyErr(keyErr *pb.KeyError) (*pb.LockInfo, error) {
   228  	if locked := keyErr.GetLocked(); locked != nil {
   229  		return locked, nil
   230  	}
   231  	if keyErr.Retryable != nil {
   232  		err := errors.Errorf("tikv restarts txn: %s", keyErr.GetRetryable())
   233  		log.Warn(err)
   234  		return nil, errors.Annotate(err, txnRetryableMark)
   235  	}
   236  	if keyErr.Abort != nil {
   237  		err := errors.Errorf("tikv aborts txn: %s", keyErr.GetAbort())
   238  		log.Warn(err)
   239  		return nil, errors.Trace(err)
   240  	}
   241  	return nil, errors.Errorf("unexpected KeyError: %s", keyErr.String())
   242  }
   243  
   244  // handleKeyError tries to resolve locks then retry to get value.
   245  func (s *tikvSnapshot) handleKeyError(keyErr *pb.KeyError) ([]byte, error) {
   246  	lockInfo, err := extractLockInfoFromKeyErr(keyErr)
   247  	if err != nil {
   248  		return nil, errors.Trace(err)
   249  	}
   250  	lock := newLock(s.store, lockInfo.GetPrimaryLock(), lockInfo.GetLockVersion(), lockInfo.GetKey(), s.version.Ver)
   251  	val, err := lock.cleanup()
   252  	if err != nil {
   253  		return nil, errors.Trace(err)
   254  	}
   255  	return val, nil
   256  }
   257  
   258  // mergeResult Merge d2 into d1. If d1 and d2 are overlap, it returns error.
   259  func mergeResult(d1, d2 map[string][]byte) (map[string][]byte, error) {
   260  	if d1 == nil {
   261  		d1 = make(map[string][]byte)
   262  	}
   263  	for k2, v2 := range d2 {
   264  		if v1, ok := d1[k2]; ok {
   265  			// Because compare []byte takes too much time,
   266  			// if conflict return error directly even their values are same.
   267  			return nil, errors.Errorf("add dict conflict key[%s] v1[%q] v2[%q]",
   268  				k2, v1, v2)
   269  		}
   270  		d1[k2] = v2
   271  	}
   272  	return d1, nil
   273  }
   274  
   275  type batchGetRegion struct {
   276  	*pb.CmdBatchGetRequest
   277  	region RegionVerID
   278  }