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  }