github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/kv/kvserver/intentresolver/intent_resolver.go (about) 1 // Copyright 2016 The Cockroach Authors. 2 // 3 // Use of this software is governed by the Business Source License 4 // included in the file licenses/BSL.txt. 5 // 6 // As of the Change Date specified in that file, in accordance with 7 // the Business Source License, use of this software will be governed 8 // by the Apache License, Version 2.0, included in the file 9 // licenses/APL.txt. 10 11 package intentresolver 12 13 import ( 14 "bytes" 15 "context" 16 "sort" 17 "time" 18 19 "github.com/cockroachdb/cockroach/pkg/internal/client/requestbatcher" 20 "github.com/cockroachdb/cockroach/pkg/keys" 21 "github.com/cockroachdb/cockroach/pkg/kv" 22 "github.com/cockroachdb/cockroach/pkg/kv/kvbase" 23 "github.com/cockroachdb/cockroach/pkg/kv/kvserver/batcheval/result" 24 "github.com/cockroachdb/cockroach/pkg/kv/kvserver/kvserverbase" 25 "github.com/cockroachdb/cockroach/pkg/kv/kvserver/txnwait" 26 "github.com/cockroachdb/cockroach/pkg/roachpb" 27 "github.com/cockroachdb/cockroach/pkg/storage/enginepb" 28 "github.com/cockroachdb/cockroach/pkg/util/contextutil" 29 "github.com/cockroachdb/cockroach/pkg/util/hlc" 30 "github.com/cockroachdb/cockroach/pkg/util/log" 31 "github.com/cockroachdb/cockroach/pkg/util/quotapool" 32 "github.com/cockroachdb/cockroach/pkg/util/stop" 33 "github.com/cockroachdb/cockroach/pkg/util/syncutil" 34 "github.com/cockroachdb/cockroach/pkg/util/uuid" 35 "github.com/cockroachdb/errors" 36 ) 37 38 const ( 39 // defaultTaskLimit is the maximum number of asynchronous tasks 40 // that may be started by intentResolver. When this limit is reached 41 // asynchronous tasks will start to block to apply backpressure. This is a 42 // last line of defense against issues like #4925. 43 // TODO(bdarnell): how to determine best value? 44 defaultTaskLimit = 1000 45 46 // asyncIntentResolutionTimeout is the timeout when processing a group of 47 // intents asynchronously. The timeout prevents async intent resolution from 48 // getting stuck. Since processing intents is best effort, we'd rather give 49 // up than wait too long (this helps avoid deadlocks during test shutdown). 50 asyncIntentResolutionTimeout = 30 * time.Second 51 52 // gcBatchSize is the maximum number of transaction records that will be 53 // GCed in a single batch. Batches that span many ranges (which is possible 54 // for the transaction records that spans many ranges) will be split into 55 // many batches by the DistSender. 56 gcBatchSize = 1024 57 58 // intentResolverBatchSize is the maximum number of intents that will be 59 // resolved in a single batch. Batches that span many ranges (which is 60 // possible for the commit of a transaction that spans many ranges) will be 61 // split into many batches by the DistSender. 62 // TODO(ajwerner): justify this value 63 intentResolverBatchSize = 100 64 65 // cleanupIntentsTxnsPerBatch is the number of transactions whose 66 // corresponding intents will be resolved at a time. Intents are batched 67 // by transaction to avoid timeouts while resolving intents and ensure that 68 // progress is made. 69 cleanupIntentsTxnsPerBatch = 100 70 71 // defaultGCBatchIdle is the default duration which the gc request batcher 72 // will wait between requests for a range before sending it. 73 defaultGCBatchIdle = -1 // disabled 74 75 // defaultGCBatchWait is the default duration which the gc request batcher 76 // will wait between requests for a range before sending it. 77 defaultGCBatchWait = time.Second 78 79 // intentResolutionBatchWait is used to configure the RequestBatcher which 80 // batches intent resolution requests across transactions. Intent resolution 81 // needs to occur in a relatively short period of time after the completion 82 // of a transaction in order to minimize the contention footprint of the write 83 // for other contending reads or writes. The chosen value was selected based 84 // on some light experimentation to ensure that performance does not degrade 85 // in the face of highly contended workloads. 86 defaultIntentResolutionBatchWait = 10 * time.Millisecond 87 88 // intentResolutionBatchIdle is similar to the above setting but is used when 89 // when no additional traffic hits the batch. 90 defaultIntentResolutionBatchIdle = 5 * time.Millisecond 91 ) 92 93 // Config contains the dependencies to construct an IntentResolver. 94 type Config struct { 95 Clock *hlc.Clock 96 DB *kv.DB 97 Stopper *stop.Stopper 98 AmbientCtx log.AmbientContext 99 TestingKnobs kvserverbase.IntentResolverTestingKnobs 100 RangeDescriptorCache kvbase.RangeDescriptorCache 101 102 TaskLimit int 103 MaxGCBatchWait time.Duration 104 MaxGCBatchIdle time.Duration 105 MaxIntentResolutionBatchWait time.Duration 106 MaxIntentResolutionBatchIdle time.Duration 107 } 108 109 // IntentResolver manages the process of pushing transactions and 110 // resolving intents. 111 type IntentResolver struct { 112 Metrics Metrics 113 114 clock *hlc.Clock 115 db *kv.DB 116 stopper *stop.Stopper 117 testingKnobs kvserverbase.IntentResolverTestingKnobs 118 ambientCtx log.AmbientContext 119 sem *quotapool.IntPool // semaphore to limit async goroutines 120 121 rdc kvbase.RangeDescriptorCache 122 123 gcBatcher *requestbatcher.RequestBatcher 124 irBatcher *requestbatcher.RequestBatcher 125 irRangeBatcher *requestbatcher.RequestBatcher 126 127 mu struct { 128 syncutil.Mutex 129 // Map from txn ID being pushed to a refcount of requests waiting on the 130 // push. 131 inFlightPushes map[uuid.UUID]int 132 // Set of txn IDs whose list of lock spans are being resolved. Note 133 // that this pertains only to EndTxn-style lock cleanups, whether 134 // called directly after EndTxn evaluation or during GC of txn spans. 135 inFlightTxnCleanups map[uuid.UUID]struct{} 136 } 137 every log.EveryN 138 } 139 140 func setConfigDefaults(c *Config) { 141 if c.TaskLimit == 0 { 142 c.TaskLimit = defaultTaskLimit 143 } 144 if c.TaskLimit == -1 || c.TestingKnobs.ForceSyncIntentResolution { 145 c.TaskLimit = 0 146 } 147 if c.MaxGCBatchIdle == 0 { 148 c.MaxGCBatchIdle = defaultGCBatchIdle 149 } 150 if c.MaxGCBatchWait == 0 { 151 c.MaxGCBatchWait = defaultGCBatchWait 152 } 153 if c.MaxIntentResolutionBatchIdle == 0 { 154 c.MaxIntentResolutionBatchIdle = defaultIntentResolutionBatchIdle 155 } 156 if c.MaxIntentResolutionBatchWait == 0 { 157 c.MaxIntentResolutionBatchWait = defaultIntentResolutionBatchWait 158 } 159 if c.RangeDescriptorCache == nil { 160 c.RangeDescriptorCache = nopRangeDescriptorCache{} 161 } 162 } 163 164 type nopRangeDescriptorCache struct{} 165 166 var zeroRangeDescriptor = &roachpb.RangeDescriptor{} 167 168 func (nrdc nopRangeDescriptorCache) LookupRangeDescriptor( 169 ctx context.Context, key roachpb.RKey, 170 ) (*roachpb.RangeDescriptor, error) { 171 return zeroRangeDescriptor, nil 172 } 173 174 // New creates an new IntentResolver. 175 func New(c Config) *IntentResolver { 176 setConfigDefaults(&c) 177 ir := &IntentResolver{ 178 clock: c.Clock, 179 db: c.DB, 180 stopper: c.Stopper, 181 sem: quotapool.NewIntPool("intent resolver", uint64(c.TaskLimit)), 182 every: log.Every(time.Minute), 183 Metrics: makeMetrics(), 184 rdc: c.RangeDescriptorCache, 185 testingKnobs: c.TestingKnobs, 186 } 187 c.Stopper.AddCloser(ir.sem.Closer("stopper")) 188 ir.mu.inFlightPushes = map[uuid.UUID]int{} 189 ir.mu.inFlightTxnCleanups = map[uuid.UUID]struct{}{} 190 gcBatchSize := gcBatchSize 191 if c.TestingKnobs.MaxIntentResolutionBatchSize > 0 { 192 gcBatchSize = c.TestingKnobs.MaxGCBatchSize 193 } 194 ir.gcBatcher = requestbatcher.New(requestbatcher.Config{ 195 Name: "intent_resolver_gc_batcher", 196 MaxMsgsPerBatch: gcBatchSize, 197 MaxWait: c.MaxGCBatchWait, 198 MaxIdle: c.MaxGCBatchIdle, 199 Stopper: c.Stopper, 200 Sender: c.DB.NonTransactionalSender(), 201 }) 202 intentResolutionBatchSize := intentResolverBatchSize 203 if c.TestingKnobs.MaxIntentResolutionBatchSize > 0 { 204 intentResolutionBatchSize = c.TestingKnobs.MaxIntentResolutionBatchSize 205 } 206 ir.irBatcher = requestbatcher.New(requestbatcher.Config{ 207 Name: "intent_resolver_ir_batcher", 208 MaxMsgsPerBatch: intentResolutionBatchSize, 209 MaxWait: c.MaxIntentResolutionBatchWait, 210 MaxIdle: c.MaxIntentResolutionBatchIdle, 211 Stopper: c.Stopper, 212 Sender: c.DB.NonTransactionalSender(), 213 }) 214 ir.irRangeBatcher = requestbatcher.New(requestbatcher.Config{ 215 Name: "intent_resolver_ir_range_batcher", 216 MaxMsgsPerBatch: intentResolutionBatchSize, 217 // NOTE: Allow each request sent in a batch to touch up to twice as 218 // many keys as messages in the batch to avoid pagination if only a 219 // few ResolveIntentRange requests touch multiple intents. 220 MaxKeysPerBatchReq: 2 * intentResolverBatchSize, 221 MaxWait: c.MaxIntentResolutionBatchWait, 222 MaxIdle: c.MaxIntentResolutionBatchIdle, 223 Stopper: c.Stopper, 224 Sender: c.DB.NonTransactionalSender(), 225 }) 226 return ir 227 } 228 229 func getPusherTxn(h roachpb.Header) roachpb.Transaction { 230 // If the txn is nil, we communicate a priority by sending an empty 231 // txn with only the priority set. This is official usage of PushTxn. 232 txn := h.Txn 233 if txn == nil { 234 txn = &roachpb.Transaction{ 235 TxnMeta: enginepb.TxnMeta{ 236 Priority: roachpb.MakePriority(h.UserPriority), 237 }, 238 } 239 } 240 return *txn 241 } 242 243 // updateIntentTxnStatus takes a slice of intents and a set of pushed 244 // transactions (like returned from MaybePushTransactions) and updates 245 // each intent with its corresponding TxnMeta and Status. 246 // resultSlice is an optional value to allow the caller to preallocate 247 // the returned intent slice. 248 func updateIntentTxnStatus( 249 ctx context.Context, 250 pushedTxns map[uuid.UUID]*roachpb.Transaction, 251 intents []roachpb.Intent, 252 skipIfInFlight bool, 253 results []roachpb.LockUpdate, 254 ) []roachpb.LockUpdate { 255 for _, intent := range intents { 256 pushee, ok := pushedTxns[intent.Txn.ID] 257 if !ok { 258 // The intent was not pushed. 259 if !skipIfInFlight { 260 log.Fatalf(ctx, "no PushTxn response for intent %+v", intent) 261 } 262 // It must have been skipped. 263 continue 264 } 265 up := roachpb.MakeLockUpdate(pushee, roachpb.Span{Key: intent.Key}) 266 results = append(results, up) 267 } 268 return results 269 } 270 271 // PushTransaction takes a transaction and pushes its record using the specified 272 // push type and request header. It returns the transaction proto corresponding 273 // to the pushed transaction. 274 func (ir *IntentResolver) PushTransaction( 275 ctx context.Context, pushTxn *enginepb.TxnMeta, h roachpb.Header, pushType roachpb.PushTxnType, 276 ) (*roachpb.Transaction, *roachpb.Error) { 277 pushTxns := make(map[uuid.UUID]*enginepb.TxnMeta, 1) 278 pushTxns[pushTxn.ID] = pushTxn 279 pushedTxns, pErr := ir.MaybePushTransactions(ctx, pushTxns, h, pushType, false /* skipIfInFlight */) 280 if pErr != nil { 281 return nil, pErr 282 } 283 pushedTxn, ok := pushedTxns[pushTxn.ID] 284 if !ok { 285 log.Fatalf(ctx, "missing PushTxn responses for %s", pushTxn) 286 } 287 return pushedTxn, nil 288 } 289 290 // MaybePushTransactions tries to push the conflicting transaction(s): 291 // either moving their timestamp forward on a read/write conflict, aborting 292 // it on a write/write conflict, or doing nothing if the transaction is no 293 // longer pending. 294 // 295 // Returns a set of transaction protos who correspond to the pushed 296 // transactions and whose intents can now be resolved, and an error. 297 // 298 // If skipIfInFlight is true, then no PushTxns will be sent and no intents 299 // will be returned for any transaction for which there is another push in 300 // progress. This should only be used by callers who are not relying on the 301 // side effect of a push (i.e. only pushType==PUSH_TOUCH), and who also 302 // don't need to synchronize with the resolution of those intents (e.g. 303 // asynchronous resolutions of intents skipped on inconsistent reads). 304 // 305 // Callers are involved with 306 // a) conflict resolution for commands being executed at the Store with the 307 // client waiting, 308 // b) resolving intents encountered during inconsistent operations, and 309 // c) resolving intents upon EndTxn which are not local to the given range. 310 // This is the only path in which the transaction is going to be in 311 // non-pending state and doesn't require a push. 312 func (ir *IntentResolver) MaybePushTransactions( 313 ctx context.Context, 314 pushTxns map[uuid.UUID]*enginepb.TxnMeta, 315 h roachpb.Header, 316 pushType roachpb.PushTxnType, 317 skipIfInFlight bool, 318 ) (map[uuid.UUID]*roachpb.Transaction, *roachpb.Error) { 319 // Decide which transactions to push and which to ignore because 320 // of other in-flight requests. For those transactions that we 321 // will be pushing, increment their ref count in the in-flight 322 // pushes map. 323 ir.mu.Lock() 324 for txnID := range pushTxns { 325 _, pushTxnInFlight := ir.mu.inFlightPushes[txnID] 326 if pushTxnInFlight && skipIfInFlight { 327 // Another goroutine is working on this transaction so we can 328 // skip it. 329 if log.V(1) { 330 log.Infof(ctx, "skipping PushTxn for %s; attempt already in flight", txnID) 331 } 332 delete(pushTxns, txnID) 333 } else { 334 ir.mu.inFlightPushes[txnID]++ 335 } 336 } 337 cleanupInFlightPushes := func() { 338 ir.mu.Lock() 339 for txnID := range pushTxns { 340 ir.mu.inFlightPushes[txnID]-- 341 if ir.mu.inFlightPushes[txnID] == 0 { 342 delete(ir.mu.inFlightPushes, txnID) 343 } 344 } 345 ir.mu.Unlock() 346 } 347 ir.mu.Unlock() 348 if len(pushTxns) == 0 { 349 return nil, nil 350 } 351 352 pusherTxn := getPusherTxn(h) 353 log.Eventf(ctx, "pushing %d transaction(s)", len(pushTxns)) 354 355 // Attempt to push the transaction(s). 356 b := &kv.Batch{} 357 b.Header.Timestamp = ir.clock.Now() 358 for _, pushTxn := range pushTxns { 359 b.AddRawRequest(&roachpb.PushTxnRequest{ 360 RequestHeader: roachpb.RequestHeader{ 361 Key: pushTxn.Key, 362 }, 363 PusherTxn: pusherTxn, 364 PusheeTxn: *pushTxn, 365 PushTo: h.Timestamp.Next(), 366 PushType: pushType, 367 }) 368 } 369 err := ir.db.Run(ctx, b) 370 cleanupInFlightPushes() 371 if err != nil { 372 return nil, b.MustPErr() 373 } 374 375 br := b.RawResponse() 376 pushedTxns := make(map[uuid.UUID]*roachpb.Transaction, len(br.Responses)) 377 for _, resp := range br.Responses { 378 txn := &resp.GetInner().(*roachpb.PushTxnResponse).PusheeTxn 379 if _, ok := pushedTxns[txn.ID]; ok { 380 log.Fatalf(ctx, "have two PushTxn responses for %s", txn.ID) 381 } 382 pushedTxns[txn.ID] = txn 383 log.Eventf(ctx, "%s is now %s", txn.ID, txn.Status) 384 } 385 return pushedTxns, nil 386 } 387 388 // runAsyncTask semi-synchronously runs a generic task function. If 389 // there is spare capacity in the limited async task semaphore, it's 390 // run asynchronously; otherwise, it's run synchronously if 391 // allowSyncProcessing is true; if false, an error is returned. 392 func (ir *IntentResolver) runAsyncTask( 393 ctx context.Context, allowSyncProcessing bool, taskFn func(context.Context), 394 ) error { 395 if ir.testingKnobs.DisableAsyncIntentResolution { 396 return errors.New("intents not processed as async resolution is disabled") 397 } 398 err := ir.stopper.RunLimitedAsyncTask( 399 // If we've successfully launched a background task, dissociate 400 // this work from our caller's context and timeout. 401 ir.ambientCtx.AnnotateCtx(context.Background()), 402 "storage.IntentResolver: processing intents", 403 ir.sem, 404 false, /* wait */ 405 taskFn, 406 ) 407 if err != nil { 408 if errors.Is(err, stop.ErrThrottled) { 409 ir.Metrics.IntentResolverAsyncThrottled.Inc(1) 410 if allowSyncProcessing { 411 // A limited task was not available. Rather than waiting for 412 // one, we reuse the current goroutine. 413 taskFn(ctx) 414 return nil 415 } 416 } 417 return errors.Wrapf(err, "during async intent resolution") 418 } 419 return nil 420 } 421 422 // CleanupIntentsAsync asynchronously processes intents which were 423 // encountered during another command but did not interfere with the 424 // execution of that command. This occurs during inconsistent 425 // reads. 426 func (ir *IntentResolver) CleanupIntentsAsync( 427 ctx context.Context, intents []roachpb.Intent, allowSyncProcessing bool, 428 ) error { 429 if len(intents) == 0 { 430 return nil 431 } 432 now := ir.clock.Now() 433 return ir.runAsyncTask(ctx, allowSyncProcessing, func(ctx context.Context) { 434 err := contextutil.RunWithTimeout(ctx, "async intent resolution", 435 asyncIntentResolutionTimeout, func(ctx context.Context) error { 436 _, err := ir.CleanupIntents(ctx, intents, now, roachpb.PUSH_TOUCH) 437 return err 438 }) 439 if err != nil && ir.every.ShouldLog() { 440 log.Warningf(ctx, "%v", err) 441 } 442 }) 443 } 444 445 // CleanupIntents processes a collection of intents by pushing each 446 // implicated transaction using the specified pushType. Intents 447 // belonging to non-pending transactions after the push are resolved. 448 // On success, returns the number of resolved intents. On error, a 449 // subset of the intents may have been resolved, but zero will be 450 // returned. 451 func (ir *IntentResolver) CleanupIntents( 452 ctx context.Context, intents []roachpb.Intent, now hlc.Timestamp, pushType roachpb.PushTxnType, 453 ) (int, error) { 454 h := roachpb.Header{Timestamp: now} 455 456 // All transactions in MaybePushTransactions will be sent in a single batch. 457 // In order to ensure that progress is made, we want to ensure that this 458 // batch does not become too big as to time out due to a deadline set above 459 // this call. If the attempt to push intents times out before any intents 460 // have been resolved, no progress is made. Since batches are atomic, a 461 // batch that times out has no effect. Hence, we chunk the work to ensure 462 // progress even when a timeout is eventually hit. 463 sort.Sort(intentsByTxn(intents)) 464 resolved := 0 465 const skipIfInFlight = true 466 pushTxns := make(map[uuid.UUID]*enginepb.TxnMeta) 467 var resolveIntents []roachpb.LockUpdate 468 for unpushed := intents; len(unpushed) > 0; { 469 for k := range pushTxns { // clear the pushTxns map 470 delete(pushTxns, k) 471 } 472 var prevTxnID uuid.UUID 473 var i int 474 for i = 0; i < len(unpushed); i++ { 475 if curTxn := &unpushed[i].Txn; curTxn.ID != prevTxnID { 476 if len(pushTxns) == cleanupIntentsTxnsPerBatch { 477 break 478 } 479 prevTxnID = curTxn.ID 480 pushTxns[curTxn.ID] = curTxn 481 } 482 } 483 484 pushedTxns, pErr := ir.MaybePushTransactions(ctx, pushTxns, h, pushType, skipIfInFlight) 485 if pErr != nil { 486 return 0, errors.Wrapf(pErr.GoError(), "failed to push during intent resolution") 487 } 488 resolveIntents = updateIntentTxnStatus(ctx, pushedTxns, unpushed[:i], 489 skipIfInFlight, resolveIntents[:0]) 490 // resolveIntents with poison=true because we're resolving 491 // intents outside of the context of an EndTxn. 492 // 493 // Naively, it doesn't seem like we need to poison the abort 494 // cache since we're pushing with PUSH_TOUCH - meaning that 495 // the primary way our Push leads to aborting intents is that 496 // of the transaction having timed out (and thus presumably no 497 // client being around any more, though at the time of writing 498 // we don't guarantee that). But there are other paths in which 499 // the Push comes back successful while the coordinating client 500 // may still be active. Examples of this are when: 501 // 502 // - the transaction was aborted by someone else, but the 503 // coordinating client may still be running. 504 // - the transaction entry wasn't written yet, which at the 505 // time of writing has our push abort it, leading to the 506 // same situation as above. 507 // 508 // Thus, we must poison. 509 opts := ResolveOptions{Poison: true} 510 if pErr := ir.ResolveIntents(ctx, resolveIntents, opts); pErr != nil { 511 return 0, errors.Wrapf(pErr.GoError(), "failed to resolve intents") 512 } 513 resolved += len(resolveIntents) 514 unpushed = unpushed[i:] 515 } 516 return resolved, nil 517 } 518 519 // CleanupTxnIntentsAsync asynchronously cleans up intents owned by 520 // a transaction on completion. 521 func (ir *IntentResolver) CleanupTxnIntentsAsync( 522 ctx context.Context, 523 rangeID roachpb.RangeID, 524 endTxns []result.EndTxnIntents, 525 allowSyncProcessing bool, 526 ) error { 527 now := ir.clock.Now() 528 for i := range endTxns { 529 et := &endTxns[i] // copy for goroutine 530 if err := ir.runAsyncTask(ctx, allowSyncProcessing, func(ctx context.Context) { 531 locked, release := ir.lockInFlightTxnCleanup(ctx, et.Txn.ID) 532 if !locked { 533 return 534 } 535 defer release() 536 intents := roachpb.AsLockUpdates(et.Txn, et.Txn.LockSpans) 537 if err := ir.cleanupFinishedTxnIntents(ctx, rangeID, et.Txn, intents, now, et.Poison, nil); err != nil { 538 if ir.every.ShouldLog() { 539 log.Warningf(ctx, "failed to cleanup transaction intents: %v", err) 540 } 541 } 542 }); err != nil { 543 return err 544 } 545 } 546 return nil 547 } 548 549 // lockInFlightTxnCleanup ensures that only a single attempt is being made 550 // to cleanup the intents belonging to the specified transaction. Returns 551 // whether this attempt to lock succeeded and if so, a function to release 552 // the lock, to be invoked subsequently by the caller. 553 func (ir *IntentResolver) lockInFlightTxnCleanup( 554 ctx context.Context, txnID uuid.UUID, 555 ) (locked bool, release func()) { 556 ir.mu.Lock() 557 defer ir.mu.Unlock() 558 _, inFlight := ir.mu.inFlightTxnCleanups[txnID] 559 if inFlight { 560 log.Eventf(ctx, "skipping txn resolved; already in flight") 561 return false, nil 562 } 563 ir.mu.inFlightTxnCleanups[txnID] = struct{}{} 564 return true, func() { 565 ir.mu.Lock() 566 delete(ir.mu.inFlightTxnCleanups, txnID) 567 ir.mu.Unlock() 568 } 569 } 570 571 // CleanupTxnIntentsOnGCAsync cleans up extant intents owned by a single 572 // transaction, asynchronously (but returning an error if the IntentResolver's 573 // semaphore is maxed out). If the transaction is not finalized, but expired, it 574 // is pushed first to abort it. onComplete is called if non-nil upon completion 575 // of async task with the intention that it be used as a hook to update metrics. 576 // It will not be called if an error is returned. 577 func (ir *IntentResolver) CleanupTxnIntentsOnGCAsync( 578 ctx context.Context, 579 rangeID roachpb.RangeID, 580 txn *roachpb.Transaction, 581 intents []roachpb.LockUpdate, 582 now hlc.Timestamp, 583 onComplete func(pushed, succeeded bool), 584 ) error { 585 return ir.stopper.RunLimitedAsyncTask( 586 // If we've successfully launched a background task, 587 // dissociate this work from our caller's context and 588 // timeout. 589 ir.ambientCtx.AnnotateCtx(context.Background()), 590 "processing txn intents", 591 ir.sem, 592 // We really do not want to hang up the GC queue on this kind of 593 // processing, so it's better to just skip txns which we can't 594 // pass to the async processor (wait=false). Their intents will 595 // get cleaned up on demand, and we'll eventually get back to 596 // them. Not much harm in having old txn records lying around in 597 // the meantime. 598 false, /* wait */ 599 func(ctx context.Context) { 600 var pushed, succeeded bool 601 defer func() { 602 if onComplete != nil { 603 onComplete(pushed, succeeded) 604 } 605 }() 606 locked, release := ir.lockInFlightTxnCleanup(ctx, txn.ID) 607 if !locked { 608 return 609 } 610 defer release() 611 // If the transaction is not yet finalized, but expired, push it 612 // before resolving the intents. 613 if !txn.Status.IsFinalized() { 614 if !txnwait.IsExpired(now, txn) { 615 log.VErrEventf(ctx, 3, "cannot push a %s transaction which is not expired: %s", txn.Status, txn) 616 return 617 } 618 b := &kv.Batch{} 619 b.Header.Timestamp = now 620 b.AddRawRequest(&roachpb.PushTxnRequest{ 621 RequestHeader: roachpb.RequestHeader{Key: txn.Key}, 622 PusherTxn: roachpb.Transaction{ 623 TxnMeta: enginepb.TxnMeta{Priority: enginepb.MaxTxnPriority}, 624 }, 625 PusheeTxn: txn.TxnMeta, 626 PushType: roachpb.PUSH_ABORT, 627 }) 628 pushed = true 629 if err := ir.db.Run(ctx, b); err != nil { 630 log.VErrEventf(ctx, 2, "failed to push %s, expired txn (%s): %s", txn.Status, txn, err) 631 return 632 } 633 // Get the pushed txn and update the intents slice. 634 txn = &b.RawResponse().Responses[0].GetInner().(*roachpb.PushTxnResponse).PusheeTxn 635 for i := range intents { 636 intents[i].SetTxn(txn) 637 } 638 } 639 var onCleanupComplete func(error) 640 if onComplete != nil { 641 onCompleteCopy := onComplete // copy onComplete for use in onCleanupComplete 642 onCleanupComplete = func(err error) { 643 onCompleteCopy(pushed, err == nil) 644 } 645 } 646 // Set onComplete to nil to disable the deferred call as the call has now 647 // been delegated to the callback passed to cleanupFinishedTxnIntents. 648 onComplete = nil 649 err := ir.cleanupFinishedTxnIntents(ctx, rangeID, txn, intents, now, false /* poison */, onCleanupComplete) 650 if err != nil { 651 if ir.every.ShouldLog() { 652 log.Warningf(ctx, "failed to cleanup transaction intents: %+v", err) 653 } 654 } 655 }, 656 ) 657 } 658 659 func (ir *IntentResolver) gcTxnRecord( 660 ctx context.Context, rangeID roachpb.RangeID, txn *roachpb.Transaction, 661 ) error { 662 // We successfully resolved the intents, so we're able to GC from 663 // the txn span directly. 664 txnKey := keys.TransactionKey(txn.Key, txn.ID) 665 // This is pretty tricky. Transaction keys are range-local and 666 // so they are encoded specially. The key range addressed by 667 // (txnKey, txnKey.Next()) might be empty (since Next() does 668 // not imply monotonicity on the address side). Instead, we 669 // send this request to a range determined using the resolved 670 // transaction anchor, i.e. if the txn is anchored on 671 // /Local/RangeDescriptor/"a"/uuid, the key range below would 672 // be ["a", "a\x00"). However, the first range is special again 673 // because the above procedure results in KeyMin, but we need 674 // at least KeyLocalMax. 675 // 676 // #7880 will address this by making GCRequest less special and 677 // thus obviating the need to cook up an artificial range here. 678 var gcArgs roachpb.GCRequest 679 { 680 key := keys.MustAddr(txn.Key) 681 if localMax := keys.MustAddr(keys.LocalMax); key.Less(localMax) { 682 key = localMax 683 } 684 endKey := key.Next() 685 686 gcArgs.RequestHeader = roachpb.RequestHeader{ 687 Key: key.AsRawKey(), 688 EndKey: endKey.AsRawKey(), 689 } 690 } 691 gcArgs.Keys = append(gcArgs.Keys, roachpb.GCRequest_GCKey{ 692 Key: txnKey, 693 }) 694 // Although the IntentResolver has a RangeDescriptorCache it could consult to 695 // to determine the range to which this request corresponds, GCRequests are 696 // always issued on behalf of the range on which this record resides which is 697 // a strong signal that it is the range which will contain the transaction 698 // record now. 699 _, err := ir.gcBatcher.Send(ctx, rangeID, &gcArgs) 700 if err != nil { 701 return errors.Wrapf(err, "could not GC completed transaction anchored at %s", 702 roachpb.Key(txn.Key)) 703 } 704 return nil 705 } 706 707 // cleanupFinishedTxnIntents cleans up extant intents owned by a single 708 // transaction and when all intents have been successfully resolved, the 709 // transaction record is GC'ed asynchronously. onComplete will be called when 710 // all processing has completed which is likely to be after this call returns 711 // in the case of success. 712 func (ir *IntentResolver) cleanupFinishedTxnIntents( 713 ctx context.Context, 714 rangeID roachpb.RangeID, 715 txn *roachpb.Transaction, 716 intents []roachpb.LockUpdate, 717 now hlc.Timestamp, 718 poison bool, 719 onComplete func(error), 720 ) (err error) { 721 defer func() { 722 // When err is non-nil we are guaranteed that the async task is not started 723 // so there is no race on calling onComplete. 724 if err != nil && onComplete != nil { 725 onComplete(err) 726 } 727 }() 728 // Resolve intents. 729 opts := ResolveOptions{Poison: poison, MinTimestamp: txn.MinTimestamp} 730 if pErr := ir.ResolveIntents(ctx, intents, opts); pErr != nil { 731 return errors.Wrapf(pErr.GoError(), "failed to resolve intents") 732 } 733 // Run transaction record GC outside of ir.sem. 734 return ir.stopper.RunAsyncTask( 735 ctx, 736 "storage.IntentResolver: cleanup txn records", 737 func(ctx context.Context) { 738 err := ir.gcTxnRecord(ctx, rangeID, txn) 739 if onComplete != nil { 740 onComplete(err) 741 } 742 if err != nil { 743 if ir.every.ShouldLog() { 744 log.Warningf(ctx, "failed to gc transaction record: %v", err) 745 } 746 } 747 }) 748 } 749 750 // ResolveOptions is used during intent resolution. It specifies whether the 751 // caller wants the call to block, and whether the ranges containing the intents 752 // are to be poisoned. 753 type ResolveOptions struct { 754 Poison bool 755 // The original transaction timestamp from the earliest txn epoch; if 756 // supplied, resolution of intent ranges can be optimized in some cases. 757 MinTimestamp hlc.Timestamp 758 } 759 760 // lookupRangeID maps a key to a RangeID for best effort batching of intent 761 // resolution requests. 762 func (ir *IntentResolver) lookupRangeID(ctx context.Context, key roachpb.Key) roachpb.RangeID { 763 rKey, err := keys.Addr(key) 764 if err != nil { 765 if ir.every.ShouldLog() { 766 log.Warningf(ctx, "failed to resolve addr for key %q: %+v", key, err) 767 } 768 return 0 769 } 770 rDesc, err := ir.rdc.LookupRangeDescriptor(ctx, rKey) 771 if err != nil { 772 if ir.every.ShouldLog() { 773 log.Warningf(ctx, "failed to look up range descriptor for key %q: %+v", key, err) 774 } 775 return 0 776 } 777 return rDesc.RangeID 778 } 779 780 // ResolveIntent synchronously resolves an intent according to opts. 781 func (ir *IntentResolver) ResolveIntent( 782 ctx context.Context, intent roachpb.LockUpdate, opts ResolveOptions, 783 ) *roachpb.Error { 784 return ir.ResolveIntents(ctx, []roachpb.LockUpdate{intent}, opts) 785 } 786 787 // ResolveIntents synchronously resolves intents according to opts. 788 func (ir *IntentResolver) ResolveIntents( 789 ctx context.Context, intents []roachpb.LockUpdate, opts ResolveOptions, 790 ) *roachpb.Error { 791 if len(intents) == 0 { 792 return nil 793 } 794 // Avoid doing any work on behalf of expired contexts. See 795 // https://github.com/cockroachdb/cockroach/issues/15997. 796 if err := ctx.Err(); err != nil { 797 return roachpb.NewError(err) 798 } 799 log.Eventf(ctx, "resolving intents") 800 ctx, cancel := context.WithCancel(ctx) 801 defer cancel() 802 803 respChan := make(chan requestbatcher.Response, len(intents)) 804 for _, intent := range intents { 805 rangeID := ir.lookupRangeID(ctx, intent.Key) 806 var req roachpb.Request 807 var batcher *requestbatcher.RequestBatcher 808 if len(intent.EndKey) == 0 { 809 req = &roachpb.ResolveIntentRequest{ 810 RequestHeader: roachpb.RequestHeaderFromSpan(intent.Span), 811 IntentTxn: intent.Txn, 812 Status: intent.Status, 813 Poison: opts.Poison, 814 IgnoredSeqNums: intent.IgnoredSeqNums, 815 } 816 batcher = ir.irBatcher 817 } else { 818 req = &roachpb.ResolveIntentRangeRequest{ 819 RequestHeader: roachpb.RequestHeaderFromSpan(intent.Span), 820 IntentTxn: intent.Txn, 821 Status: intent.Status, 822 Poison: opts.Poison, 823 MinTimestamp: opts.MinTimestamp, 824 IgnoredSeqNums: intent.IgnoredSeqNums, 825 } 826 batcher = ir.irRangeBatcher 827 } 828 if err := batcher.SendWithChan(ctx, respChan, rangeID, req); err != nil { 829 return roachpb.NewError(err) 830 } 831 } 832 for seen := 0; seen < len(intents); seen++ { 833 select { 834 case resp := <-respChan: 835 if resp.Err != nil { 836 return roachpb.NewError(resp.Err) 837 } 838 _ = resp.Resp // ignore the response 839 case <-ctx.Done(): 840 return roachpb.NewError(ctx.Err()) 841 } 842 } 843 return nil 844 } 845 846 // intentsByTxn implements sort.Interface to sort intents based on txnID. 847 type intentsByTxn []roachpb.Intent 848 849 var _ sort.Interface = intentsByTxn(nil) 850 851 func (s intentsByTxn) Len() int { return len(s) } 852 func (s intentsByTxn) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 853 func (s intentsByTxn) Less(i, j int) bool { 854 return bytes.Compare(s[i].Txn.ID[:], s[j].Txn.ID[:]) < 0 855 }