github.com/whtcorpsinc/MilevaDB-Prod@v0.0.0-20211104133533-f57f4be3b597/causetstore/milevadb-server/einsteindb/lock_resolver.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 "bytes" 18 "container/list" 19 "context" 20 "fmt" 21 "math" 22 "sync" 23 "time" 24 25 fidel "github.com/einsteindb/fidel/client" 26 "github.com/whtcorpsinc/ekvproto/pkg/ekvrpcpb" 27 "github.com/whtcorpsinc/errors" 28 "github.com/whtcorpsinc/failpoint" 29 "github.com/whtcorpsinc/milevadb/causetstore/einsteindb/einsteindbrpc" 30 "github.com/whtcorpsinc/milevadb/config" 31 "github.com/whtcorpsinc/milevadb/ekv" 32 "github.com/whtcorpsinc/milevadb/metrics" 33 "github.com/whtcorpsinc/milevadb/soliton/execdetails" 34 "github.com/whtcorpsinc/milevadb/soliton/logutil" 35 "go.uber.org/zap" 36 ) 37 38 // ResolvedCacheSize is max number of cached txn status. 39 const ResolvedCacheSize = 2048 40 41 // bigTxnThreshold : transaction involves keys exceed this threshold can be treated as `big transaction`. 42 const bigTxnThreshold = 16 43 44 var ( 45 einsteindbLockResolverCountWithBatchResolve = metrics.EinsteinDBLockResolverCounter.WithLabelValues("batch_resolve") 46 einsteindbLockResolverCountWithExpired = metrics.EinsteinDBLockResolverCounter.WithLabelValues("expired") 47 einsteindbLockResolverCountWithNotExpired = metrics.EinsteinDBLockResolverCounter.WithLabelValues("not_expired") 48 einsteindbLockResolverCountWithWaitExpired = metrics.EinsteinDBLockResolverCounter.WithLabelValues("wait_expired") 49 einsteindbLockResolverCountWithResolve = metrics.EinsteinDBLockResolverCounter.WithLabelValues("resolve") 50 einsteindbLockResolverCountWithResolveForWrite = metrics.EinsteinDBLockResolverCounter.WithLabelValues("resolve_for_write") 51 einsteindbLockResolverCountWithResolveAsync = metrics.EinsteinDBLockResolverCounter.WithLabelValues("resolve_async_commit") 52 einsteindbLockResolverCountWithWriteConflict = metrics.EinsteinDBLockResolverCounter.WithLabelValues("write_conflict") 53 einsteindbLockResolverCountWithQueryTxnStatus = metrics.EinsteinDBLockResolverCounter.WithLabelValues("query_txn_status") 54 einsteindbLockResolverCountWithQueryTxnStatusCommitted = metrics.EinsteinDBLockResolverCounter.WithLabelValues("query_txn_status_committed") 55 einsteindbLockResolverCountWithQueryTxnStatusRolledBack = metrics.EinsteinDBLockResolverCounter.WithLabelValues("query_txn_status_rolled_back") 56 einsteindbLockResolverCountWithQueryCheckSecondaryLocks = metrics.EinsteinDBLockResolverCounter.WithLabelValues("query_check_secondary_locks") 57 einsteindbLockResolverCountWithResolveLocks = metrics.EinsteinDBLockResolverCounter.WithLabelValues("query_resolve_locks") 58 einsteindbLockResolverCountWithResolveLockLite = metrics.EinsteinDBLockResolverCounter.WithLabelValues("query_resolve_lock_lite") 59 ) 60 61 // LockResolver resolves locks and also caches resolved txn status. 62 type LockResolver struct { 63 causetstore CausetStorage 64 mu struct { 65 sync.RWMutex 66 // resolved caches resolved txns (FIFO, txn id -> txnStatus). 67 resolved map[uint64]TxnStatus 68 recentResolved *list.List 69 } 70 testingKnobs struct { 71 meetLock func(locks []*Lock) 72 } 73 } 74 75 func newLockResolver(causetstore CausetStorage) *LockResolver { 76 r := &LockResolver{ 77 causetstore: causetstore, 78 } 79 r.mu.resolved = make(map[uint64]TxnStatus) 80 r.mu.recentResolved = list.New() 81 return r 82 } 83 84 // NewLockResolver is exported for other pkg to use, suppress unused warning. 85 var _ = NewLockResolver 86 87 // NewLockResolver creates a LockResolver. 88 // It is exported for other pkg to use. For instance, binlog service needs 89 // to determine a transaction's commit state. 90 func NewLockResolver(etcdAddrs []string, security config.Security, opts ...fidel.ClientOption) (*LockResolver, error) { 91 FIDelCli, err := fidel.NewClient(etcdAddrs, fidel.SecurityOption{ 92 CAPath: security.ClusterSSLCA, 93 CertPath: security.ClusterSSLCert, 94 KeyPath: security.ClusterSSLKey, 95 }, opts...) 96 if err != nil { 97 return nil, errors.Trace(err) 98 } 99 FIDelCli = execdetails.InterceptedFIDelClient{Client: FIDelCli} 100 uuid := fmt.Sprintf("einsteindb-%v", FIDelCli.GetClusterID(context.TODO())) 101 102 tlsConfig, err := security.ToTLSConfig() 103 if err != nil { 104 return nil, errors.Trace(err) 105 } 106 107 spekv, err := NewEtcdSafePointKV(etcdAddrs, tlsConfig) 108 if err != nil { 109 return nil, errors.Trace(err) 110 } 111 112 s, err := newEinsteinDBStore(uuid, &codecFIDelClient{FIDelCli}, spekv, newRPCClient(security), false, nil) 113 if err != nil { 114 return nil, errors.Trace(err) 115 } 116 return s.lockResolver, nil 117 } 118 119 // TxnStatus represents a txn's final status. It should be Lock or Commit or Rollback. 120 type TxnStatus struct { 121 ttl uint64 122 commitTS uint64 123 action ekvrpcpb.CausetAction 124 primaryLock *ekvrpcpb.LockInfo 125 } 126 127 // IsCommitted returns true if the txn's final status is Commit. 128 func (s TxnStatus) IsCommitted() bool { return s.ttl == 0 && s.commitTS > 0 } 129 130 // CommitTS returns the txn's commitTS. It is valid iff `IsCommitted` is true. 131 func (s TxnStatus) CommitTS() uint64 { return s.commitTS } 132 133 // TTL returns the TTL of the transaction if the transaction is still alive. 134 func (s TxnStatus) TTL() uint64 { return s.ttl } 135 136 // CausetAction returns what the CheckTxnStatus request have done to the transaction. 137 func (s TxnStatus) CausetAction() ekvrpcpb.CausetAction { return s.action } 138 139 // By default, locks after 3000ms is considered unusual (the client created the 140 // dagger might be dead). Other client may cleanup this HoTT of dagger. 141 // For locks created recently, we will do backoff and retry. 142 var defaultLockTTL uint64 = 3000 143 144 // ttl = ttlFactor * sqrt(writeSizeInMiB) 145 var ttlFactor = 6000 146 147 // Lock represents a dagger from einsteindb server. 148 type Lock struct { 149 Key []byte 150 Primary []byte 151 TxnID uint64 152 TTL uint64 153 TxnSize uint64 154 LockType ekvrpcpb.Op 155 UseAsyncCommit bool 156 LockForUFIDelateTS uint64 157 MinCommitTS uint64 158 } 159 160 func (l *Lock) String() string { 161 buf := bytes.NewBuffer(make([]byte, 0, 128)) 162 buf.WriteString("key: ") 163 prettyWriteKey(buf, l.Key) 164 buf.WriteString(", primary: ") 165 prettyWriteKey(buf, l.Primary) 166 return fmt.Sprintf("%s, txnStartTS: %d, lockForUFIDelateTS:%d, minCommitTs:%d, ttl: %d, type: %s", buf.String(), l.TxnID, l.LockForUFIDelateTS, l.MinCommitTS, l.TTL, l.LockType) 167 } 168 169 // NewLock creates a new *Lock. 170 func NewLock(l *ekvrpcpb.LockInfo) *Lock { 171 return &Lock{ 172 Key: l.GetKey(), 173 Primary: l.GetPrimaryLock(), 174 TxnID: l.GetLockVersion(), 175 TTL: l.GetLockTtl(), 176 TxnSize: l.GetTxnSize(), 177 LockType: l.LockType, 178 UseAsyncCommit: l.UseAsyncCommit, 179 LockForUFIDelateTS: l.LockForUFIDelateTs, 180 MinCommitTS: l.MinCommitTs, 181 } 182 } 183 184 func (lr *LockResolver) saveResolved(txnID uint64, status TxnStatus) { 185 lr.mu.Lock() 186 defer lr.mu.Unlock() 187 188 if _, ok := lr.mu.resolved[txnID]; ok { 189 return 190 } 191 lr.mu.resolved[txnID] = status 192 lr.mu.recentResolved.PushBack(txnID) 193 if len(lr.mu.resolved) > ResolvedCacheSize { 194 front := lr.mu.recentResolved.Front() 195 delete(lr.mu.resolved, front.Value.(uint64)) 196 lr.mu.recentResolved.Remove(front) 197 } 198 } 199 200 func (lr *LockResolver) getResolved(txnID uint64) (TxnStatus, bool) { 201 lr.mu.RLock() 202 defer lr.mu.RUnlock() 203 204 s, ok := lr.mu.resolved[txnID] 205 return s, ok 206 } 207 208 // BatchResolveLocks resolve locks in a batch. 209 // Used it in gcworker only! 210 func (lr *LockResolver) BatchResolveLocks(bo *Backoffer, locks []*Lock, loc RegionVerID) (bool, error) { 211 if len(locks) == 0 { 212 return true, nil 213 } 214 215 einsteindbLockResolverCountWithBatchResolve.Inc() 216 217 // The GCWorker kill all ongoing transactions, because it must make sure all 218 // locks have been cleaned before GC. 219 expiredLocks := locks 220 221 callerStartTS, err := lr.causetstore.GetOracle().GetTimestamp(bo.ctx) 222 if err != nil { 223 return false, errors.Trace(err) 224 } 225 226 txnInfos := make(map[uint64]uint64) 227 startTime := time.Now() 228 for _, l := range expiredLocks { 229 if _, ok := txnInfos[l.TxnID]; ok { 230 continue 231 } 232 einsteindbLockResolverCountWithExpired.Inc() 233 234 // Use currentTS = math.MaxUint64 means rollback the txn, no matter the dagger is expired or not! 235 status, err := lr.getTxnStatus(bo, l.TxnID, l.Primary, callerStartTS, math.MaxUint64, true) 236 if err != nil { 237 return false, err 238 } 239 240 // If the transaction uses async commit, CheckTxnStatus will reject rolling back the primary dagger. 241 // Then we need to check the secondary locks to determine the final status of the transaction. 242 if status.primaryLock != nil && status.primaryLock.UseAsyncCommit { 243 resolveData, err := lr.checkAllSecondaries(bo, l, &status) 244 if err != nil { 245 return false, err 246 } 247 txnInfos[l.TxnID] = resolveData.commitTs 248 continue 249 } 250 251 if status.ttl > 0 { 252 logutil.BgLogger().Error("BatchResolveLocks fail to clean locks, this result is not expected!") 253 return false, errors.New("MilevaDB ask EinsteinDB to rollback locks but it doesn't, the protocol maybe wrong") 254 } 255 256 txnInfos[l.TxnID] = status.commitTS 257 } 258 logutil.BgLogger().Info("BatchResolveLocks: lookup txn status", 259 zap.Duration("cost time", time.Since(startTime)), 260 zap.Int("num of txn", len(txnInfos))) 261 262 listTxnInfos := make([]*ekvrpcpb.TxnInfo, 0, len(txnInfos)) 263 for txnID, status := range txnInfos { 264 listTxnInfos = append(listTxnInfos, &ekvrpcpb.TxnInfo{ 265 Txn: txnID, 266 Status: status, 267 }) 268 } 269 270 req := einsteindbrpc.NewRequest(einsteindbrpc.CmdResolveLock, &ekvrpcpb.ResolveLockRequest{TxnInfos: listTxnInfos}) 271 startTime = time.Now() 272 resp, err := lr.causetstore.SendReq(bo, req, loc, readTimeoutShort) 273 if err != nil { 274 return false, errors.Trace(err) 275 } 276 277 regionErr, err := resp.GetRegionError() 278 if err != nil { 279 return false, errors.Trace(err) 280 } 281 282 if regionErr != nil { 283 err = bo.Backoff(BoRegionMiss, errors.New(regionErr.String())) 284 if err != nil { 285 return false, errors.Trace(err) 286 } 287 return false, nil 288 } 289 290 if resp.Resp == nil { 291 return false, errors.Trace(ErrBodyMissing) 292 } 293 cmdResp := resp.Resp.(*ekvrpcpb.ResolveLockResponse) 294 if keyErr := cmdResp.GetError(); keyErr != nil { 295 return false, errors.Errorf("unexpected resolve err: %s", keyErr) 296 } 297 298 logutil.BgLogger().Info("BatchResolveLocks: resolve locks in a batch", 299 zap.Duration("cost time", time.Since(startTime)), 300 zap.Int("num of locks", len(expiredLocks))) 301 return true, nil 302 } 303 304 // ResolveLocks tries to resolve Locks. The resolving process is in 3 steps: 305 // 1) Use the `lockTTL` to pick up all expired locks. Only locks that are too 306 // old are considered orphan locks and will be handled later. If all locks 307 // are expired then all locks will be resolved so the returned `ok` will be 308 // true, otherwise caller should sleep a while before retry. 309 // 2) For each dagger, query the primary key to get txn(which left the dagger)'s 310 // commit status. 311 // 3) Send `ResolveLock` cmd to the dagger's region to resolve all locks belong to 312 // the same transaction. 313 func (lr *LockResolver) ResolveLocks(bo *Backoffer, callerStartTS uint64, locks []*Lock) (int64, []uint64 /*pushed*/, error) { 314 return lr.resolveLocks(bo, callerStartTS, locks, false, false) 315 } 316 317 func (lr *LockResolver) resolveLocksLite(bo *Backoffer, callerStartTS uint64, locks []*Lock) (int64, []uint64 /*pushed*/, error) { 318 return lr.resolveLocks(bo, callerStartTS, locks, false, true) 319 } 320 321 func (lr *LockResolver) resolveLocks(bo *Backoffer, callerStartTS uint64, locks []*Lock, forWrite bool, lite bool) (int64, []uint64 /*pushed*/, error) { 322 if lr.testingKnobs.meetLock != nil { 323 lr.testingKnobs.meetLock(locks) 324 } 325 var msBeforeTxnExpired txnExpireTime 326 if len(locks) == 0 { 327 return msBeforeTxnExpired.value(), nil, nil 328 } 329 330 if forWrite { 331 einsteindbLockResolverCountWithResolveForWrite.Inc() 332 } else { 333 einsteindbLockResolverCountWithResolve.Inc() 334 } 335 336 var pushFail bool 337 // TxnID -> []Region, record resolved Regions. 338 // TODO: Maybe put it in LockResolver and share by all txns. 339 cleanTxns := make(map[uint64]map[RegionVerID]struct{}) 340 var pushed []uint64 341 // pushed is only used in the read operation. 342 if !forWrite { 343 pushed = make([]uint64, 0, len(locks)) 344 } 345 346 for _, l := range locks { 347 status, err := lr.getTxnStatusFromLock(bo, l, callerStartTS) 348 if err != nil { 349 msBeforeTxnExpired.uFIDelate(0) 350 err = errors.Trace(err) 351 return msBeforeTxnExpired.value(), nil, err 352 } 353 354 if status.ttl == 0 { 355 einsteindbLockResolverCountWithExpired.Inc() 356 // If the dagger is committed or rollbacked, resolve dagger. 357 cleanRegions, exists := cleanTxns[l.TxnID] 358 if !exists { 359 cleanRegions = make(map[RegionVerID]struct{}) 360 cleanTxns[l.TxnID] = cleanRegions 361 } 362 363 if status.primaryLock != nil && status.primaryLock.UseAsyncCommit && !exists { 364 err = lr.resolveLockAsync(bo, l, status) 365 } else if l.LockType == ekvrpcpb.Op_PessimisticLock { 366 err = lr.resolvePessimisticLock(bo, l, cleanRegions) 367 } else { 368 err = lr.resolveLock(bo, l, status, lite, cleanRegions) 369 } 370 if err != nil { 371 msBeforeTxnExpired.uFIDelate(0) 372 err = errors.Trace(err) 373 return msBeforeTxnExpired.value(), nil, err 374 } 375 } else { 376 einsteindbLockResolverCountWithNotExpired.Inc() 377 // If the dagger is valid, the txn may be a pessimistic transaction. 378 // UFIDelate the txn expire time. 379 msBeforeLockExpired := lr.causetstore.GetOracle().UntilExpired(l.TxnID, status.ttl) 380 msBeforeTxnExpired.uFIDelate(msBeforeLockExpired) 381 if forWrite { 382 // Write conflict detected! 383 // If it's a optimistic conflict and current txn is earlier than the dagger tenant, 384 // abort current transaction. 385 // This could avoids the deadlock scene of two large transaction. 386 if l.LockType != ekvrpcpb.Op_PessimisticLock && l.TxnID > callerStartTS { 387 einsteindbLockResolverCountWithWriteConflict.Inc() 388 return msBeforeTxnExpired.value(), nil, ekv.ErrWriteConflict.GenWithStackByArgs(callerStartTS, l.TxnID, status.commitTS, l.Key) 389 } 390 } else { 391 if status.action != ekvrpcpb.CausetAction_MinCommitTSPushed { 392 pushFail = true 393 continue 394 } 395 pushed = append(pushed, l.TxnID) 396 } 397 } 398 } 399 if pushFail { 400 // If any of the dagger fails to push minCommitTS, don't return the pushed array. 401 pushed = nil 402 } 403 404 if msBeforeTxnExpired.value() > 0 && len(pushed) == 0 { 405 // If len(pushed) > 0, the caller will not causet on the locks, it push the minCommitTS instead. 406 einsteindbLockResolverCountWithWaitExpired.Inc() 407 } 408 return msBeforeTxnExpired.value(), pushed, nil 409 } 410 411 func (lr *LockResolver) resolveLocksForWrite(bo *Backoffer, callerStartTS uint64, locks []*Lock) (int64, error) { 412 msBeforeTxnExpired, _, err := lr.resolveLocks(bo, callerStartTS, locks, true, false) 413 return msBeforeTxnExpired, err 414 } 415 416 type txnExpireTime struct { 417 initialized bool 418 txnExpire int64 419 } 420 421 func (t *txnExpireTime) uFIDelate(lockExpire int64) { 422 if lockExpire <= 0 { 423 lockExpire = 0 424 } 425 if !t.initialized { 426 t.txnExpire = lockExpire 427 t.initialized = true 428 return 429 } 430 if lockExpire < t.txnExpire { 431 t.txnExpire = lockExpire 432 } 433 } 434 435 func (t *txnExpireTime) value() int64 { 436 if !t.initialized { 437 return 0 438 } 439 return t.txnExpire 440 } 441 442 // GetTxnStatus queries einsteindb-server for a txn's status (commit/rollback). 443 // If the primary key is still locked, it will launch a Rollback to abort it. 444 // To avoid unnecessarily aborting too many txns, it is wiser to wait a few 445 // seconds before calling it after Prewrite. 446 func (lr *LockResolver) GetTxnStatus(txnID uint64, callerStartTS uint64, primary []byte) (TxnStatus, error) { 447 var status TxnStatus 448 bo := NewBackoffer(context.Background(), cleanupMaxBackoff) 449 currentTS, err := lr.causetstore.GetOracle().GetLowResolutionTimestamp(bo.ctx) 450 if err != nil { 451 return status, err 452 } 453 return lr.getTxnStatus(bo, txnID, primary, callerStartTS, currentTS, true) 454 } 455 456 func (lr *LockResolver) getTxnStatusFromLock(bo *Backoffer, l *Lock, callerStartTS uint64) (TxnStatus, error) { 457 var currentTS uint64 458 var err error 459 var status TxnStatus 460 if l.UseAsyncCommit { 461 // Async commit doesn't need the current ts since it uses the minCommitTS. 462 currentTS = 0 463 // Set to 0 so as not to push forward min commit ts. 464 callerStartTS = 0 465 } else if l.TTL == 0 { 466 // NOTE: l.TTL = 0 is a special protocol!!! 467 // When the pessimistic txn prewrite meets locks of a txn, it should resolve the dagger **unconditionally**. 468 // In this case, EinsteinDB use dagger TTL = 0 to notify MilevaDB, and MilevaDB should resolve the dagger! 469 // Set currentTS to max uint64 to make the dagger expired. 470 currentTS = math.MaxUint64 471 } else { 472 currentTS, err = lr.causetstore.GetOracle().GetLowResolutionTimestamp(bo.ctx) 473 if err != nil { 474 return TxnStatus{}, err 475 } 476 } 477 478 rollbackIfNotExist := false 479 failpoint.Inject("getTxnStatusDelay", func() { 480 time.Sleep(100 * time.Millisecond) 481 }) 482 for { 483 status, err = lr.getTxnStatus(bo, l.TxnID, l.Primary, callerStartTS, currentTS, rollbackIfNotExist) 484 if err == nil { 485 return status, nil 486 } 487 // If the error is something other than txnNotFoundErr, throw the error (network 488 // unavailable, einsteindb down, backoff timeout etc) to the caller. 489 if _, ok := errors.Cause(err).(txnNotFoundErr); !ok { 490 return TxnStatus{}, err 491 } 492 493 failpoint.Inject("txnNotFoundRetTTL", func() { 494 failpoint.Return(TxnStatus{ttl: l.TTL, action: ekvrpcpb.CausetAction_NoCausetAction}, nil) 495 }) 496 497 // Handle txnNotFound error. 498 // getTxnStatus() returns it when the secondary locks exist while the primary dagger doesn't. 499 // This is likely to happen in the concurrently prewrite when secondary regions 500 // success before the primary region. 501 if err := bo.Backoff(boTxnNotFound, err); err != nil { 502 logutil.Logger(bo.ctx).Warn("getTxnStatusFromLock backoff fail", zap.Error(err)) 503 } 504 505 if lr.causetstore.GetOracle().UntilExpired(l.TxnID, l.TTL) <= 0 { 506 logutil.Logger(bo.ctx).Warn("dagger txn not found, dagger has expired", 507 zap.Uint64("CallerStartTs", callerStartTS), 508 zap.Stringer("dagger str", l)) 509 if l.LockType == ekvrpcpb.Op_PessimisticLock { 510 failpoint.Inject("txnExpireRetTTL", func() { 511 failpoint.Return(TxnStatus{ttl: l.TTL, action: ekvrpcpb.CausetAction_NoCausetAction}, 512 errors.New("error txn not found and dagger expired")) 513 }) 514 return TxnStatus{}, nil 515 } 516 rollbackIfNotExist = true 517 } else { 518 if l.LockType == ekvrpcpb.Op_PessimisticLock { 519 return TxnStatus{ttl: l.TTL}, nil 520 } 521 } 522 } 523 } 524 525 type txnNotFoundErr struct { 526 *ekvrpcpb.TxnNotFound 527 } 528 529 func (e txnNotFoundErr) Error() string { 530 return e.TxnNotFound.String() 531 } 532 533 // getTxnStatus sends the CheckTxnStatus request to the EinsteinDB server. 534 // When rollbackIfNotExist is false, the caller should be careful with the txnNotFoundErr error. 535 func (lr *LockResolver) getTxnStatus(bo *Backoffer, txnID uint64, primary []byte, callerStartTS, currentTS uint64, rollbackIfNotExist bool) (TxnStatus, error) { 536 if s, ok := lr.getResolved(txnID); ok { 537 return s, nil 538 } 539 540 einsteindbLockResolverCountWithQueryTxnStatus.Inc() 541 542 // CheckTxnStatus may meet the following cases: 543 // 1. LOCK 544 // 1.1 Lock expired -- orphan dagger, fail to uFIDelate TTL, crash recovery etc. 545 // 1.2 Lock TTL -- active transaction holding the dagger. 546 // 2. NO LOCK 547 // 2.1 Txn Committed 548 // 2.2 Txn Rollbacked -- rollback itself, rollback by others, GC tomb etc. 549 // 2.3 No dagger -- pessimistic dagger rollback, concurrence prewrite. 550 551 var status TxnStatus 552 req := einsteindbrpc.NewRequest(einsteindbrpc.CmdCheckTxnStatus, &ekvrpcpb.CheckTxnStatusRequest{ 553 PrimaryKey: primary, 554 LockTs: txnID, 555 CallerStartTs: callerStartTS, 556 CurrentTs: currentTS, 557 RollbackIfNotExist: rollbackIfNotExist, 558 }) 559 for { 560 loc, err := lr.causetstore.GetRegionCache().LocateKey(bo, primary) 561 if err != nil { 562 return status, errors.Trace(err) 563 } 564 resp, err := lr.causetstore.SendReq(bo, req, loc.Region, readTimeoutShort) 565 if err != nil { 566 return status, errors.Trace(err) 567 } 568 regionErr, err := resp.GetRegionError() 569 if err != nil { 570 return status, errors.Trace(err) 571 } 572 if regionErr != nil { 573 err = bo.Backoff(BoRegionMiss, errors.New(regionErr.String())) 574 if err != nil { 575 return status, errors.Trace(err) 576 } 577 continue 578 } 579 if resp.Resp == nil { 580 return status, errors.Trace(ErrBodyMissing) 581 } 582 cmdResp := resp.Resp.(*ekvrpcpb.CheckTxnStatusResponse) 583 if keyErr := cmdResp.GetError(); keyErr != nil { 584 txnNotFound := keyErr.GetTxnNotFound() 585 if txnNotFound != nil { 586 return status, txnNotFoundErr{txnNotFound} 587 } 588 589 err = errors.Errorf("unexpected err: %s, tid: %v", keyErr, txnID) 590 logutil.BgLogger().Error("getTxnStatus error", zap.Error(err)) 591 return status, err 592 } 593 status.action = cmdResp.CausetAction 594 status.primaryLock = cmdResp.LockInfo 595 596 if status.primaryLock != nil && status.primaryLock.UseAsyncCommit { 597 if !lr.causetstore.GetOracle().IsExpired(txnID, cmdResp.LockTtl) { 598 status.ttl = cmdResp.LockTtl 599 } 600 } else if cmdResp.LockTtl != 0 { 601 status.ttl = cmdResp.LockTtl 602 } else { 603 if cmdResp.CommitVersion == 0 { 604 einsteindbLockResolverCountWithQueryTxnStatusRolledBack.Inc() 605 } else { 606 einsteindbLockResolverCountWithQueryTxnStatusCommitted.Inc() 607 } 608 609 status.commitTS = cmdResp.CommitVersion 610 lr.saveResolved(txnID, status) 611 } 612 613 return status, nil 614 } 615 } 616 617 // asyncResolveData is data contributed by multiple goroutines when resolving locks using the async commit protocol. All 618 // data should be protected by the mutex field. 619 type asyncResolveData struct { 620 mutex sync.Mutex 621 // If any key has been committed (missingLock is true), then this is the commit ts. In that case, all locks should 622 // be committed with the same commit timestamp. If no locks have been committed (missingLock is false), then we will 623 // use max(all min commit ts) from all locks; i.e., it is the commit ts we should use. Note that a secondary dagger's 624 // commit ts may or may not be the same as the primary dagger's min commit ts. 625 commitTs uint64 626 keys [][]byte 627 missingLock bool 628 } 629 630 // addKeys adds the keys from locks to data, keeping other fields up to date. startTS and commitTS are for the 631 // transaction being resolved. 632 // 633 // In the async commit protocol when checking locks, we send a list of keys to check and get back a list of locks. There 634 // will be a dagger for every key which is locked. If there are fewer locks than keys, then a dagger is missing because it 635 // has been committed, rolled back, or was never locked. 636 // 637 // In this function, locks is the list of locks, and expected is the number of keys. asyncResolveData.missingLock will be 638 // set to true if the lengths don't match. If the lengths do match, then the locks are added to asyncResolveData.locks 639 // and will need to be resolved by the caller. 640 func (data *asyncResolveData) addKeys(locks []*ekvrpcpb.LockInfo, expected int, startTS uint64, commitTS uint64) error { 641 data.mutex.Lock() 642 defer data.mutex.Unlock() 643 644 // Check locks to see if any have been committed or rolled back. 645 if len(locks) < expected { 646 logutil.BgLogger().Debug("addKeys: dagger has been committed or rolled back", zap.Uint64("commit ts", commitTS), zap.Uint64("start ts", startTS)) 647 // A dagger is missing - the transaction must either have been rolled back or committed. 648 if !data.missingLock { 649 // commitTS == 0 => dagger has been rolled back. 650 if commitTS != 0 && commitTS < data.commitTs { 651 return errors.Errorf("commit TS must be greater or equal to min commit TS: commit ts: %v, min commit ts: %v", commitTS, data.commitTs) 652 } 653 data.commitTs = commitTS 654 } 655 data.missingLock = true 656 657 if data.commitTs != commitTS { 658 return errors.Errorf("commit TS mismatch in async commit recovery: %v and %v", data.commitTs, commitTS) 659 } 660 661 // We do not need to resolve the remaining locks because EinsteinDB will have resolved them as appropriate. 662 return nil 663 } 664 665 logutil.BgLogger().Debug("addKeys: all locks present", zap.Uint64("start ts", startTS)) 666 // Save all locks to be resolved. 667 for _, lockInfo := range locks { 668 if lockInfo.LockVersion != startTS { 669 err := errors.Errorf("unexpected timestamp, expected: %v, found: %v", startTS, lockInfo.LockVersion) 670 logutil.BgLogger().Error("addLocks error", zap.Error(err)) 671 return err 672 } 673 674 if !data.missingLock && lockInfo.MinCommitTs > data.commitTs { 675 data.commitTs = lockInfo.MinCommitTs 676 } 677 data.keys = append(data.keys, lockInfo.Key) 678 } 679 680 return nil 681 } 682 683 func (lr *LockResolver) checkSecondaries(bo *Backoffer, txnID uint64, curKeys [][]byte, curRegionID RegionVerID, shared *asyncResolveData) error { 684 checkReq := &ekvrpcpb.CheckSecondaryLocksRequest{ 685 Keys: curKeys, 686 StartVersion: txnID, 687 } 688 req := einsteindbrpc.NewRequest(einsteindbrpc.CmdCheckSecondaryLocks, checkReq) 689 einsteindbLockResolverCountWithQueryCheckSecondaryLocks.Inc() 690 resp, err := lr.causetstore.SendReq(bo, req, curRegionID, readTimeoutShort) 691 if err != nil { 692 return errors.Trace(err) 693 } 694 regionErr, err := resp.GetRegionError() 695 if err != nil { 696 return errors.Trace(err) 697 } 698 if regionErr != nil { 699 err = bo.Backoff(BoRegionMiss, errors.New(regionErr.String())) 700 if err != nil { 701 return errors.Trace(err) 702 } 703 704 logutil.BgLogger().Debug("checkSecondaries: region error, regrouping", zap.Uint64("txn id", txnID), zap.Uint64("region", curRegionID.GetID())) 705 706 // If regions have changed, then we might need to regroup the keys. Since this should be rare and for the sake 707 // of simplicity, we will resolve regions sequentially. 708 regions, _, err := lr.causetstore.GetRegionCache().GroupKeysByRegion(bo, curKeys, nil) 709 if err != nil { 710 return errors.Trace(err) 711 } 712 for regionID, keys := range regions { 713 // Recursion will terminate because the resolve request succeeds or the Backoffer reaches its limit. 714 if err = lr.checkSecondaries(bo, txnID, keys, regionID, shared); err != nil { 715 return err 716 } 717 } 718 return nil 719 } 720 if resp.Resp == nil { 721 return errors.Trace(ErrBodyMissing) 722 } 723 724 checkResp := resp.Resp.(*ekvrpcpb.CheckSecondaryLocksResponse) 725 return shared.addKeys(checkResp.Locks, len(curKeys), txnID, checkResp.CommitTs) 726 } 727 728 // resolveLockAsync resolves l assuming it was locked using the async commit protocol. 729 func (lr *LockResolver) resolveLockAsync(bo *Backoffer, l *Lock, status TxnStatus) error { 730 einsteindbLockResolverCountWithResolveAsync.Inc() 731 732 resolveData, err := lr.checkAllSecondaries(bo, l, &status) 733 if err != nil { 734 return err 735 } 736 737 status.commitTS = resolveData.commitTs 738 739 resolveData.keys = append(resolveData.keys, l.Primary) 740 keysByRegion, _, err := lr.causetstore.GetRegionCache().GroupKeysByRegion(bo, resolveData.keys, nil) 741 if err != nil { 742 return errors.Trace(err) 743 } 744 745 logutil.BgLogger().Info("resolve async commit", zap.Uint64("startTS", l.TxnID), zap.Uint64("commitTS", status.commitTS)) 746 747 errChan := make(chan error, len(keysByRegion)) 748 // Resolve every dagger in the transaction. 749 for region, locks := range keysByRegion { 750 curLocks := locks 751 curRegion := region 752 go func() { 753 errChan <- lr.resolveRegionLocks(bo, l, curRegion, curLocks, status) 754 }() 755 } 756 757 var errs []string 758 for range keysByRegion { 759 err1 := <-errChan 760 if err1 != nil { 761 errs = append(errs, err1.Error()) 762 } 763 } 764 765 if len(errs) > 0 { 766 return errors.Errorf("async commit recovery (sending ResolveLock) finished with errors: %v", errs) 767 } 768 769 return nil 770 } 771 772 // checkAllSecondaries checks the secondary locks of an async commit transaction to find out the final 773 // status of the transaction 774 func (lr *LockResolver) checkAllSecondaries(bo *Backoffer, l *Lock, status *TxnStatus) (*asyncResolveData, error) { 775 regions, _, err := lr.causetstore.GetRegionCache().GroupKeysByRegion(bo, status.primaryLock.Secondaries, nil) 776 if err != nil { 777 return nil, errors.Trace(err) 778 } 779 780 shared := asyncResolveData{ 781 mutex: sync.Mutex{}, 782 commitTs: status.primaryLock.MinCommitTs, 783 keys: [][]byte{}, 784 missingLock: false, 785 } 786 787 errChan := make(chan error, len(regions)) 788 789 for regionID, keys := range regions { 790 curRegionID := regionID 791 curKeys := keys 792 793 go func() { 794 errChan <- lr.checkSecondaries(bo, l.TxnID, curKeys, curRegionID, &shared) 795 }() 796 } 797 798 var errs []string 799 for range regions { 800 err1 := <-errChan 801 if err1 != nil { 802 errs = append(errs, err1.Error()) 803 } 804 } 805 806 if len(errs) > 0 { 807 return nil, errors.Errorf("async commit recovery (sending CheckSecondaryLocks) finished with errors: %v", errs) 808 } 809 810 // TODO(nrc, cfzjywxk) schemaReplicant lease check 811 812 return &shared, nil 813 } 814 815 // resolveRegionLocks is essentially the same as resolveLock, but we resolve all keys in the same region at the same time. 816 func (lr *LockResolver) resolveRegionLocks(bo *Backoffer, l *Lock, region RegionVerID, keys [][]byte, status TxnStatus) error { 817 lreq := &ekvrpcpb.ResolveLockRequest{ 818 StartVersion: l.TxnID, 819 } 820 if status.IsCommitted() { 821 lreq.CommitVersion = status.CommitTS() 822 } 823 lreq.Keys = keys 824 req := einsteindbrpc.NewRequest(einsteindbrpc.CmdResolveLock, lreq) 825 826 resp, err := lr.causetstore.SendReq(bo, req, region, readTimeoutShort) 827 if err != nil { 828 return errors.Trace(err) 829 } 830 831 regionErr, err := resp.GetRegionError() 832 if err != nil { 833 return errors.Trace(err) 834 } 835 if regionErr != nil { 836 err := bo.Backoff(BoRegionMiss, errors.New(regionErr.String())) 837 if err != nil { 838 return errors.Trace(err) 839 } 840 841 logutil.BgLogger().Info("resolveRegionLocks region error, regrouping", zap.String("dagger", l.String()), zap.Uint64("region", region.GetID())) 842 843 // Regroup locks. 844 regions, _, err := lr.causetstore.GetRegionCache().GroupKeysByRegion(bo, keys, nil) 845 if err != nil { 846 return errors.Trace(err) 847 } 848 for regionID, keys := range regions { 849 // Recursion will terminate because the resolve request succeeds or the Backoffer reaches its limit. 850 if err = lr.resolveRegionLocks(bo, l, regionID, keys, status); err != nil { 851 return err 852 } 853 } 854 return nil 855 } 856 if resp.Resp == nil { 857 return errors.Trace(ErrBodyMissing) 858 } 859 cmdResp := resp.Resp.(*ekvrpcpb.ResolveLockResponse) 860 if keyErr := cmdResp.GetError(); keyErr != nil { 861 err = errors.Errorf("unexpected resolve err: %s, dagger: %v", keyErr, l) 862 logutil.BgLogger().Error("resolveLock error", zap.Error(err)) 863 } 864 865 return nil 866 } 867 868 func (lr *LockResolver) resolveLock(bo *Backoffer, l *Lock, status TxnStatus, lite bool, cleanRegions map[RegionVerID]struct{}) error { 869 einsteindbLockResolverCountWithResolveLocks.Inc() 870 resolveLite := lite || l.TxnSize < bigTxnThreshold 871 for { 872 loc, err := lr.causetstore.GetRegionCache().LocateKey(bo, l.Key) 873 if err != nil { 874 return errors.Trace(err) 875 } 876 if _, ok := cleanRegions[loc.Region]; ok { 877 return nil 878 } 879 lreq := &ekvrpcpb.ResolveLockRequest{ 880 StartVersion: l.TxnID, 881 } 882 if status.IsCommitted() { 883 lreq.CommitVersion = status.CommitTS() 884 } else { 885 logutil.BgLogger().Info("resolveLock rollback", zap.String("dagger", l.String())) 886 } 887 888 if resolveLite { 889 // Only resolve specified keys when it is a small transaction, 890 // prevent from scanning the whole region in this case. 891 einsteindbLockResolverCountWithResolveLockLite.Inc() 892 lreq.Keys = [][]byte{l.Key} 893 } 894 req := einsteindbrpc.NewRequest(einsteindbrpc.CmdResolveLock, lreq) 895 resp, err := lr.causetstore.SendReq(bo, req, loc.Region, readTimeoutShort) 896 if err != nil { 897 return errors.Trace(err) 898 } 899 regionErr, err := resp.GetRegionError() 900 if err != nil { 901 return errors.Trace(err) 902 } 903 if regionErr != nil { 904 err = bo.Backoff(BoRegionMiss, errors.New(regionErr.String())) 905 if err != nil { 906 return errors.Trace(err) 907 } 908 continue 909 } 910 if resp.Resp == nil { 911 return errors.Trace(ErrBodyMissing) 912 } 913 cmdResp := resp.Resp.(*ekvrpcpb.ResolveLockResponse) 914 if keyErr := cmdResp.GetError(); keyErr != nil { 915 err = errors.Errorf("unexpected resolve err: %s, dagger: %v", keyErr, l) 916 logutil.BgLogger().Error("resolveLock error", zap.Error(err)) 917 return err 918 } 919 if !resolveLite { 920 cleanRegions[loc.Region] = struct{}{} 921 } 922 return nil 923 } 924 } 925 926 func (lr *LockResolver) resolvePessimisticLock(bo *Backoffer, l *Lock, cleanRegions map[RegionVerID]struct{}) error { 927 einsteindbLockResolverCountWithResolveLocks.Inc() 928 for { 929 loc, err := lr.causetstore.GetRegionCache().LocateKey(bo, l.Key) 930 if err != nil { 931 return errors.Trace(err) 932 } 933 if _, ok := cleanRegions[loc.Region]; ok { 934 return nil 935 } 936 forUFIDelateTS := l.LockForUFIDelateTS 937 if forUFIDelateTS == 0 { 938 forUFIDelateTS = math.MaxUint64 939 } 940 pessimisticRollbackReq := &ekvrpcpb.PessimisticRollbackRequest{ 941 StartVersion: l.TxnID, 942 ForUFIDelateTs: forUFIDelateTS, 943 Keys: [][]byte{l.Key}, 944 } 945 req := einsteindbrpc.NewRequest(einsteindbrpc.CmdPessimisticRollback, pessimisticRollbackReq) 946 resp, err := lr.causetstore.SendReq(bo, req, loc.Region, readTimeoutShort) 947 if err != nil { 948 return errors.Trace(err) 949 } 950 regionErr, err := resp.GetRegionError() 951 if err != nil { 952 return errors.Trace(err) 953 } 954 if regionErr != nil { 955 err = bo.Backoff(BoRegionMiss, errors.New(regionErr.String())) 956 if err != nil { 957 return errors.Trace(err) 958 } 959 continue 960 } 961 if resp.Resp == nil { 962 return errors.Trace(ErrBodyMissing) 963 } 964 cmdResp := resp.Resp.(*ekvrpcpb.PessimisticRollbackResponse) 965 if keyErr := cmdResp.GetErrors(); len(keyErr) > 0 { 966 err = errors.Errorf("unexpected resolve pessimistic dagger err: %s, dagger: %v", keyErr[0], l) 967 logutil.Logger(bo.ctx).Error("resolveLock error", zap.Error(err)) 968 return err 969 } 970 return nil 971 } 972 }