Skip to content

Commit 6e424ea

Browse files
authored
Global RPS Control for Admin Batch Operation (#10546)
## What changed? - Add host level rate limiter for controlling RPS of admin batch operations across all namespaces and all admin batch workflows. ## Why? - When running admin batch operations we only care about controlling the total RPS, not really RPS for a given namespace or a given admin batch workflow. ## How did you test it? - [x] built - [x] run locally and tested manually - [x] covered by existing tests - [ ] added new unit test(s) - [ ] added new functional test(s)
1 parent e594a8f commit 6e424ea

6 files changed

Lines changed: 72 additions & 26 deletions

File tree

common/dynamicconfig/constants.go

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3236,12 +3236,24 @@ When enabled, the scavenger will delete completed workflow execution data that a
32363236
BatcherRPS = NewNamespaceIntSetting(
32373237
"worker.batcherRPS",
32383238
50,
3239-
`BatcherRPS controls number the rps of batch operations`,
3239+
`BatcherRPS controls number the rps of one batch operation`,
32403240
)
32413241
BatcherConcurrency = NewNamespaceIntSetting(
32423242
"worker.batcherConcurrency",
32433243
5,
3244-
`BatcherConcurrency controls the concurrency of one batch operation`,
3244+
`BatcherConcurrency controls the concurrency of one batch or admin batch operation`,
3245+
)
3246+
AdminBatcherHostRPS = NewGlobalIntSetting(
3247+
"worker.adminBatcherHostRPS",
3248+
100,
3249+
`AdminBatcherHostRPS controls the rps of all admin batch operations per host`,
3250+
)
3251+
AdminBatcherGlobalRPS = NewGlobalIntSetting(
3252+
"worker.adminBatcherGlobalRPS",
3253+
0,
3254+
`AdminBatcherGlobalRPS controls the rps of all admin batch operations across all worker hosts.
3255+
The configured value will be divided by the number of worker hosts to get the per host rps limit.
3256+
0 means no global limit and each host will use AdminBatcherHostRPS.`,
32453257
)
32463258
WorkerParentCloseMaxConcurrentActivityExecutionSize = NewGlobalIntSetting(
32473259
"worker.ParentCloseMaxConcurrentActivityExecutionSize",

common/log/tag/values.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ var (
121121
ComponentESVisibilityManager = component("es-visibility-manager")
122122
ComponentArchiver = component("archiver")
123123
ComponentBatcher = component("batcher")
124+
ComponentAdminBatcher = component("admin-batcher")
124125
ComponentWorker = component("worker")
125126
ComponentWorkerManager = component("worker-manager")
126127
ComponentPerNSWorkerManager = component("perns-worker-manager")

service/worker/batcher/activities.go

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,16 @@ const (
4343

4444
var (
4545
errNamespaceMismatch = errors.New("namespace mismatch")
46+
47+
batchQuotaRequest = quotas.Request{
48+
Token: 1,
49+
}
4650
)
4751

4852
// batchProcessorConfig holds the configuration for batch processing
4953
type batchProcessorConfig struct {
5054
namespace string
5155
adjustedQuery string
52-
rps dynamicconfig.IntPropertyFnWithNamespaceFilter
5356
concurrency int
5457
initialPageToken []byte
5558
initialExecutions []*commonpb.WorkflowExecution
@@ -60,7 +63,7 @@ type batchWorkerProcessor func(
6063
ctx context.Context,
6164
taskCh chan task,
6265
respCh chan taskResponse,
63-
rateLimiter quotas.RateLimiter,
66+
rateLimiter quotas.RequestRateLimiter,
6467
sdkClient sdkclient.Client,
6568
frontendClient workflowservice.WorkflowServiceClient,
6669
metricsHandler metrics.Handler,
@@ -158,14 +161,12 @@ func (a *activities) processWorkflowsWithProactiveFetching(
158161
ctx context.Context,
159162
config batchProcessorConfig,
160163
startWorkerProcessor batchWorkerProcessor,
164+
rateLimiter quotas.RequestRateLimiter,
161165
sdkClient sdkclient.Client,
162166
metricsHandler metrics.Handler,
163167
logger log.Logger,
164168
hbd HeartBeatDetails,
165169
) (HeartBeatDetails, error) {
166-
rateLimiter := quotas.NewDefaultOutgoingRateLimiter(func() float64 {
167-
return float64(config.rps(config.namespace))
168-
})
169170

170171
concurrency := int(math.Max(1, float64(config.concurrency)))
171172

@@ -319,6 +320,9 @@ func (a *activities) BatchActivityWithProtobuf(ctx context.Context, batchParams
319320
var visibilityQuery string
320321
var executions []*commonpb.WorkflowExecution
321322

323+
// Admin batch uses the host level rate limiter which applies across all namespaces and all admin batch workflows.
324+
rateLimiter := quotas.RequestRateLimiter(a.AdminBatcherRateLimiter)
325+
322326
if batchParams.AdminRequest != nil {
323327
ctx = headers.SetCallerType(ctx, headers.CallerTypePreemptable)
324328
adminReq := batchParams.AdminRequest
@@ -327,6 +331,9 @@ func (a *activities) BatchActivityWithProtobuf(ctx context.Context, batchParams
327331
} else {
328332
visibilityQuery = a.adjustQueryBatchTypeEnum(batchParams.Request.VisibilityQuery, batchParams.BatchType)
329333
executions = batchParams.Request.Executions
334+
rateLimiter = quotas.NewRequestRateLimiterAdapter(quotas.NewDefaultOutgoingRateLimiter(func() float64 {
335+
return float64(a.rps(ns))
336+
}))
330337
}
331338

332339
if startOver {
@@ -353,7 +360,6 @@ func (a *activities) BatchActivityWithProtobuf(ctx context.Context, batchParams
353360
config := batchProcessorConfig{
354361
namespace: ns,
355362
adjustedQuery: visibilityQuery,
356-
rps: a.rps,
357363
concurrency: a.getOperationConcurrency(int(batchParams.Concurrency)),
358364
initialPageToken: hbd.PageToken,
359365
initialExecutions: executions,
@@ -364,7 +370,7 @@ func (a *activities) BatchActivityWithProtobuf(ctx context.Context, batchParams
364370
ctx context.Context,
365371
taskCh chan task,
366372
respCh chan taskResponse,
367-
rateLimiter quotas.RateLimiter,
373+
rateLimiter quotas.RequestRateLimiter,
368374
sdkClient sdkclient.Client,
369375
frontendClient workflowservice.WorkflowServiceClient,
370376
metricsHandler metrics.Handler,
@@ -373,7 +379,7 @@ func (a *activities) BatchActivityWithProtobuf(ctx context.Context, batchParams
373379
a.startTaskProcessor(ctx, batchParams, ns, taskCh, respCh, rateLimiter, sdkClient, frontendClient, metricsHandler, logger)
374380
}
375381

376-
return a.processWorkflowsWithProactiveFetching(ctx, config, workerProcessor, sdkClient, metricsHandler, logger, hbd)
382+
return a.processWorkflowsWithProactiveFetching(ctx, config, workerProcessor, rateLimiter, sdkClient, metricsHandler, logger, hbd)
377383
}
378384

379385
func (a *activities) getActivityLogger(ctx context.Context) log.Logger {
@@ -420,7 +426,7 @@ func (a *activities) startTaskProcessor(
420426
namespace string,
421427
taskCh chan task,
422428
respCh chan taskResponse,
423-
limiter quotas.RateLimiter,
429+
limiter quotas.RequestRateLimiter,
424430
sdkClient sdkclient.Client,
425431
frontendClient workflowservice.WorkflowServiceClient,
426432
metricsHandler metrics.Handler,
@@ -673,7 +679,7 @@ func (a *activities) processAdminTask(
673679
ctx context.Context,
674680
batchOperation *batchspb.BatchOperationInput,
675681
task task,
676-
limiter quotas.RateLimiter,
682+
limiter quotas.RequestRateLimiter,
677683
) error {
678684
adminReq := batchOperation.AdminRequest
679685
switch adminReq.Operation.(type) {
@@ -701,11 +707,11 @@ func (a *activities) processAdminTask(
701707

702708
func processTask(
703709
ctx context.Context,
704-
limiter quotas.RateLimiter,
710+
limiter quotas.RequestRateLimiter,
705711
task task,
706712
procFn func(*workflowpb.WorkflowExecutionInfo) error,
707713
) error {
708-
err := limiter.Wait(ctx)
714+
err := limiter.Wait(ctx, batchQuotaRequest)
709715
if err != nil {
710716
return err
711717
}

service/worker/batcher/activities_namespace_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -142,8 +142,8 @@ func TestStartTaskProcessor_UsesWorkerBoundNamespaceForSignal(t *testing.T) {
142142
go func() {
143143
defer close(done)
144144
a.startTaskProcessor(ctx, batchOp, ns, taskCh, respCh,
145-
quotas.NewDefaultOutgoingRateLimiter(func() float64 { return 1e9 }), nil, mockFE,
146-
metrics.NoopMetricsHandler, log.NewTestLogger())
145+
quotas.NewRequestRateLimiterAdapter(quotas.NewDefaultOutgoingRateLimiter(func() float64 { return 1e9 })),
146+
nil, mockFE, metrics.NoopMetricsHandler, log.NewTestLogger())
147147
}()
148148

149149
<-respCh

service/worker/batcher/activities_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -451,7 +451,7 @@ func (s *activitiesSuite) TestProcessAdminTask_RefreshWorkflowTasks() {
451451
},
452452
}
453453

454-
limiter := quotas.NewDefaultOutgoingRateLimiter(func() float64 { return 100 })
454+
limiter := quotas.NewRequestRateLimiterAdapter(quotas.NewDefaultOutgoingRateLimiter(func() float64 { return 100 }))
455455

456456
// Expect RefreshWorkflowTasks to be called with correct parameters
457457
mockHistoryClient.EXPECT().RefreshWorkflowTasks(gomock.Any(), gomock.Any()).DoAndReturn(
@@ -497,7 +497,7 @@ func (s *activitiesSuite) TestProcessAdminTask_RefreshWorkflowTasks_Error() {
497497
},
498498
}
499499

500-
limiter := quotas.NewDefaultOutgoingRateLimiter(func() float64 { return 100 })
500+
limiter := quotas.NewRequestRateLimiterAdapter(quotas.NewDefaultOutgoingRateLimiter(func() float64 { return 100 }))
501501

502502
expectedErr := errors.New("refresh failed")
503503
// Use gomock.Any() for context since it's modified with CallerTypePreemptable header
@@ -617,7 +617,7 @@ func (s *activitiesSuite) TestStartTaskProcessor_SignalUsesWorkerNamespace() {
617617

618618
taskCh := make(chan task, 1)
619619
respCh := make(chan taskResponse, 1)
620-
limiter := quotas.NewDefaultOutgoingRateLimiter(func() float64 { return 100 })
620+
limiter := quotas.NewRequestRateLimiterAdapter(quotas.NewDefaultOutgoingRateLimiter(func() float64 { return 100 }))
621621

622622
// The signal must be executed with the worker's trusted namespace, not the user-supplied one.
623623
s.mockFrontendClient.EXPECT().
@@ -657,7 +657,7 @@ func (s *activitiesSuite) TestProcessAdminTask_UnknownOperation() {
657657
},
658658
}
659659

660-
limiter := quotas.NewDefaultOutgoingRateLimiter(func() float64 { return 100 })
660+
limiter := quotas.NewRequestRateLimiterAdapter(quotas.NewDefaultOutgoingRateLimiter(func() float64 { return 100 }))
661661

662662
err := a.processAdminTask(ctx, batchOperation, testTask, limiter)
663663
s.Require().Error(err)

service/worker/batcher/fx.go

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,12 @@ import (
77
"go.temporal.io/server/api/adminservice/v1"
88
"go.temporal.io/server/common/dynamicconfig"
99
"go.temporal.io/server/common/log"
10+
"go.temporal.io/server/common/log/tag"
11+
"go.temporal.io/server/common/membership"
1012
"go.temporal.io/server/common/metrics"
1113
"go.temporal.io/server/common/namespace"
14+
"go.temporal.io/server/common/quotas"
15+
"go.temporal.io/server/common/quotas/calculator"
1216
"go.temporal.io/server/common/resource"
1317
"go.temporal.io/server/common/sdk"
1418
workercommon "go.temporal.io/server/service/worker/common"
@@ -24,6 +28,8 @@ const (
2428
)
2529

2630
type (
31+
AdminBatcherRateLimiter quotas.RequestRateLimiter
32+
2733
workerComponent struct {
2834
activityDeps activityDeps
2935
dc *dynamicconfig.Collection
@@ -32,12 +38,13 @@ type (
3238

3339
activityDeps struct {
3440
fx.In
35-
MetricsHandler metrics.Handler
36-
Logger log.Logger
37-
ClientFactory sdk.ClientFactory
38-
FrontendClient workflowservice.WorkflowServiceClient
39-
AdminClient adminservice.AdminServiceClient
40-
HistoryClient resource.HistoryClient
41+
MetricsHandler metrics.Handler
42+
Logger log.Logger
43+
ClientFactory sdk.ClientFactory
44+
FrontendClient workflowservice.WorkflowServiceClient
45+
AdminClient adminservice.AdminServiceClient
46+
HistoryClient resource.HistoryClient
47+
AdminBatcherRateLimiter AdminBatcherRateLimiter
4148
}
4249

4350
fxResult struct {
@@ -47,9 +54,29 @@ type (
4754
)
4855

4956
var Module = fx.Options(
57+
fx.Provide(AdminBatcherRateLimiterProvider),
5058
fx.Provide(NewResult),
5159
)
5260

61+
func AdminBatcherRateLimiterProvider(
62+
dc *dynamicconfig.Collection,
63+
serviceResolver membership.ServiceResolver,
64+
logger log.Logger,
65+
) AdminBatcherRateLimiter {
66+
return quotas.NewRequestRateLimiterAdapter(
67+
quotas.NewDefaultOutgoingRateLimiter(
68+
calculator.NewLoggedCalculator(
69+
calculator.ClusterAwareQuotaCalculator{
70+
MemberCounter: serviceResolver,
71+
PerInstanceQuota: dynamicconfig.AdminBatcherHostRPS.Get(dc),
72+
GlobalQuota: dynamicconfig.AdminBatcherGlobalRPS.Get(dc),
73+
},
74+
log.With(logger, tag.ComponentAdminBatcher, tag.ScopeHost),
75+
).GetQuota,
76+
),
77+
)
78+
}
79+
5380
func NewResult(
5481
dc *dynamicconfig.Collection,
5582
params activityDeps,

0 commit comments

Comments
 (0)