github.com/cockroachdb/cockroachdb-parser@v0.23.3-0.20240213214944-911057d40c9a/pkg/util/admission/admissionpb/admissionpb.go (about)

     1  // Copyright 2022 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 admissionpb
    12  
    13  import (
    14  	"math"
    15  
    16  	"github.com/cockroachdb/errors"
    17  	"github.com/cockroachdb/redact"
    18  )
    19  
    20  // WorkPriority represents the priority of work. In an WorkQueue, it is only
    21  // used for ordering within a tenant. High priority work can starve lower
    22  // priority work.
    23  type WorkPriority int8
    24  
    25  // When adding to this list, remember to update the initialization logic of
    26  // workPriorityToLockPriMap.
    27  const (
    28  	// LowPri is low priority work.
    29  	LowPri WorkPriority = math.MinInt8
    30  	// TTLLowPri is low priority work from TTL internal submissions.
    31  	TTLLowPri WorkPriority = -100
    32  	// UserLowPri is low priority work from user submissions (SQL).
    33  	UserLowPri WorkPriority = -50
    34  	// BulkNormalPri is bulk priority work from bulk jobs, which could be run due
    35  	// to user submissions or be automatic.
    36  	BulkNormalPri WorkPriority = -30
    37  	// NormalPri is normal priority work.
    38  	NormalPri WorkPriority = 0
    39  	// LockingNormalPri is used for user normal priority transactions that are
    40  	// acquiring locks.
    41  	LockingNormalPri WorkPriority = 10
    42  	// UserHighPri is high priority work from user submissions (SQL).
    43  	UserHighPri WorkPriority = 50
    44  	// LockingUserHighPri is for user high priority transactions that are
    45  	// acquiring locks.
    46  	LockingUserHighPri WorkPriority = 100
    47  	// HighPri is high priority work.
    48  	HighPri WorkPriority = math.MaxInt8
    49  	// OneAboveHighPri is one priority level above the highest priority.
    50  	OneAboveHighPri int = int(HighPri) + 1
    51  )
    52  
    53  func (w WorkPriority) String() string {
    54  	return redact.StringWithoutMarkers(w)
    55  }
    56  
    57  // SafeFormat implements the redact.SafeFormatter interface.
    58  func (w WorkPriority) SafeFormat(p redact.SafePrinter, verb rune) {
    59  	if s, ok := WorkPriorityDict[w]; ok {
    60  		p.Print(s)
    61  		return
    62  	}
    63  	p.Printf("custom-pri=%d", int8(w))
    64  }
    65  
    66  // WorkPriorityDict is a mapping of the priorities to a short string name. The
    67  // name is used as the suffix on exported work queue metrics.
    68  var WorkPriorityDict = map[WorkPriority]string{
    69  	LowPri:           "low-pri",
    70  	TTLLowPri:        "ttl-low-pri",
    71  	UserLowPri:       "user-low-pri",
    72  	BulkNormalPri:    "bulk-normal-pri",
    73  	NormalPri:        "normal-pri",
    74  	LockingNormalPri: "locking-normal-pri",
    75  	UserHighPri:      "user-high-pri",
    76  	// This ought to be called "locking-user-high-pri", but we retain the old
    77  	// name for continuity with metrics in older versions.
    78  	LockingUserHighPri: "locking-pri",
    79  	HighPri:            "high-pri",
    80  }
    81  
    82  // workPriorityToLockPriMap maps WorkPriority to another WorkPriority for when
    83  // the txn has already acquired a lock. Since WorkPriority can be negative,
    84  // and this map is an array, the index into the array is priToArrayIndex(p)
    85  // where p is a WorkPriority.
    86  //
    87  // The priority mapping is not simply p+1 since the enum values are used in
    88  // exported metrics, and we don't want to increase the number of such metrics.
    89  var workPriorityToLockPriMap [math.MaxInt8 - math.MinInt8 + 1]WorkPriority
    90  
    91  func priToArrayIndex(pri WorkPriority) int {
    92  	return int(pri) - math.MinInt8
    93  }
    94  
    95  // TestingReverseWorkPriorityDict is the reverse-lookup dictionary for
    96  // WorkPriorityDict, for use in tests.
    97  var TestingReverseWorkPriorityDict map[string]WorkPriority
    98  
    99  func init() {
   100  	TestingReverseWorkPriorityDict = make(map[string]WorkPriority)
   101  	for k, v := range WorkPriorityDict {
   102  		TestingReverseWorkPriorityDict[v] = k
   103  	}
   104  
   105  	orderedPris := []WorkPriority{
   106  		LowPri,
   107  		TTLLowPri,
   108  		UserLowPri,
   109  		BulkNormalPri,
   110  		NormalPri,
   111  		LockingNormalPri,
   112  		UserHighPri,
   113  		LockingUserHighPri,
   114  		HighPri,
   115  	}
   116  	j := 0
   117  	for i := range workPriorityToLockPriMap {
   118  		pri := WorkPriority(i) + math.MinInt8
   119  		if pri == orderedPris[j] && i != len(workPriorityToLockPriMap)-1 {
   120  			// Move to the next higher priority.
   121  			j++
   122  		}
   123  		workPriorityToLockPriMap[i] = orderedPris[j]
   124  	}
   125  	for i := range workPriorityToLockPriMap {
   126  		if priToArrayIndex(workPriorityToLockPriMap[i]) < i {
   127  			panic(errors.AssertionFailedf("workPriorityToLockPriMap at index %d has value %d",
   128  				i, workPriorityToLockPriMap[i]))
   129  		}
   130  		if priToArrayIndex(workPriorityToLockPriMap[i]) == i && i != priToArrayIndex(math.MaxInt8) {
   131  			panic(errors.AssertionFailedf(
   132  				"workPriorityToLockPriMap at index %d has no change for locking", i))
   133  		}
   134  	}
   135  }
   136  
   137  // AdjustedPriorityWhenHoldingLocks takes the original priority of a
   138  // transaction and updates it under the knowledge that the transaction is
   139  // holding locks.
   140  //
   141  // This broader context of locking is technically not in scope of the
   142  // admission package, but we define this function here as the WorkPriority
   143  // enum values are defined here.
   144  //
   145  // For example, UserLowPri should map to BulkNormalPri (see the hack below),
   146  // NormalPri maps to LockingNormalPri, and UserHighPri maps to
   147  // LockingUserHighPri. Say users are running at these different priorities in
   148  // different parts of the key space, say key-low, key-normal, key-high, then
   149  // even after the mapping, a txn holding locks (or resolving intents) in
   150  // key-low will have lower priority (BulkNormalPri) than the non-adjusted
   151  // priority in key-normal (NormalPri). The same holds true for txn holding
   152  // locks in key-normal, since LockingNormalPri is lower priority than
   153  // UserHighPri.
   154  //
   155  // Adjusting the priority can also be beneficial when all txns have the same
   156  // QoS requirements, but there is lock contention. In tpcc with 3000
   157  // warehouses, it halved the number of lock waiters, and increased the
   158  // transaction throughput by 10+%. In that experiment 40% of the BatchRequests
   159  // evaluated by KV had been assigned a higher priority due to locking.
   160  func AdjustedPriorityWhenHoldingLocks(pri WorkPriority) WorkPriority {
   161  	// TODO(sumeer): this is a temporary hack since index backfill and TTL can
   162  	// be running on tables that have user-facing work. We want these background
   163  	// transactions (when holding locks) to run at the priority of user-facing
   164  	// work + 1, but we don't know what that value is. This could be solved by
   165  	// providing the user-facing work priority in a SpanConfig, but for now we
   166  	// just assume that all user-facing work is running at NormalPri. See the
   167  	// examples in intentresolver/admission.go.
   168  	if pri < NormalPri {
   169  		pri = NormalPri
   170  	}
   171  	return workPriorityToLockPriMap[priToArrayIndex(pri)]
   172  }
   173  
   174  // WorkClass represents the class of work, which is defined entirely by its
   175  // WorkPriority. Namely, everything less than NormalPri is defined to be
   176  // "Elastic", while everything above and including NormalPri is considered
   177  // "Regular.
   178  type WorkClass int8
   179  
   180  const (
   181  	// RegularWorkClass is for work corresponding to workloads that are
   182  	// throughput and latency sensitive.
   183  	RegularWorkClass WorkClass = iota
   184  	// ElasticWorkClass is for work corresponding to workloads that can handle
   185  	// reduced throughput, possibly by taking longer to finish a workload. It is
   186  	// not latency sensitive.
   187  	ElasticWorkClass
   188  	// NumWorkClasses is the number of work classes.
   189  	NumWorkClasses
   190  )
   191  
   192  // WorkClassFromPri translates a WorkPriority to its given WorkClass.
   193  func WorkClassFromPri(pri WorkPriority) WorkClass {
   194  	class := RegularWorkClass
   195  	if pri < NormalPri {
   196  		class = ElasticWorkClass
   197  	}
   198  	return class
   199  }
   200  
   201  func (w WorkClass) String() string {
   202  	return redact.StringWithoutMarkers(w)
   203  }
   204  
   205  // SafeFormat implements the redact.SafeFormatter interface.
   206  func (w WorkClass) SafeFormat(p redact.SafePrinter, verb rune) {
   207  	switch w {
   208  	case RegularWorkClass:
   209  		p.Printf("regular")
   210  	case ElasticWorkClass:
   211  		p.Printf("elastic")
   212  	default:
   213  		p.Printf("<unknown-class>")
   214  	}
   215  }
   216  
   217  // Prevent the linter from emitting unused warnings.
   218  var _ = LowPri
   219  var _ = TTLLowPri
   220  var _ = UserLowPri
   221  var _ = NormalPri
   222  var _ = UserHighPri
   223  var _ = LockingUserHighPri
   224  var _ = HighPri