go.temporal.io/server@v1.23.0/common/persistence/client/quotas.go (about) 1 // The MIT License 2 // 3 // Copyright (c) 2020 Temporal Technologies Inc. All rights reserved. 4 // 5 // Copyright (c) 2020 Uber Technologies, Inc. 6 // 7 // Permission is hereby granted, free of charge, to any person obtaining a copy 8 // of this software and associated documentation files (the "Software"), to deal 9 // in the Software without restriction, including without limitation the rights 10 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 // copies of the Software, and to permit persons to whom the Software is 12 // furnished to do so, subject to the following conditions: 13 // 14 // The above copyright notice and this permission notice shall be included in 15 // all copies or substantial portions of the Software. 16 // 17 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 // THE SOFTWARE. 24 25 package client 26 27 import ( 28 "go.temporal.io/server/common/headers" 29 "go.temporal.io/server/common/log" 30 "go.temporal.io/server/common/metrics" 31 p "go.temporal.io/server/common/persistence" 32 "go.temporal.io/server/common/quotas" 33 "go.temporal.io/server/service/history/tasks" 34 ) 35 36 type ( 37 perShardPerNamespaceKey struct { 38 namespaceID string 39 shardID int32 40 } 41 ) 42 43 var ( 44 CallerTypeDefaultPriority = map[string]int{ 45 headers.CallerTypeOperator: 0, 46 headers.CallerTypeAPI: 2, 47 headers.CallerTypeBackground: 4, 48 headers.CallerTypePreemptable: 5, 49 } 50 51 APITypeCallOriginPriorityOverride = map[string]int{ 52 "StartWorkflowExecution": 1, 53 "SignalWithStartWorkflowExecution": 1, 54 "SignalWorkflowExecution": 1, 55 "RequestCancelWorkflowExecution": 1, 56 "TerminateWorkflowExecution": 1, 57 "GetWorkflowExecutionHistory": 1, 58 "UpdateWorkflowExecution": 1, 59 } 60 61 BackgroundTypeAPIPriorityOverride = map[string]int{ 62 "GetOrCreateShard": 1, 63 "UpdateShard": 1, 64 65 // This is a preprequisite for checkpointing queue process progress 66 p.ConstructHistoryTaskAPI("RangeCompleteHistoryTasks", tasks.CategoryTransfer): 1, 67 p.ConstructHistoryTaskAPI("RangeCompleteHistoryTasks", tasks.CategoryTimer): 1, 68 p.ConstructHistoryTaskAPI("RangeCompleteHistoryTasks", tasks.CategoryVisibility): 1, 69 70 // Task resource isolation assumes task can always be loaded. 71 // When one namespace has high load, all task processing goroutines 72 // may be busy and consumes all persistence request tokens, preventing 73 // tasks for other namespaces to be loaded. So give task loading a higher 74 // priority than other background requests. 75 // NOTE: we also don't want task loading to consume all persistence request tokens, 76 // and blocks all other operations. This is done by setting the queue host rps limit 77 // dynamic config. 78 p.ConstructHistoryTaskAPI("GetHistoryTasks", tasks.CategoryTransfer): 3, 79 p.ConstructHistoryTaskAPI("GetHistoryTasks", tasks.CategoryTimer): 3, 80 p.ConstructHistoryTaskAPI("GetHistoryTasks", tasks.CategoryVisibility): 3, 81 } 82 83 RequestPrioritiesOrdered = []int{0, 1, 2, 3, 4, 5} 84 ) 85 86 func NewPriorityRateLimiter( 87 namespaceMaxQPS PersistenceNamespaceMaxQps, 88 hostMaxQPS PersistenceMaxQps, 89 perShardNamespaceMaxQPS PersistencePerShardNamespaceMaxQPS, 90 requestPriorityFn quotas.RequestPriorityFn, 91 operatorRPSRatio OperatorRPSRatio, 92 healthSignals p.HealthSignalAggregator, 93 dynamicParams DynamicRateLimitingParams, 94 metricsHandler metrics.Handler, 95 logger log.Logger, 96 ) quotas.RequestRateLimiter { 97 hostRateFn := func() float64 { return float64(hostMaxQPS()) } 98 99 return quotas.NewMultiRequestRateLimiter( 100 // per shardID+namespaceID rate limiters 101 newPerShardPerNamespacePriorityRateLimiter(perShardNamespaceMaxQPS, hostMaxQPS, requestPriorityFn, operatorRPSRatio), 102 // per namespaceID rate limiters 103 newPriorityNamespaceRateLimiter(namespaceMaxQPS, hostMaxQPS, requestPriorityFn, operatorRPSRatio), 104 // host-level dynamic rate limiter 105 newPriorityDynamicRateLimiter(hostRateFn, requestPriorityFn, operatorRPSRatio, healthSignals, dynamicParams, metricsHandler, logger), 106 // basic host-level rate limiter 107 newPriorityRateLimiter(hostRateFn, requestPriorityFn, operatorRPSRatio), 108 ) 109 } 110 111 func newPerShardPerNamespacePriorityRateLimiter( 112 perShardNamespaceMaxQPS PersistencePerShardNamespaceMaxQPS, 113 hostMaxQPS PersistenceMaxQps, 114 requestPriorityFn quotas.RequestPriorityFn, 115 operatorRPSRatio OperatorRPSRatio, 116 ) quotas.RequestRateLimiter { 117 return quotas.NewMapRequestRateLimiter(func(req quotas.Request) quotas.RequestRateLimiter { 118 if hasCaller(req) && hasCallerSegment(req) { 119 return newPriorityRateLimiter(func() float64 { 120 if perShardNamespaceMaxQPS == nil || perShardNamespaceMaxQPS(req.Caller) <= 0 { 121 return float64(hostMaxQPS()) 122 } 123 return float64(perShardNamespaceMaxQPS(req.Caller)) 124 }, 125 requestPriorityFn, 126 operatorRPSRatio, 127 ) 128 } 129 return quotas.NoopRequestRateLimiter 130 }, 131 perShardPerNamespaceKeyFn, 132 ) 133 } 134 135 func perShardPerNamespaceKeyFn(req quotas.Request) perShardPerNamespaceKey { 136 return perShardPerNamespaceKey{ 137 namespaceID: req.Caller, 138 shardID: req.CallerSegment, 139 } 140 } 141 142 func newPriorityNamespaceRateLimiter( 143 namespaceMaxQPS PersistenceNamespaceMaxQps, 144 hostMaxQPS PersistenceMaxQps, 145 requestPriorityFn quotas.RequestPriorityFn, 146 operatorRPSRatio OperatorRPSRatio, 147 ) quotas.RequestRateLimiter { 148 return quotas.NewNamespaceRequestRateLimiter(func(req quotas.Request) quotas.RequestRateLimiter { 149 if hasCaller(req) { 150 return newPriorityRateLimiter( 151 func() float64 { 152 if namespaceMaxQPS == nil { 153 return float64(hostMaxQPS()) 154 } 155 156 namespaceQPS := float64(namespaceMaxQPS(req.Caller)) 157 if namespaceQPS <= 0 { 158 return float64(hostMaxQPS()) 159 } 160 161 return namespaceQPS 162 }, 163 requestPriorityFn, 164 operatorRPSRatio, 165 ) 166 } 167 return quotas.NoopRequestRateLimiter 168 }) 169 } 170 171 func newPriorityRateLimiter( 172 rateFn quotas.RateFn, 173 requestPriorityFn quotas.RequestPriorityFn, 174 operatorRPSRatio OperatorRPSRatio, 175 ) quotas.RequestRateLimiter { 176 rateLimiters := make(map[int]quotas.RequestRateLimiter) 177 for priority := range RequestPrioritiesOrdered { 178 if priority == CallerTypeDefaultPriority[headers.CallerTypeOperator] { 179 rateLimiters[priority] = quotas.NewRequestRateLimiterAdapter(quotas.NewDefaultOutgoingRateLimiter(operatorRateFn(rateFn, operatorRPSRatio))) 180 } else { 181 rateLimiters[priority] = quotas.NewRequestRateLimiterAdapter(quotas.NewDefaultOutgoingRateLimiter(rateFn)) 182 } 183 } 184 185 return quotas.NewPriorityRateLimiter( 186 requestPriorityFn, 187 rateLimiters, 188 ) 189 } 190 191 func newPriorityDynamicRateLimiter( 192 rateFn quotas.RateFn, 193 requestPriorityFn quotas.RequestPriorityFn, 194 operatorRPSRatio OperatorRPSRatio, 195 healthSignals p.HealthSignalAggregator, 196 dynamicParams DynamicRateLimitingParams, 197 metricsHandler metrics.Handler, 198 logger log.Logger, 199 ) quotas.RequestRateLimiter { 200 rateLimiters := make(map[int]quotas.RequestRateLimiter) 201 for priority := range RequestPrioritiesOrdered { 202 // TODO: refactor this so dynamic rate adjustment is global for all priorities 203 if priority == CallerTypeDefaultPriority[headers.CallerTypeOperator] { 204 rateLimiters[priority] = NewHealthRequestRateLimiterImpl(healthSignals, operatorRateFn(rateFn, operatorRPSRatio), dynamicParams, metricsHandler, logger) 205 } else { 206 rateLimiters[priority] = NewHealthRequestRateLimiterImpl(healthSignals, rateFn, dynamicParams, metricsHandler, logger) 207 } 208 } 209 210 return quotas.NewPriorityRateLimiter( 211 requestPriorityFn, 212 rateLimiters, 213 ) 214 } 215 216 func NewNoopPriorityRateLimiter( 217 maxQPS PersistenceMaxQps, 218 ) quotas.RequestRateLimiter { 219 priority := RequestPrioritiesOrdered[0] 220 221 return quotas.NewPriorityRateLimiter( 222 func(_ quotas.Request) int { return priority }, 223 map[int]quotas.RequestRateLimiter{ 224 priority: quotas.NewRequestRateLimiterAdapter(quotas.NewDefaultOutgoingRateLimiter( 225 func() float64 { return float64(maxQPS()) }, 226 )), 227 }, 228 ) 229 } 230 231 func RequestPriorityFn(req quotas.Request) int { 232 switch req.CallerType { 233 case headers.CallerTypeOperator: 234 return CallerTypeDefaultPriority[req.CallerType] 235 case headers.CallerTypeAPI: 236 if priority, ok := APITypeCallOriginPriorityOverride[req.Initiation]; ok { 237 return priority 238 } 239 return CallerTypeDefaultPriority[req.CallerType] 240 case headers.CallerTypeBackground: 241 if priority, ok := BackgroundTypeAPIPriorityOverride[req.API]; ok { 242 return priority 243 } 244 return CallerTypeDefaultPriority[req.CallerType] 245 case headers.CallerTypePreemptable: 246 return CallerTypeDefaultPriority[req.CallerType] 247 default: 248 // default requests to API priority to be consistent with existing behavior 249 return CallerTypeDefaultPriority[headers.CallerTypeAPI] 250 } 251 } 252 253 func operatorRateFn(rateFn quotas.RateFn, operatorRPSRatio OperatorRPSRatio) quotas.RateFn { 254 return func() float64 { 255 return operatorRPSRatio() * rateFn() 256 } 257 } 258 259 func hasCaller(req quotas.Request) bool { 260 return req.Caller != "" && req.Caller != headers.CallerNameSystem 261 } 262 263 func hasCallerSegment(req quotas.Request) bool { 264 return req.CallerSegment > 0 && req.CallerSegment != p.CallerSegmentMissing 265 }