Dynamic partitioning: scale manager (#10365)

## What changed?
Add scale manager component.

## Why?
Part of dynamic partitioning.

## How did you test it?
- [ ] built
- [ ] run locally and tested manually
- [ ] covered by existing tests
- [x] added new unit test(s)
- [x] added new functional test(s) - to come in future PRs

## Potential risks
No behavior changes yet.
This commit is contained in:
David Reiss
2026-06-04 17:34:25 -07:00
committed by GitHub
parent 28dd23a0a1
commit d69aede7e5
38 changed files with 2029 additions and 259 deletions

View File

@@ -5219,6 +5219,7 @@ type DescribeTaskQueuePartitionResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
// contains k-v pairs of the type: buildID -> TaskQueueVersionInfoInternal
VersionsInfoInternal map[string]*v114.TaskQueueVersionInfoInternal `protobuf:"bytes,1,rep,name=versions_info_internal,json=versionsInfoInternal,proto3" json:"versions_info_internal,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
ScaleInfo *v114.PartitionScaleInfo `protobuf:"bytes,2,opt,name=scale_info,json=scaleInfo,proto3" json:"scale_info,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
@@ -5260,6 +5261,13 @@ func (x *DescribeTaskQueuePartitionResponse) GetVersionsInfoInternal() map[strin
return nil
}
func (x *DescribeTaskQueuePartitionResponse) GetScaleInfo() *v114.PartitionScaleInfo {
if x != nil {
return x.ScaleInfo
}
return nil
}
type ForceUnloadTaskQueuePartitionRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
Namespace string `protobuf:"bytes,1,opt,name=namespace,proto3" json:"namespace,omitempty"`
@@ -6294,9 +6302,11 @@ const file_temporal_server_api_adminservice_v1_request_response_proto_rawDesc =
"!DescribeTaskQueuePartitionRequest\x12\x1c\n" +
"\tnamespace\x18\x01 \x01(\tR\tnamespace\x12f\n" +
"\x14task_queue_partition\x18\x02 \x01(\v24.temporal.server.api.taskqueue.v1.TaskQueuePartitionR\x12taskQueuePartition\x12Q\n" +
"\tbuild_ids\x18\x03 \x01(\v24.temporal.api.taskqueue.v1.TaskQueueVersionSelectionR\bbuildIds\"\xc8\x02\n" +
"\tbuild_ids\x18\x03 \x01(\v24.temporal.api.taskqueue.v1.TaskQueueVersionSelectionR\bbuildIds\"\x9d\x03\n" +
"\"DescribeTaskQueuePartitionResponse\x12\x97\x01\n" +
"\x16versions_info_internal\x18\x01 \x03(\v2a.temporal.server.api.adminservice.v1.DescribeTaskQueuePartitionResponse.VersionsInfoInternalEntryR\x14versionsInfoInternal\x1a\x87\x01\n" +
"\x16versions_info_internal\x18\x01 \x03(\v2a.temporal.server.api.adminservice.v1.DescribeTaskQueuePartitionResponse.VersionsInfoInternalEntryR\x14versionsInfoInternal\x12S\n" +
"\n" +
"scale_info\x18\x02 \x01(\v24.temporal.server.api.taskqueue.v1.PartitionScaleInfoR\tscaleInfo\x1a\x87\x01\n" +
"\x19VersionsInfoInternalEntry\x12\x10\n" +
"\x03key\x18\x01 \x01(\tR\x03key\x12T\n" +
"\x05value\x18\x02 \x01(\v2>.temporal.server.api.taskqueue.v1.TaskQueueVersionInfoInternalR\x05value:\x028\x01\"\xac\x01\n" +
@@ -6505,9 +6515,10 @@ var file_temporal_server_api_adminservice_v1_request_response_proto_goTypes = []
(*v15.VersionedTransitionArtifact)(nil), // 144: temporal.server.api.replication.v1.VersionedTransitionArtifact
(*v114.TaskQueuePartition)(nil), // 145: temporal.server.api.taskqueue.v1.TaskQueuePartition
(*v115.TaskQueueVersionSelection)(nil), // 146: temporal.api.taskqueue.v1.TaskQueueVersionSelection
(*v12.TaskQueueTypeUserData)(nil), // 147: temporal.server.api.persistence.v1.TaskQueueTypeUserData
(v16.IndexedValueType)(0), // 148: temporal.api.enums.v1.IndexedValueType
(*v114.TaskQueueVersionInfoInternal)(nil), // 149: temporal.server.api.taskqueue.v1.TaskQueueVersionInfoInternal
(*v114.PartitionScaleInfo)(nil), // 147: temporal.server.api.taskqueue.v1.PartitionScaleInfo
(*v12.TaskQueueTypeUserData)(nil), // 148: temporal.server.api.persistence.v1.TaskQueueTypeUserData
(v16.IndexedValueType)(0), // 149: temporal.api.enums.v1.IndexedValueType
(*v114.TaskQueueVersionInfoInternal)(nil), // 150: temporal.server.api.taskqueue.v1.TaskQueueVersionInfoInternal
}
var file_temporal_server_api_adminservice_v1_request_response_proto_depIdxs = []int32{
106, // 0: temporal.server.api.adminservice.v1.RebuildMutableStateRequest.execution:type_name -> temporal.api.common.v1.WorkflowExecution
@@ -6591,23 +6602,24 @@ var file_temporal_server_api_adminservice_v1_request_response_proto_depIdxs = []
145, // 78: temporal.server.api.adminservice.v1.DescribeTaskQueuePartitionRequest.task_queue_partition:type_name -> temporal.server.api.taskqueue.v1.TaskQueuePartition
146, // 79: temporal.server.api.adminservice.v1.DescribeTaskQueuePartitionRequest.build_ids:type_name -> temporal.api.taskqueue.v1.TaskQueueVersionSelection
105, // 80: temporal.server.api.adminservice.v1.DescribeTaskQueuePartitionResponse.versions_info_internal:type_name -> temporal.server.api.adminservice.v1.DescribeTaskQueuePartitionResponse.VersionsInfoInternalEntry
145, // 81: temporal.server.api.adminservice.v1.ForceUnloadTaskQueuePartitionRequest.task_queue_partition:type_name -> temporal.server.api.taskqueue.v1.TaskQueuePartition
127, // 82: temporal.server.api.adminservice.v1.GetTaskQueueUserDataRequest.task_queue_type:type_name -> temporal.api.enums.v1.TaskQueueType
147, // 83: temporal.server.api.adminservice.v1.GetTaskQueueUserDataResponse.user_data:type_name -> temporal.server.api.persistence.v1.TaskQueueTypeUserData
106, // 84: temporal.server.api.adminservice.v1.StartAdminBatchOperationRequest.executions:type_name -> temporal.api.common.v1.WorkflowExecution
93, // 85: temporal.server.api.adminservice.v1.StartAdminBatchOperationRequest.refresh_tasks_operation:type_name -> temporal.server.api.adminservice.v1.BatchOperationRefreshTasks
0, // 86: temporal.server.api.adminservice.v1.MigrateScheduleRequest.target:type_name -> temporal.server.api.adminservice.v1.MigrateScheduleRequest.SchedulerTarget
116, // 87: temporal.server.api.adminservice.v1.GetReplicationMessagesResponse.ShardMessagesEntry.value:type_name -> temporal.server.api.replication.v1.ReplicationMessages
148, // 88: temporal.server.api.adminservice.v1.AddSearchAttributesRequest.SearchAttributesEntry.value:type_name -> temporal.api.enums.v1.IndexedValueType
148, // 89: temporal.server.api.adminservice.v1.GetSearchAttributesResponse.CustomAttributesEntry.value:type_name -> temporal.api.enums.v1.IndexedValueType
148, // 90: temporal.server.api.adminservice.v1.GetSearchAttributesResponse.SystemAttributesEntry.value:type_name -> temporal.api.enums.v1.IndexedValueType
107, // 91: temporal.server.api.adminservice.v1.AddTasksRequest.Task.blob:type_name -> temporal.api.common.v1.DataBlob
149, // 92: temporal.server.api.adminservice.v1.DescribeTaskQueuePartitionResponse.VersionsInfoInternalEntry.value:type_name -> temporal.server.api.taskqueue.v1.TaskQueueVersionInfoInternal
93, // [93:93] is the sub-list for method output_type
93, // [93:93] is the sub-list for method input_type
93, // [93:93] is the sub-list for extension type_name
93, // [93:93] is the sub-list for extension extendee
0, // [0:93] is the sub-list for field type_name
147, // 81: temporal.server.api.adminservice.v1.DescribeTaskQueuePartitionResponse.scale_info:type_name -> temporal.server.api.taskqueue.v1.PartitionScaleInfo
145, // 82: temporal.server.api.adminservice.v1.ForceUnloadTaskQueuePartitionRequest.task_queue_partition:type_name -> temporal.server.api.taskqueue.v1.TaskQueuePartition
127, // 83: temporal.server.api.adminservice.v1.GetTaskQueueUserDataRequest.task_queue_type:type_name -> temporal.api.enums.v1.TaskQueueType
148, // 84: temporal.server.api.adminservice.v1.GetTaskQueueUserDataResponse.user_data:type_name -> temporal.server.api.persistence.v1.TaskQueueTypeUserData
106, // 85: temporal.server.api.adminservice.v1.StartAdminBatchOperationRequest.executions:type_name -> temporal.api.common.v1.WorkflowExecution
93, // 86: temporal.server.api.adminservice.v1.StartAdminBatchOperationRequest.refresh_tasks_operation:type_name -> temporal.server.api.adminservice.v1.BatchOperationRefreshTasks
0, // 87: temporal.server.api.adminservice.v1.MigrateScheduleRequest.target:type_name -> temporal.server.api.adminservice.v1.MigrateScheduleRequest.SchedulerTarget
116, // 88: temporal.server.api.adminservice.v1.GetReplicationMessagesResponse.ShardMessagesEntry.value:type_name -> temporal.server.api.replication.v1.ReplicationMessages
149, // 89: temporal.server.api.adminservice.v1.AddSearchAttributesRequest.SearchAttributesEntry.value:type_name -> temporal.api.enums.v1.IndexedValueType
149, // 90: temporal.server.api.adminservice.v1.GetSearchAttributesResponse.CustomAttributesEntry.value:type_name -> temporal.api.enums.v1.IndexedValueType
149, // 91: temporal.server.api.adminservice.v1.GetSearchAttributesResponse.SystemAttributesEntry.value:type_name -> temporal.api.enums.v1.IndexedValueType
107, // 92: temporal.server.api.adminservice.v1.AddTasksRequest.Task.blob:type_name -> temporal.api.common.v1.DataBlob
150, // 93: temporal.server.api.adminservice.v1.DescribeTaskQueuePartitionResponse.VersionsInfoInternalEntry.value:type_name -> temporal.server.api.taskqueue.v1.TaskQueueVersionInfoInternal
94, // [94:94] is the sub-list for method output_type
94, // [94:94] is the sub-list for method input_type
94, // [94:94] is the sub-list for extension type_name
94, // [94:94] is the sub-list for extension extendee
0, // [0:94] is the sub-list for field type_name
}
func init() { file_temporal_server_api_adminservice_v1_request_response_proto_init() }

View File

@@ -1969,6 +1969,7 @@ func (x *DescribeTaskQueuePartitionRequest) GetReportInternalTaskQueueStatus() b
type DescribeTaskQueuePartitionResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
VersionsInfoInternal map[string]*v18.TaskQueueVersionInfoInternal `protobuf:"bytes,1,rep,name=versions_info_internal,json=versionsInfoInternal,proto3" json:"versions_info_internal,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
ScaleInfo *v18.PartitionScaleInfo `protobuf:"bytes,2,opt,name=scale_info,json=scaleInfo,proto3" json:"scale_info,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
@@ -2010,6 +2011,13 @@ func (x *DescribeTaskQueuePartitionResponse) GetVersionsInfoInternal() map[strin
return nil
}
func (x *DescribeTaskQueuePartitionResponse) GetScaleInfo() *v18.PartitionScaleInfo {
if x != nil {
return x.ScaleInfo
}
return nil
}
type ListTaskQueuePartitionsRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
Namespace string `protobuf:"bytes,1,opt,name=namespace,proto3" json:"namespace,omitempty"`
@@ -5965,9 +5973,11 @@ const file_temporal_server_api_matchingservice_v1_request_response_proto_rawDesc
"\bversions\x18\x03 \x01(\v24.temporal.api.taskqueue.v1.TaskQueueVersionSelectionR\bversions\x12!\n" +
"\freport_stats\x18\x04 \x01(\bR\vreportStats\x12%\n" +
"\x0ereport_pollers\x18\x05 \x01(\bR\rreportPollers\x12H\n" +
"!report_internal_task_queue_status\x18\x06 \x01(\bR\x1dreportInternalTaskQueueStatus\"\xcb\x02\n" +
"!report_internal_task_queue_status\x18\x06 \x01(\bR\x1dreportInternalTaskQueueStatus\"\xa0\x03\n" +
"\"DescribeTaskQueuePartitionResponse\x12\x9a\x01\n" +
"\x16versions_info_internal\x18\x01 \x03(\v2d.temporal.server.api.matchingservice.v1.DescribeTaskQueuePartitionResponse.VersionsInfoInternalEntryR\x14versionsInfoInternal\x1a\x87\x01\n" +
"\x16versions_info_internal\x18\x01 \x03(\v2d.temporal.server.api.matchingservice.v1.DescribeTaskQueuePartitionResponse.VersionsInfoInternalEntryR\x14versionsInfoInternal\x12S\n" +
"\n" +
"scale_info\x18\x02 \x01(\v24.temporal.server.api.taskqueue.v1.PartitionScaleInfoR\tscaleInfo\x1a\x87\x01\n" +
"\x19VersionsInfoInternalEntry\x12\x10\n" +
"\x03key\x18\x01 \x01(\tR\x03key\x12T\n" +
"\x05value\x18\x02 \x01(\v2>.temporal.server.api.taskqueue.v1.TaskQueueVersionInfoInternalR\x05value:\x028\x01\"\xa6\x01\n" +
@@ -6333,40 +6343,41 @@ var file_temporal_server_api_matchingservice_v1_request_response_proto_goTypes =
(*v1.DescribeTaskQueueResponse)(nil), // 118: temporal.api.workflowservice.v1.DescribeTaskQueueResponse
(*v18.TaskQueuePartition)(nil), // 119: temporal.server.api.taskqueue.v1.TaskQueuePartition
(*v14.TaskQueueVersionSelection)(nil), // 120: temporal.api.taskqueue.v1.TaskQueueVersionSelection
(*v14.TaskQueuePartitionMetadata)(nil), // 121: temporal.api.taskqueue.v1.TaskQueuePartitionMetadata
(*v1.GetWorkerVersioningRulesRequest)(nil), // 122: temporal.api.workflowservice.v1.GetWorkerVersioningRulesRequest
(*v1.GetWorkerVersioningRulesResponse)(nil), // 123: temporal.api.workflowservice.v1.GetWorkerVersioningRulesResponse
(*v1.UpdateWorkerVersioningRulesRequest)(nil), // 124: temporal.api.workflowservice.v1.UpdateWorkerVersioningRulesRequest
(*v1.UpdateWorkerVersioningRulesResponse)(nil), // 125: temporal.api.workflowservice.v1.UpdateWorkerVersioningRulesResponse
(*v1.GetWorkerBuildIdCompatibilityRequest)(nil), // 126: temporal.api.workflowservice.v1.GetWorkerBuildIdCompatibilityRequest
(*v1.GetWorkerBuildIdCompatibilityResponse)(nil), // 127: temporal.api.workflowservice.v1.GetWorkerBuildIdCompatibilityResponse
(*v111.VersionedTaskQueueUserData)(nil), // 128: temporal.server.api.persistence.v1.VersionedTaskQueueUserData
(*v18.VersionedEphemeralData)(nil), // 129: temporal.server.api.taskqueue.v1.VersionedEphemeralData
(*v110.DeploymentVersionData)(nil), // 130: temporal.server.api.deployment.v1.DeploymentVersionData
(*v112.RoutingConfig)(nil), // 131: temporal.api.deployment.v1.RoutingConfig
(*v111.TaskQueueUserData)(nil), // 132: temporal.server.api.persistence.v1.TaskQueueUserData
(*v113.Request)(nil), // 133: temporal.api.nexus.v1.Request
(*v113.HandlerError)(nil), // 134: temporal.api.nexus.v1.HandlerError
(*v113.Response)(nil), // 135: temporal.api.nexus.v1.Response
(*v114.Failure)(nil), // 136: temporal.api.failure.v1.Failure
(*v1.PollNexusTaskQueueRequest)(nil), // 137: temporal.api.workflowservice.v1.PollNexusTaskQueueRequest
(*v1.PollNexusTaskQueueResponse)(nil), // 138: temporal.api.workflowservice.v1.PollNexusTaskQueueResponse
(*v1.RespondNexusTaskCompletedRequest)(nil), // 139: temporal.api.workflowservice.v1.RespondNexusTaskCompletedRequest
(*v1.RespondNexusTaskFailedRequest)(nil), // 140: temporal.api.workflowservice.v1.RespondNexusTaskFailedRequest
(*v111.NexusEndpointSpec)(nil), // 141: temporal.server.api.persistence.v1.NexusEndpointSpec
(*v111.NexusEndpointEntry)(nil), // 142: temporal.server.api.persistence.v1.NexusEndpointEntry
(*v1.RecordWorkerHeartbeatRequest)(nil), // 143: temporal.api.workflowservice.v1.RecordWorkerHeartbeatRequest
(*v1.ListWorkersRequest)(nil), // 144: temporal.api.workflowservice.v1.ListWorkersRequest
(*v115.WorkerInfo)(nil), // 145: temporal.api.worker.v1.WorkerInfo
(*v115.WorkerListInfo)(nil), // 146: temporal.api.worker.v1.WorkerListInfo
(*v1.UpdateTaskQueueConfigRequest)(nil), // 147: temporal.api.workflowservice.v1.UpdateTaskQueueConfigRequest
(*v14.TaskQueueConfig)(nil), // 148: temporal.api.taskqueue.v1.TaskQueueConfig
(*v1.DescribeWorkerRequest)(nil), // 149: temporal.api.workflowservice.v1.DescribeWorkerRequest
(v116.FairnessState)(0), // 150: temporal.server.api.enums.v1.FairnessState
(*v14.TaskQueueStats)(nil), // 151: temporal.api.taskqueue.v1.TaskQueueStats
(*v18.TaskQueueVersionInfoInternal)(nil), // 152: temporal.server.api.taskqueue.v1.TaskQueueVersionInfoInternal
(*v1.UpdateWorkerBuildIdCompatibilityRequest)(nil), // 153: temporal.api.workflowservice.v1.UpdateWorkerBuildIdCompatibilityRequest
(*v110.WorkerDeploymentVersionData)(nil), // 154: temporal.server.api.deployment.v1.WorkerDeploymentVersionData
(*v18.PartitionScaleInfo)(nil), // 121: temporal.server.api.taskqueue.v1.PartitionScaleInfo
(*v14.TaskQueuePartitionMetadata)(nil), // 122: temporal.api.taskqueue.v1.TaskQueuePartitionMetadata
(*v1.GetWorkerVersioningRulesRequest)(nil), // 123: temporal.api.workflowservice.v1.GetWorkerVersioningRulesRequest
(*v1.GetWorkerVersioningRulesResponse)(nil), // 124: temporal.api.workflowservice.v1.GetWorkerVersioningRulesResponse
(*v1.UpdateWorkerVersioningRulesRequest)(nil), // 125: temporal.api.workflowservice.v1.UpdateWorkerVersioningRulesRequest
(*v1.UpdateWorkerVersioningRulesResponse)(nil), // 126: temporal.api.workflowservice.v1.UpdateWorkerVersioningRulesResponse
(*v1.GetWorkerBuildIdCompatibilityRequest)(nil), // 127: temporal.api.workflowservice.v1.GetWorkerBuildIdCompatibilityRequest
(*v1.GetWorkerBuildIdCompatibilityResponse)(nil), // 128: temporal.api.workflowservice.v1.GetWorkerBuildIdCompatibilityResponse
(*v111.VersionedTaskQueueUserData)(nil), // 129: temporal.server.api.persistence.v1.VersionedTaskQueueUserData
(*v18.VersionedEphemeralData)(nil), // 130: temporal.server.api.taskqueue.v1.VersionedEphemeralData
(*v110.DeploymentVersionData)(nil), // 131: temporal.server.api.deployment.v1.DeploymentVersionData
(*v112.RoutingConfig)(nil), // 132: temporal.api.deployment.v1.RoutingConfig
(*v111.TaskQueueUserData)(nil), // 133: temporal.server.api.persistence.v1.TaskQueueUserData
(*v113.Request)(nil), // 134: temporal.api.nexus.v1.Request
(*v113.HandlerError)(nil), // 135: temporal.api.nexus.v1.HandlerError
(*v113.Response)(nil), // 136: temporal.api.nexus.v1.Response
(*v114.Failure)(nil), // 137: temporal.api.failure.v1.Failure
(*v1.PollNexusTaskQueueRequest)(nil), // 138: temporal.api.workflowservice.v1.PollNexusTaskQueueRequest
(*v1.PollNexusTaskQueueResponse)(nil), // 139: temporal.api.workflowservice.v1.PollNexusTaskQueueResponse
(*v1.RespondNexusTaskCompletedRequest)(nil), // 140: temporal.api.workflowservice.v1.RespondNexusTaskCompletedRequest
(*v1.RespondNexusTaskFailedRequest)(nil), // 141: temporal.api.workflowservice.v1.RespondNexusTaskFailedRequest
(*v111.NexusEndpointSpec)(nil), // 142: temporal.server.api.persistence.v1.NexusEndpointSpec
(*v111.NexusEndpointEntry)(nil), // 143: temporal.server.api.persistence.v1.NexusEndpointEntry
(*v1.RecordWorkerHeartbeatRequest)(nil), // 144: temporal.api.workflowservice.v1.RecordWorkerHeartbeatRequest
(*v1.ListWorkersRequest)(nil), // 145: temporal.api.workflowservice.v1.ListWorkersRequest
(*v115.WorkerInfo)(nil), // 146: temporal.api.worker.v1.WorkerInfo
(*v115.WorkerListInfo)(nil), // 147: temporal.api.worker.v1.WorkerListInfo
(*v1.UpdateTaskQueueConfigRequest)(nil), // 148: temporal.api.workflowservice.v1.UpdateTaskQueueConfigRequest
(*v14.TaskQueueConfig)(nil), // 149: temporal.api.taskqueue.v1.TaskQueueConfig
(*v1.DescribeWorkerRequest)(nil), // 150: temporal.api.workflowservice.v1.DescribeWorkerRequest
(v116.FairnessState)(0), // 151: temporal.server.api.enums.v1.FairnessState
(*v14.TaskQueueStats)(nil), // 152: temporal.api.taskqueue.v1.TaskQueueStats
(*v18.TaskQueueVersionInfoInternal)(nil), // 153: temporal.server.api.taskqueue.v1.TaskQueueVersionInfoInternal
(*v1.UpdateWorkerBuildIdCompatibilityRequest)(nil), // 154: temporal.api.workflowservice.v1.UpdateWorkerBuildIdCompatibilityRequest
(*v110.WorkerDeploymentVersionData)(nil), // 155: temporal.server.api.deployment.v1.WorkerDeploymentVersionData
}
var file_temporal_server_api_matchingservice_v1_request_response_proto_depIdxs = []int32{
92, // 0: temporal.server.api.matchingservice.v1.PollWorkflowTaskQueueRequest.poll_request:type_name -> temporal.api.workflowservice.v1.PollWorkflowTaskQueueRequest
@@ -6449,77 +6460,78 @@ var file_temporal_server_api_matchingservice_v1_request_response_proto_depIdxs =
119, // 77: temporal.server.api.matchingservice.v1.DescribeTaskQueuePartitionRequest.task_queue_partition:type_name -> temporal.server.api.taskqueue.v1.TaskQueuePartition
120, // 78: temporal.server.api.matchingservice.v1.DescribeTaskQueuePartitionRequest.versions:type_name -> temporal.api.taskqueue.v1.TaskQueueVersionSelection
87, // 79: temporal.server.api.matchingservice.v1.DescribeTaskQueuePartitionResponse.versions_info_internal:type_name -> temporal.server.api.matchingservice.v1.DescribeTaskQueuePartitionResponse.VersionsInfoInternalEntry
97, // 80: temporal.server.api.matchingservice.v1.ListTaskQueuePartitionsRequest.task_queue:type_name -> temporal.api.taskqueue.v1.TaskQueue
121, // 81: temporal.server.api.matchingservice.v1.ListTaskQueuePartitionsResponse.activity_task_queue_partitions:type_name -> temporal.api.taskqueue.v1.TaskQueuePartitionMetadata
121, // 82: temporal.server.api.matchingservice.v1.ListTaskQueuePartitionsResponse.workflow_task_queue_partitions:type_name -> temporal.api.taskqueue.v1.TaskQueuePartitionMetadata
88, // 83: temporal.server.api.matchingservice.v1.UpdateWorkerBuildIdCompatibilityRequest.apply_public_request:type_name -> temporal.server.api.matchingservice.v1.UpdateWorkerBuildIdCompatibilityRequest.ApplyPublicRequest
89, // 84: temporal.server.api.matchingservice.v1.UpdateWorkerBuildIdCompatibilityRequest.remove_build_ids:type_name -> temporal.server.api.matchingservice.v1.UpdateWorkerBuildIdCompatibilityRequest.RemoveBuildIds
122, // 85: temporal.server.api.matchingservice.v1.GetWorkerVersioningRulesRequest.request:type_name -> temporal.api.workflowservice.v1.GetWorkerVersioningRulesRequest
123, // 86: temporal.server.api.matchingservice.v1.GetWorkerVersioningRulesResponse.response:type_name -> temporal.api.workflowservice.v1.GetWorkerVersioningRulesResponse
124, // 87: temporal.server.api.matchingservice.v1.UpdateWorkerVersioningRulesRequest.request:type_name -> temporal.api.workflowservice.v1.UpdateWorkerVersioningRulesRequest
125, // 88: temporal.server.api.matchingservice.v1.UpdateWorkerVersioningRulesResponse.response:type_name -> temporal.api.workflowservice.v1.UpdateWorkerVersioningRulesResponse
126, // 89: temporal.server.api.matchingservice.v1.GetWorkerBuildIdCompatibilityRequest.request:type_name -> temporal.api.workflowservice.v1.GetWorkerBuildIdCompatibilityRequest
127, // 90: temporal.server.api.matchingservice.v1.GetWorkerBuildIdCompatibilityResponse.response:type_name -> temporal.api.workflowservice.v1.GetWorkerBuildIdCompatibilityResponse
115, // 91: temporal.server.api.matchingservice.v1.GetTaskQueueUserDataRequest.task_queue_type:type_name -> temporal.api.enums.v1.TaskQueueType
128, // 92: temporal.server.api.matchingservice.v1.GetTaskQueueUserDataResponse.user_data:type_name -> temporal.server.api.persistence.v1.VersionedTaskQueueUserData
129, // 93: temporal.server.api.matchingservice.v1.GetTaskQueueUserDataResponse.ephemeral_data:type_name -> temporal.server.api.taskqueue.v1.VersionedEphemeralData
115, // 94: temporal.server.api.matchingservice.v1.SyncDeploymentUserDataRequest.task_queue_types:type_name -> temporal.api.enums.v1.TaskQueueType
130, // 95: temporal.server.api.matchingservice.v1.SyncDeploymentUserDataRequest.update_version_data:type_name -> temporal.server.api.deployment.v1.DeploymentVersionData
117, // 96: temporal.server.api.matchingservice.v1.SyncDeploymentUserDataRequest.forget_version:type_name -> temporal.server.api.deployment.v1.WorkerDeploymentVersion
131, // 97: temporal.server.api.matchingservice.v1.SyncDeploymentUserDataRequest.update_routing_config:type_name -> temporal.api.deployment.v1.RoutingConfig
90, // 98: temporal.server.api.matchingservice.v1.SyncDeploymentUserDataRequest.upsert_versions_data:type_name -> temporal.server.api.matchingservice.v1.SyncDeploymentUserDataRequest.UpsertVersionsDataEntry
132, // 99: temporal.server.api.matchingservice.v1.ApplyTaskQueueUserDataReplicationEventRequest.user_data:type_name -> temporal.server.api.persistence.v1.TaskQueueUserData
119, // 100: temporal.server.api.matchingservice.v1.ForceLoadTaskQueuePartitionRequest.task_queue_partition:type_name -> temporal.server.api.taskqueue.v1.TaskQueuePartition
115, // 101: temporal.server.api.matchingservice.v1.ForceUnloadTaskQueueRequest.task_queue_type:type_name -> temporal.api.enums.v1.TaskQueueType
119, // 102: temporal.server.api.matchingservice.v1.ForceUnloadTaskQueuePartitionRequest.task_queue_partition:type_name -> temporal.server.api.taskqueue.v1.TaskQueuePartition
128, // 103: temporal.server.api.matchingservice.v1.UpdateTaskQueueUserDataRequest.user_data:type_name -> temporal.server.api.persistence.v1.VersionedTaskQueueUserData
132, // 104: temporal.server.api.matchingservice.v1.ReplicateTaskQueueUserDataRequest.user_data:type_name -> temporal.server.api.persistence.v1.TaskQueueUserData
97, // 105: temporal.server.api.matchingservice.v1.DispatchNexusTaskRequest.task_queue:type_name -> temporal.api.taskqueue.v1.TaskQueue
133, // 106: temporal.server.api.matchingservice.v1.DispatchNexusTaskRequest.request:type_name -> temporal.api.nexus.v1.Request
111, // 107: temporal.server.api.matchingservice.v1.DispatchNexusTaskRequest.forward_info:type_name -> temporal.server.api.taskqueue.v1.TaskForwardInfo
134, // 108: temporal.server.api.matchingservice.v1.DispatchNexusTaskResponse.handler_error:type_name -> temporal.api.nexus.v1.HandlerError
135, // 109: temporal.server.api.matchingservice.v1.DispatchNexusTaskResponse.response:type_name -> temporal.api.nexus.v1.Response
91, // 110: temporal.server.api.matchingservice.v1.DispatchNexusTaskResponse.request_timeout:type_name -> temporal.server.api.matchingservice.v1.DispatchNexusTaskResponse.Timeout
136, // 111: temporal.server.api.matchingservice.v1.DispatchNexusTaskResponse.failure:type_name -> temporal.api.failure.v1.Failure
137, // 112: temporal.server.api.matchingservice.v1.PollNexusTaskQueueRequest.request:type_name -> temporal.api.workflowservice.v1.PollNexusTaskQueueRequest
81, // 113: temporal.server.api.matchingservice.v1.PollNexusTaskQueueRequest.conditions:type_name -> temporal.server.api.matchingservice.v1.PollConditions
138, // 114: temporal.server.api.matchingservice.v1.PollNexusTaskQueueResponse.response:type_name -> temporal.api.workflowservice.v1.PollNexusTaskQueueResponse
97, // 115: temporal.server.api.matchingservice.v1.RespondNexusTaskCompletedRequest.task_queue:type_name -> temporal.api.taskqueue.v1.TaskQueue
139, // 116: temporal.server.api.matchingservice.v1.RespondNexusTaskCompletedRequest.request:type_name -> temporal.api.workflowservice.v1.RespondNexusTaskCompletedRequest
97, // 117: temporal.server.api.matchingservice.v1.RespondNexusTaskFailedRequest.task_queue:type_name -> temporal.api.taskqueue.v1.TaskQueue
140, // 118: temporal.server.api.matchingservice.v1.RespondNexusTaskFailedRequest.request:type_name -> temporal.api.workflowservice.v1.RespondNexusTaskFailedRequest
141, // 119: temporal.server.api.matchingservice.v1.CreateNexusEndpointRequest.spec:type_name -> temporal.server.api.persistence.v1.NexusEndpointSpec
142, // 120: temporal.server.api.matchingservice.v1.CreateNexusEndpointResponse.entry:type_name -> temporal.server.api.persistence.v1.NexusEndpointEntry
141, // 121: temporal.server.api.matchingservice.v1.UpdateNexusEndpointRequest.spec:type_name -> temporal.server.api.persistence.v1.NexusEndpointSpec
142, // 122: temporal.server.api.matchingservice.v1.UpdateNexusEndpointResponse.entry:type_name -> temporal.server.api.persistence.v1.NexusEndpointEntry
142, // 123: temporal.server.api.matchingservice.v1.ListNexusEndpointsResponse.entries:type_name -> temporal.server.api.persistence.v1.NexusEndpointEntry
143, // 124: temporal.server.api.matchingservice.v1.RecordWorkerHeartbeatRequest.heartbeart_request:type_name -> temporal.api.workflowservice.v1.RecordWorkerHeartbeatRequest
144, // 125: temporal.server.api.matchingservice.v1.ListWorkersRequest.list_request:type_name -> temporal.api.workflowservice.v1.ListWorkersRequest
145, // 126: temporal.server.api.matchingservice.v1.ListWorkersResponse.workers_info:type_name -> temporal.api.worker.v1.WorkerInfo
146, // 127: temporal.server.api.matchingservice.v1.ListWorkersResponse.workers:type_name -> temporal.api.worker.v1.WorkerListInfo
147, // 128: temporal.server.api.matchingservice.v1.UpdateTaskQueueConfigRequest.update_taskqueue_config:type_name -> temporal.api.workflowservice.v1.UpdateTaskQueueConfigRequest
148, // 129: temporal.server.api.matchingservice.v1.UpdateTaskQueueConfigResponse.updated_taskqueue_config:type_name -> temporal.api.taskqueue.v1.TaskQueueConfig
149, // 130: temporal.server.api.matchingservice.v1.DescribeWorkerRequest.request:type_name -> temporal.api.workflowservice.v1.DescribeWorkerRequest
145, // 131: temporal.server.api.matchingservice.v1.DescribeWorkerResponse.worker_info:type_name -> temporal.api.worker.v1.WorkerInfo
115, // 132: temporal.server.api.matchingservice.v1.UpdateFairnessStateRequest.task_queue_type:type_name -> temporal.api.enums.v1.TaskQueueType
150, // 133: temporal.server.api.matchingservice.v1.UpdateFairnessStateRequest.fairness_state:type_name -> temporal.server.api.enums.v1.FairnessState
115, // 134: temporal.server.api.matchingservice.v1.CheckTaskQueueVersionMembershipRequest.task_queue_type:type_name -> temporal.api.enums.v1.TaskQueueType
117, // 135: temporal.server.api.matchingservice.v1.CheckTaskQueueVersionMembershipRequest.version:type_name -> temporal.server.api.deployment.v1.WorkerDeploymentVersion
95, // 136: temporal.server.api.matchingservice.v1.PollWorkflowTaskQueueResponse.QueriesEntry.value:type_name -> temporal.api.query.v1.WorkflowQuery
95, // 137: temporal.server.api.matchingservice.v1.PollWorkflowTaskQueueResponseWithRawHistory.QueriesEntry.value:type_name -> temporal.api.query.v1.WorkflowQuery
115, // 138: temporal.server.api.matchingservice.v1.DescribeVersionedTaskQueuesRequest.VersionTaskQueue.type:type_name -> temporal.api.enums.v1.TaskQueueType
115, // 139: temporal.server.api.matchingservice.v1.DescribeVersionedTaskQueuesResponse.VersionTaskQueue.type:type_name -> temporal.api.enums.v1.TaskQueueType
151, // 140: temporal.server.api.matchingservice.v1.DescribeVersionedTaskQueuesResponse.VersionTaskQueue.stats:type_name -> temporal.api.taskqueue.v1.TaskQueueStats
86, // 141: temporal.server.api.matchingservice.v1.DescribeVersionedTaskQueuesResponse.VersionTaskQueue.stats_by_priority_key:type_name -> temporal.server.api.matchingservice.v1.DescribeVersionedTaskQueuesResponse.VersionTaskQueue.StatsByPriorityKeyEntry
151, // 142: temporal.server.api.matchingservice.v1.DescribeVersionedTaskQueuesResponse.VersionTaskQueue.StatsByPriorityKeyEntry.value:type_name -> temporal.api.taskqueue.v1.TaskQueueStats
152, // 143: temporal.server.api.matchingservice.v1.DescribeTaskQueuePartitionResponse.VersionsInfoInternalEntry.value:type_name -> temporal.server.api.taskqueue.v1.TaskQueueVersionInfoInternal
153, // 144: temporal.server.api.matchingservice.v1.UpdateWorkerBuildIdCompatibilityRequest.ApplyPublicRequest.request:type_name -> temporal.api.workflowservice.v1.UpdateWorkerBuildIdCompatibilityRequest
154, // 145: temporal.server.api.matchingservice.v1.SyncDeploymentUserDataRequest.UpsertVersionsDataEntry.value:type_name -> temporal.server.api.deployment.v1.WorkerDeploymentVersionData
146, // [146:146] is the sub-list for method output_type
146, // [146:146] is the sub-list for method input_type
146, // [146:146] is the sub-list for extension type_name
146, // [146:146] is the sub-list for extension extendee
0, // [0:146] is the sub-list for field type_name
121, // 80: temporal.server.api.matchingservice.v1.DescribeTaskQueuePartitionResponse.scale_info:type_name -> temporal.server.api.taskqueue.v1.PartitionScaleInfo
97, // 81: temporal.server.api.matchingservice.v1.ListTaskQueuePartitionsRequest.task_queue:type_name -> temporal.api.taskqueue.v1.TaskQueue
122, // 82: temporal.server.api.matchingservice.v1.ListTaskQueuePartitionsResponse.activity_task_queue_partitions:type_name -> temporal.api.taskqueue.v1.TaskQueuePartitionMetadata
122, // 83: temporal.server.api.matchingservice.v1.ListTaskQueuePartitionsResponse.workflow_task_queue_partitions:type_name -> temporal.api.taskqueue.v1.TaskQueuePartitionMetadata
88, // 84: temporal.server.api.matchingservice.v1.UpdateWorkerBuildIdCompatibilityRequest.apply_public_request:type_name -> temporal.server.api.matchingservice.v1.UpdateWorkerBuildIdCompatibilityRequest.ApplyPublicRequest
89, // 85: temporal.server.api.matchingservice.v1.UpdateWorkerBuildIdCompatibilityRequest.remove_build_ids:type_name -> temporal.server.api.matchingservice.v1.UpdateWorkerBuildIdCompatibilityRequest.RemoveBuildIds
123, // 86: temporal.server.api.matchingservice.v1.GetWorkerVersioningRulesRequest.request:type_name -> temporal.api.workflowservice.v1.GetWorkerVersioningRulesRequest
124, // 87: temporal.server.api.matchingservice.v1.GetWorkerVersioningRulesResponse.response:type_name -> temporal.api.workflowservice.v1.GetWorkerVersioningRulesResponse
125, // 88: temporal.server.api.matchingservice.v1.UpdateWorkerVersioningRulesRequest.request:type_name -> temporal.api.workflowservice.v1.UpdateWorkerVersioningRulesRequest
126, // 89: temporal.server.api.matchingservice.v1.UpdateWorkerVersioningRulesResponse.response:type_name -> temporal.api.workflowservice.v1.UpdateWorkerVersioningRulesResponse
127, // 90: temporal.server.api.matchingservice.v1.GetWorkerBuildIdCompatibilityRequest.request:type_name -> temporal.api.workflowservice.v1.GetWorkerBuildIdCompatibilityRequest
128, // 91: temporal.server.api.matchingservice.v1.GetWorkerBuildIdCompatibilityResponse.response:type_name -> temporal.api.workflowservice.v1.GetWorkerBuildIdCompatibilityResponse
115, // 92: temporal.server.api.matchingservice.v1.GetTaskQueueUserDataRequest.task_queue_type:type_name -> temporal.api.enums.v1.TaskQueueType
129, // 93: temporal.server.api.matchingservice.v1.GetTaskQueueUserDataResponse.user_data:type_name -> temporal.server.api.persistence.v1.VersionedTaskQueueUserData
130, // 94: temporal.server.api.matchingservice.v1.GetTaskQueueUserDataResponse.ephemeral_data:type_name -> temporal.server.api.taskqueue.v1.VersionedEphemeralData
115, // 95: temporal.server.api.matchingservice.v1.SyncDeploymentUserDataRequest.task_queue_types:type_name -> temporal.api.enums.v1.TaskQueueType
131, // 96: temporal.server.api.matchingservice.v1.SyncDeploymentUserDataRequest.update_version_data:type_name -> temporal.server.api.deployment.v1.DeploymentVersionData
117, // 97: temporal.server.api.matchingservice.v1.SyncDeploymentUserDataRequest.forget_version:type_name -> temporal.server.api.deployment.v1.WorkerDeploymentVersion
132, // 98: temporal.server.api.matchingservice.v1.SyncDeploymentUserDataRequest.update_routing_config:type_name -> temporal.api.deployment.v1.RoutingConfig
90, // 99: temporal.server.api.matchingservice.v1.SyncDeploymentUserDataRequest.upsert_versions_data:type_name -> temporal.server.api.matchingservice.v1.SyncDeploymentUserDataRequest.UpsertVersionsDataEntry
133, // 100: temporal.server.api.matchingservice.v1.ApplyTaskQueueUserDataReplicationEventRequest.user_data:type_name -> temporal.server.api.persistence.v1.TaskQueueUserData
119, // 101: temporal.server.api.matchingservice.v1.ForceLoadTaskQueuePartitionRequest.task_queue_partition:type_name -> temporal.server.api.taskqueue.v1.TaskQueuePartition
115, // 102: temporal.server.api.matchingservice.v1.ForceUnloadTaskQueueRequest.task_queue_type:type_name -> temporal.api.enums.v1.TaskQueueType
119, // 103: temporal.server.api.matchingservice.v1.ForceUnloadTaskQueuePartitionRequest.task_queue_partition:type_name -> temporal.server.api.taskqueue.v1.TaskQueuePartition
129, // 104: temporal.server.api.matchingservice.v1.UpdateTaskQueueUserDataRequest.user_data:type_name -> temporal.server.api.persistence.v1.VersionedTaskQueueUserData
133, // 105: temporal.server.api.matchingservice.v1.ReplicateTaskQueueUserDataRequest.user_data:type_name -> temporal.server.api.persistence.v1.TaskQueueUserData
97, // 106: temporal.server.api.matchingservice.v1.DispatchNexusTaskRequest.task_queue:type_name -> temporal.api.taskqueue.v1.TaskQueue
134, // 107: temporal.server.api.matchingservice.v1.DispatchNexusTaskRequest.request:type_name -> temporal.api.nexus.v1.Request
111, // 108: temporal.server.api.matchingservice.v1.DispatchNexusTaskRequest.forward_info:type_name -> temporal.server.api.taskqueue.v1.TaskForwardInfo
135, // 109: temporal.server.api.matchingservice.v1.DispatchNexusTaskResponse.handler_error:type_name -> temporal.api.nexus.v1.HandlerError
136, // 110: temporal.server.api.matchingservice.v1.DispatchNexusTaskResponse.response:type_name -> temporal.api.nexus.v1.Response
91, // 111: temporal.server.api.matchingservice.v1.DispatchNexusTaskResponse.request_timeout:type_name -> temporal.server.api.matchingservice.v1.DispatchNexusTaskResponse.Timeout
137, // 112: temporal.server.api.matchingservice.v1.DispatchNexusTaskResponse.failure:type_name -> temporal.api.failure.v1.Failure
138, // 113: temporal.server.api.matchingservice.v1.PollNexusTaskQueueRequest.request:type_name -> temporal.api.workflowservice.v1.PollNexusTaskQueueRequest
81, // 114: temporal.server.api.matchingservice.v1.PollNexusTaskQueueRequest.conditions:type_name -> temporal.server.api.matchingservice.v1.PollConditions
139, // 115: temporal.server.api.matchingservice.v1.PollNexusTaskQueueResponse.response:type_name -> temporal.api.workflowservice.v1.PollNexusTaskQueueResponse
97, // 116: temporal.server.api.matchingservice.v1.RespondNexusTaskCompletedRequest.task_queue:type_name -> temporal.api.taskqueue.v1.TaskQueue
140, // 117: temporal.server.api.matchingservice.v1.RespondNexusTaskCompletedRequest.request:type_name -> temporal.api.workflowservice.v1.RespondNexusTaskCompletedRequest
97, // 118: temporal.server.api.matchingservice.v1.RespondNexusTaskFailedRequest.task_queue:type_name -> temporal.api.taskqueue.v1.TaskQueue
141, // 119: temporal.server.api.matchingservice.v1.RespondNexusTaskFailedRequest.request:type_name -> temporal.api.workflowservice.v1.RespondNexusTaskFailedRequest
142, // 120: temporal.server.api.matchingservice.v1.CreateNexusEndpointRequest.spec:type_name -> temporal.server.api.persistence.v1.NexusEndpointSpec
143, // 121: temporal.server.api.matchingservice.v1.CreateNexusEndpointResponse.entry:type_name -> temporal.server.api.persistence.v1.NexusEndpointEntry
142, // 122: temporal.server.api.matchingservice.v1.UpdateNexusEndpointRequest.spec:type_name -> temporal.server.api.persistence.v1.NexusEndpointSpec
143, // 123: temporal.server.api.matchingservice.v1.UpdateNexusEndpointResponse.entry:type_name -> temporal.server.api.persistence.v1.NexusEndpointEntry
143, // 124: temporal.server.api.matchingservice.v1.ListNexusEndpointsResponse.entries:type_name -> temporal.server.api.persistence.v1.NexusEndpointEntry
144, // 125: temporal.server.api.matchingservice.v1.RecordWorkerHeartbeatRequest.heartbeart_request:type_name -> temporal.api.workflowservice.v1.RecordWorkerHeartbeatRequest
145, // 126: temporal.server.api.matchingservice.v1.ListWorkersRequest.list_request:type_name -> temporal.api.workflowservice.v1.ListWorkersRequest
146, // 127: temporal.server.api.matchingservice.v1.ListWorkersResponse.workers_info:type_name -> temporal.api.worker.v1.WorkerInfo
147, // 128: temporal.server.api.matchingservice.v1.ListWorkersResponse.workers:type_name -> temporal.api.worker.v1.WorkerListInfo
148, // 129: temporal.server.api.matchingservice.v1.UpdateTaskQueueConfigRequest.update_taskqueue_config:type_name -> temporal.api.workflowservice.v1.UpdateTaskQueueConfigRequest
149, // 130: temporal.server.api.matchingservice.v1.UpdateTaskQueueConfigResponse.updated_taskqueue_config:type_name -> temporal.api.taskqueue.v1.TaskQueueConfig
150, // 131: temporal.server.api.matchingservice.v1.DescribeWorkerRequest.request:type_name -> temporal.api.workflowservice.v1.DescribeWorkerRequest
146, // 132: temporal.server.api.matchingservice.v1.DescribeWorkerResponse.worker_info:type_name -> temporal.api.worker.v1.WorkerInfo
115, // 133: temporal.server.api.matchingservice.v1.UpdateFairnessStateRequest.task_queue_type:type_name -> temporal.api.enums.v1.TaskQueueType
151, // 134: temporal.server.api.matchingservice.v1.UpdateFairnessStateRequest.fairness_state:type_name -> temporal.server.api.enums.v1.FairnessState
115, // 135: temporal.server.api.matchingservice.v1.CheckTaskQueueVersionMembershipRequest.task_queue_type:type_name -> temporal.api.enums.v1.TaskQueueType
117, // 136: temporal.server.api.matchingservice.v1.CheckTaskQueueVersionMembershipRequest.version:type_name -> temporal.server.api.deployment.v1.WorkerDeploymentVersion
95, // 137: temporal.server.api.matchingservice.v1.PollWorkflowTaskQueueResponse.QueriesEntry.value:type_name -> temporal.api.query.v1.WorkflowQuery
95, // 138: temporal.server.api.matchingservice.v1.PollWorkflowTaskQueueResponseWithRawHistory.QueriesEntry.value:type_name -> temporal.api.query.v1.WorkflowQuery
115, // 139: temporal.server.api.matchingservice.v1.DescribeVersionedTaskQueuesRequest.VersionTaskQueue.type:type_name -> temporal.api.enums.v1.TaskQueueType
115, // 140: temporal.server.api.matchingservice.v1.DescribeVersionedTaskQueuesResponse.VersionTaskQueue.type:type_name -> temporal.api.enums.v1.TaskQueueType
152, // 141: temporal.server.api.matchingservice.v1.DescribeVersionedTaskQueuesResponse.VersionTaskQueue.stats:type_name -> temporal.api.taskqueue.v1.TaskQueueStats
86, // 142: temporal.server.api.matchingservice.v1.DescribeVersionedTaskQueuesResponse.VersionTaskQueue.stats_by_priority_key:type_name -> temporal.server.api.matchingservice.v1.DescribeVersionedTaskQueuesResponse.VersionTaskQueue.StatsByPriorityKeyEntry
152, // 143: temporal.server.api.matchingservice.v1.DescribeVersionedTaskQueuesResponse.VersionTaskQueue.StatsByPriorityKeyEntry.value:type_name -> temporal.api.taskqueue.v1.TaskQueueStats
153, // 144: temporal.server.api.matchingservice.v1.DescribeTaskQueuePartitionResponse.VersionsInfoInternalEntry.value:type_name -> temporal.server.api.taskqueue.v1.TaskQueueVersionInfoInternal
154, // 145: temporal.server.api.matchingservice.v1.UpdateWorkerBuildIdCompatibilityRequest.ApplyPublicRequest.request:type_name -> temporal.api.workflowservice.v1.UpdateWorkerBuildIdCompatibilityRequest
155, // 146: temporal.server.api.matchingservice.v1.SyncDeploymentUserDataRequest.UpsertVersionsDataEntry.value:type_name -> temporal.server.api.deployment.v1.WorkerDeploymentVersionData
147, // [147:147] is the sub-list for method output_type
147, // [147:147] is the sub-list for method input_type
147, // [147:147] is the sub-list for extension type_name
147, // [147:147] is the sub-list for extension extendee
0, // [0:147] is the sub-list for field type_name
}
func init() { file_temporal_server_api_matchingservice_v1_request_response_proto_init() }

View File

@@ -5,6 +5,43 @@ import (
"google.golang.org/protobuf/proto"
)
// Marshal an object of type TaskKey to the protobuf v3 wire format
func (val *TaskKey) Marshal() ([]byte, error) {
return proto.Marshal(val)
}
// Unmarshal an object of type TaskKey from the protobuf v3 wire format
func (val *TaskKey) Unmarshal(buf []byte) error {
return proto.Unmarshal(buf, val)
}
// Size returns the size of the object, in bytes, once serialized
func (val *TaskKey) Size() int {
return proto.Size(val)
}
// Equal returns whether two TaskKey values are equivalent by recursively
// comparing the message's fields.
// For more information see the documentation for
// https://pkg.go.dev/google.golang.org/protobuf/proto#Equal
func (this *TaskKey) Equal(that interface{}) bool {
if that == nil {
return this == nil
}
var that1 *TaskKey
switch t := that.(type) {
case *TaskKey:
that1 = t
case TaskKey:
that1 = &t
default:
return false
}
return proto.Equal(this, that1)
}
// Marshal an object of type QueueState to the protobuf v3 wire format
func (val *QueueState) Marshal() ([]byte, error) {
return proto.Marshal(val)

View File

@@ -14,6 +14,7 @@ import (
v1 "go.temporal.io/api/common/v1"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
timestamppb "google.golang.org/protobuf/types/known/timestamppb"
)
const (
@@ -23,6 +24,58 @@ const (
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type TaskKey struct {
state protoimpl.MessageState `protogen:"open.v1"`
FireTime *timestamppb.Timestamp `protobuf:"bytes,1,opt,name=fire_time,json=fireTime,proto3" json:"fire_time,omitempty"`
TaskId int64 `protobuf:"varint,2,opt,name=task_id,json=taskId,proto3" json:"task_id,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *TaskKey) Reset() {
*x = TaskKey{}
mi := &file_temporal_server_api_persistence_v1_queues_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *TaskKey) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*TaskKey) ProtoMessage() {}
func (x *TaskKey) ProtoReflect() protoreflect.Message {
mi := &file_temporal_server_api_persistence_v1_queues_proto_msgTypes[0]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use TaskKey.ProtoReflect.Descriptor instead.
func (*TaskKey) Descriptor() ([]byte, []int) {
return file_temporal_server_api_persistence_v1_queues_proto_rawDescGZIP(), []int{0}
}
func (x *TaskKey) GetFireTime() *timestamppb.Timestamp {
if x != nil {
return x.FireTime
}
return nil
}
func (x *TaskKey) GetTaskId() int64 {
if x != nil {
return x.TaskId
}
return 0
}
type QueueState struct {
state protoimpl.MessageState `protogen:"open.v1"`
ReaderStates map[int64]*QueueReaderState `protobuf:"bytes,1,rep,name=reader_states,json=readerStates,proto3" json:"reader_states,omitempty" protobuf_key:"varint,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
@@ -33,7 +86,7 @@ type QueueState struct {
func (x *QueueState) Reset() {
*x = QueueState{}
mi := &file_temporal_server_api_persistence_v1_queues_proto_msgTypes[0]
mi := &file_temporal_server_api_persistence_v1_queues_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -45,7 +98,7 @@ func (x *QueueState) String() string {
func (*QueueState) ProtoMessage() {}
func (x *QueueState) ProtoReflect() protoreflect.Message {
mi := &file_temporal_server_api_persistence_v1_queues_proto_msgTypes[0]
mi := &file_temporal_server_api_persistence_v1_queues_proto_msgTypes[1]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -58,7 +111,7 @@ func (x *QueueState) ProtoReflect() protoreflect.Message {
// Deprecated: Use QueueState.ProtoReflect.Descriptor instead.
func (*QueueState) Descriptor() ([]byte, []int) {
return file_temporal_server_api_persistence_v1_queues_proto_rawDescGZIP(), []int{0}
return file_temporal_server_api_persistence_v1_queues_proto_rawDescGZIP(), []int{1}
}
func (x *QueueState) GetReaderStates() map[int64]*QueueReaderState {
@@ -84,7 +137,7 @@ type QueueReaderState struct {
func (x *QueueReaderState) Reset() {
*x = QueueReaderState{}
mi := &file_temporal_server_api_persistence_v1_queues_proto_msgTypes[1]
mi := &file_temporal_server_api_persistence_v1_queues_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -96,7 +149,7 @@ func (x *QueueReaderState) String() string {
func (*QueueReaderState) ProtoMessage() {}
func (x *QueueReaderState) ProtoReflect() protoreflect.Message {
mi := &file_temporal_server_api_persistence_v1_queues_proto_msgTypes[1]
mi := &file_temporal_server_api_persistence_v1_queues_proto_msgTypes[2]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -109,7 +162,7 @@ func (x *QueueReaderState) ProtoReflect() protoreflect.Message {
// Deprecated: Use QueueReaderState.ProtoReflect.Descriptor instead.
func (*QueueReaderState) Descriptor() ([]byte, []int) {
return file_temporal_server_api_persistence_v1_queues_proto_rawDescGZIP(), []int{1}
return file_temporal_server_api_persistence_v1_queues_proto_rawDescGZIP(), []int{2}
}
func (x *QueueReaderState) GetScopes() []*QueueSliceScope {
@@ -129,7 +182,7 @@ type QueueSliceScope struct {
func (x *QueueSliceScope) Reset() {
*x = QueueSliceScope{}
mi := &file_temporal_server_api_persistence_v1_queues_proto_msgTypes[2]
mi := &file_temporal_server_api_persistence_v1_queues_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -141,7 +194,7 @@ func (x *QueueSliceScope) String() string {
func (*QueueSliceScope) ProtoMessage() {}
func (x *QueueSliceScope) ProtoReflect() protoreflect.Message {
mi := &file_temporal_server_api_persistence_v1_queues_proto_msgTypes[2]
mi := &file_temporal_server_api_persistence_v1_queues_proto_msgTypes[3]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -154,7 +207,7 @@ func (x *QueueSliceScope) ProtoReflect() protoreflect.Message {
// Deprecated: Use QueueSliceScope.ProtoReflect.Descriptor instead.
func (*QueueSliceScope) Descriptor() ([]byte, []int) {
return file_temporal_server_api_persistence_v1_queues_proto_rawDescGZIP(), []int{2}
return file_temporal_server_api_persistence_v1_queues_proto_rawDescGZIP(), []int{3}
}
func (x *QueueSliceScope) GetRange() *QueueSliceRange {
@@ -181,7 +234,7 @@ type QueueSliceRange struct {
func (x *QueueSliceRange) Reset() {
*x = QueueSliceRange{}
mi := &file_temporal_server_api_persistence_v1_queues_proto_msgTypes[3]
mi := &file_temporal_server_api_persistence_v1_queues_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -193,7 +246,7 @@ func (x *QueueSliceRange) String() string {
func (*QueueSliceRange) ProtoMessage() {}
func (x *QueueSliceRange) ProtoReflect() protoreflect.Message {
mi := &file_temporal_server_api_persistence_v1_queues_proto_msgTypes[3]
mi := &file_temporal_server_api_persistence_v1_queues_proto_msgTypes[4]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -206,7 +259,7 @@ func (x *QueueSliceRange) ProtoReflect() protoreflect.Message {
// Deprecated: Use QueueSliceRange.ProtoReflect.Descriptor instead.
func (*QueueSliceRange) Descriptor() ([]byte, []int) {
return file_temporal_server_api_persistence_v1_queues_proto_rawDescGZIP(), []int{3}
return file_temporal_server_api_persistence_v1_queues_proto_rawDescGZIP(), []int{4}
}
func (x *QueueSliceRange) GetInclusiveMin() *TaskKey {
@@ -232,7 +285,7 @@ type ReadQueueMessagesNextPageToken struct {
func (x *ReadQueueMessagesNextPageToken) Reset() {
*x = ReadQueueMessagesNextPageToken{}
mi := &file_temporal_server_api_persistence_v1_queues_proto_msgTypes[4]
mi := &file_temporal_server_api_persistence_v1_queues_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -244,7 +297,7 @@ func (x *ReadQueueMessagesNextPageToken) String() string {
func (*ReadQueueMessagesNextPageToken) ProtoMessage() {}
func (x *ReadQueueMessagesNextPageToken) ProtoReflect() protoreflect.Message {
mi := &file_temporal_server_api_persistence_v1_queues_proto_msgTypes[4]
mi := &file_temporal_server_api_persistence_v1_queues_proto_msgTypes[5]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -257,7 +310,7 @@ func (x *ReadQueueMessagesNextPageToken) ProtoReflect() protoreflect.Message {
// Deprecated: Use ReadQueueMessagesNextPageToken.ProtoReflect.Descriptor instead.
func (*ReadQueueMessagesNextPageToken) Descriptor() ([]byte, []int) {
return file_temporal_server_api_persistence_v1_queues_proto_rawDescGZIP(), []int{4}
return file_temporal_server_api_persistence_v1_queues_proto_rawDescGZIP(), []int{5}
}
func (x *ReadQueueMessagesNextPageToken) GetLastReadMessageId() int64 {
@@ -276,7 +329,7 @@ type ListQueuesNextPageToken struct {
func (x *ListQueuesNextPageToken) Reset() {
*x = ListQueuesNextPageToken{}
mi := &file_temporal_server_api_persistence_v1_queues_proto_msgTypes[5]
mi := &file_temporal_server_api_persistence_v1_queues_proto_msgTypes[6]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -288,7 +341,7 @@ func (x *ListQueuesNextPageToken) String() string {
func (*ListQueuesNextPageToken) ProtoMessage() {}
func (x *ListQueuesNextPageToken) ProtoReflect() protoreflect.Message {
mi := &file_temporal_server_api_persistence_v1_queues_proto_msgTypes[5]
mi := &file_temporal_server_api_persistence_v1_queues_proto_msgTypes[6]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -301,7 +354,7 @@ func (x *ListQueuesNextPageToken) ProtoReflect() protoreflect.Message {
// Deprecated: Use ListQueuesNextPageToken.ProtoReflect.Descriptor instead.
func (*ListQueuesNextPageToken) Descriptor() ([]byte, []int) {
return file_temporal_server_api_persistence_v1_queues_proto_rawDescGZIP(), []int{5}
return file_temporal_server_api_persistence_v1_queues_proto_rawDescGZIP(), []int{6}
}
func (x *ListQueuesNextPageToken) GetLastReadQueueNumber() int64 {
@@ -330,7 +383,7 @@ type HistoryTask struct {
func (x *HistoryTask) Reset() {
*x = HistoryTask{}
mi := &file_temporal_server_api_persistence_v1_queues_proto_msgTypes[6]
mi := &file_temporal_server_api_persistence_v1_queues_proto_msgTypes[7]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -342,7 +395,7 @@ func (x *HistoryTask) String() string {
func (*HistoryTask) ProtoMessage() {}
func (x *HistoryTask) ProtoReflect() protoreflect.Message {
mi := &file_temporal_server_api_persistence_v1_queues_proto_msgTypes[6]
mi := &file_temporal_server_api_persistence_v1_queues_proto_msgTypes[7]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -355,7 +408,7 @@ func (x *HistoryTask) ProtoReflect() protoreflect.Message {
// Deprecated: Use HistoryTask.ProtoReflect.Descriptor instead.
func (*HistoryTask) Descriptor() ([]byte, []int) {
return file_temporal_server_api_persistence_v1_queues_proto_rawDescGZIP(), []int{6}
return file_temporal_server_api_persistence_v1_queues_proto_rawDescGZIP(), []int{7}
}
func (x *HistoryTask) GetShardId() int32 {
@@ -392,7 +445,7 @@ type QueuePartition struct {
func (x *QueuePartition) Reset() {
*x = QueuePartition{}
mi := &file_temporal_server_api_persistence_v1_queues_proto_msgTypes[7]
mi := &file_temporal_server_api_persistence_v1_queues_proto_msgTypes[8]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -404,7 +457,7 @@ func (x *QueuePartition) String() string {
func (*QueuePartition) ProtoMessage() {}
func (x *QueuePartition) ProtoReflect() protoreflect.Message {
mi := &file_temporal_server_api_persistence_v1_queues_proto_msgTypes[7]
mi := &file_temporal_server_api_persistence_v1_queues_proto_msgTypes[8]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -417,7 +470,7 @@ func (x *QueuePartition) ProtoReflect() protoreflect.Message {
// Deprecated: Use QueuePartition.ProtoReflect.Descriptor instead.
func (*QueuePartition) Descriptor() ([]byte, []int) {
return file_temporal_server_api_persistence_v1_queues_proto_rawDescGZIP(), []int{7}
return file_temporal_server_api_persistence_v1_queues_proto_rawDescGZIP(), []int{8}
}
func (x *QueuePartition) GetMinMessageId() int64 {
@@ -437,7 +490,7 @@ type Queue struct {
func (x *Queue) Reset() {
*x = Queue{}
mi := &file_temporal_server_api_persistence_v1_queues_proto_msgTypes[8]
mi := &file_temporal_server_api_persistence_v1_queues_proto_msgTypes[9]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -449,7 +502,7 @@ func (x *Queue) String() string {
func (*Queue) ProtoMessage() {}
func (x *Queue) ProtoReflect() protoreflect.Message {
mi := &file_temporal_server_api_persistence_v1_queues_proto_msgTypes[8]
mi := &file_temporal_server_api_persistence_v1_queues_proto_msgTypes[9]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -462,7 +515,7 @@ func (x *Queue) ProtoReflect() protoreflect.Message {
// Deprecated: Use Queue.ProtoReflect.Descriptor instead.
func (*Queue) Descriptor() ([]byte, []int) {
return file_temporal_server_api_persistence_v1_queues_proto_rawDescGZIP(), []int{8}
return file_temporal_server_api_persistence_v1_queues_proto_rawDescGZIP(), []int{9}
}
func (x *Queue) GetPartitions() map[int32]*QueuePartition {
@@ -476,7 +529,10 @@ var File_temporal_server_api_persistence_v1_queues_proto protoreflect.FileDescri
const file_temporal_server_api_persistence_v1_queues_proto_rawDesc = "" +
"\n" +
"/temporal/server/api/persistence/v1/queues.proto\x12\"temporal.server.api.persistence.v1\x1a$temporal/api/common/v1/message.proto\x1a3temporal/server/api/persistence/v1/predicates.proto\x1a.temporal/server/api/persistence/v1/tasks.proto\"\xde\x02\n" +
"/temporal/server/api/persistence/v1/queues.proto\x12\"temporal.server.api.persistence.v1\x1a\x1fgoogle/protobuf/timestamp.proto\x1a$temporal/api/common/v1/message.proto\x1a3temporal/server/api/persistence/v1/predicates.proto\"[\n" +
"\aTaskKey\x127\n" +
"\tfire_time\x18\x01 \x01(\v2\x1a.google.protobuf.TimestampR\bfireTime\x12\x17\n" +
"\atask_id\x18\x02 \x01(\x03R\x06taskId\"\xde\x02\n" +
"\n" +
"QueueState\x12e\n" +
"\rreader_states\x18\x01 \x03(\v2@.temporal.server.api.persistence.v1.QueueState.ReaderStatesEntryR\freaderStates\x12r\n" +
@@ -521,40 +577,42 @@ func file_temporal_server_api_persistence_v1_queues_proto_rawDescGZIP() []byte {
return file_temporal_server_api_persistence_v1_queues_proto_rawDescData
}
var file_temporal_server_api_persistence_v1_queues_proto_msgTypes = make([]protoimpl.MessageInfo, 11)
var file_temporal_server_api_persistence_v1_queues_proto_msgTypes = make([]protoimpl.MessageInfo, 12)
var file_temporal_server_api_persistence_v1_queues_proto_goTypes = []any{
(*QueueState)(nil), // 0: temporal.server.api.persistence.v1.QueueState
(*QueueReaderState)(nil), // 1: temporal.server.api.persistence.v1.QueueReaderState
(*QueueSliceScope)(nil), // 2: temporal.server.api.persistence.v1.QueueSliceScope
(*QueueSliceRange)(nil), // 3: temporal.server.api.persistence.v1.QueueSliceRange
(*ReadQueueMessagesNextPageToken)(nil), // 4: temporal.server.api.persistence.v1.ReadQueueMessagesNextPageToken
(*ListQueuesNextPageToken)(nil), // 5: temporal.server.api.persistence.v1.ListQueuesNextPageToken
(*HistoryTask)(nil), // 6: temporal.server.api.persistence.v1.HistoryTask
(*QueuePartition)(nil), // 7: temporal.server.api.persistence.v1.QueuePartition
(*Queue)(nil), // 8: temporal.server.api.persistence.v1.Queue
nil, // 9: temporal.server.api.persistence.v1.QueueState.ReaderStatesEntry
nil, // 10: temporal.server.api.persistence.v1.Queue.PartitionsEntry
(*TaskKey)(nil), // 11: temporal.server.api.persistence.v1.TaskKey
(*Predicate)(nil), // 12: temporal.server.api.persistence.v1.Predicate
(*v1.DataBlob)(nil), // 13: temporal.api.common.v1.DataBlob
(*TaskKey)(nil), // 0: temporal.server.api.persistence.v1.TaskKey
(*QueueState)(nil), // 1: temporal.server.api.persistence.v1.QueueState
(*QueueReaderState)(nil), // 2: temporal.server.api.persistence.v1.QueueReaderState
(*QueueSliceScope)(nil), // 3: temporal.server.api.persistence.v1.QueueSliceScope
(*QueueSliceRange)(nil), // 4: temporal.server.api.persistence.v1.QueueSliceRange
(*ReadQueueMessagesNextPageToken)(nil), // 5: temporal.server.api.persistence.v1.ReadQueueMessagesNextPageToken
(*ListQueuesNextPageToken)(nil), // 6: temporal.server.api.persistence.v1.ListQueuesNextPageToken
(*HistoryTask)(nil), // 7: temporal.server.api.persistence.v1.HistoryTask
(*QueuePartition)(nil), // 8: temporal.server.api.persistence.v1.QueuePartition
(*Queue)(nil), // 9: temporal.server.api.persistence.v1.Queue
nil, // 10: temporal.server.api.persistence.v1.QueueState.ReaderStatesEntry
nil, // 11: temporal.server.api.persistence.v1.Queue.PartitionsEntry
(*timestamppb.Timestamp)(nil), // 12: google.protobuf.Timestamp
(*Predicate)(nil), // 13: temporal.server.api.persistence.v1.Predicate
(*v1.DataBlob)(nil), // 14: temporal.api.common.v1.DataBlob
}
var file_temporal_server_api_persistence_v1_queues_proto_depIdxs = []int32{
9, // 0: temporal.server.api.persistence.v1.QueueState.reader_states:type_name -> temporal.server.api.persistence.v1.QueueState.ReaderStatesEntry
11, // 1: temporal.server.api.persistence.v1.QueueState.exclusive_reader_high_watermark:type_name -> temporal.server.api.persistence.v1.TaskKey
2, // 2: temporal.server.api.persistence.v1.QueueReaderState.scopes:type_name -> temporal.server.api.persistence.v1.QueueSliceScope
3, // 3: temporal.server.api.persistence.v1.QueueSliceScope.range:type_name -> temporal.server.api.persistence.v1.QueueSliceRange
12, // 4: temporal.server.api.persistence.v1.QueueSliceScope.predicate:type_name -> temporal.server.api.persistence.v1.Predicate
11, // 5: temporal.server.api.persistence.v1.QueueSliceRange.inclusive_min:type_name -> temporal.server.api.persistence.v1.TaskKey
11, // 6: temporal.server.api.persistence.v1.QueueSliceRange.exclusive_max:type_name -> temporal.server.api.persistence.v1.TaskKey
13, // 7: temporal.server.api.persistence.v1.HistoryTask.blob:type_name -> temporal.api.common.v1.DataBlob
10, // 8: temporal.server.api.persistence.v1.Queue.partitions:type_name -> temporal.server.api.persistence.v1.Queue.PartitionsEntry
1, // 9: temporal.server.api.persistence.v1.QueueState.ReaderStatesEntry.value:type_name -> temporal.server.api.persistence.v1.QueueReaderState
7, // 10: temporal.server.api.persistence.v1.Queue.PartitionsEntry.value:type_name -> temporal.server.api.persistence.v1.QueuePartition
11, // [11:11] is the sub-list for method output_type
11, // [11:11] is the sub-list for method input_type
11, // [11:11] is the sub-list for extension type_name
11, // [11:11] is the sub-list for extension extendee
0, // [0:11] is the sub-list for field type_name
12, // 0: temporal.server.api.persistence.v1.TaskKey.fire_time:type_name -> google.protobuf.Timestamp
10, // 1: temporal.server.api.persistence.v1.QueueState.reader_states:type_name -> temporal.server.api.persistence.v1.QueueState.ReaderStatesEntry
0, // 2: temporal.server.api.persistence.v1.QueueState.exclusive_reader_high_watermark:type_name -> temporal.server.api.persistence.v1.TaskKey
3, // 3: temporal.server.api.persistence.v1.QueueReaderState.scopes:type_name -> temporal.server.api.persistence.v1.QueueSliceScope
4, // 4: temporal.server.api.persistence.v1.QueueSliceScope.range:type_name -> temporal.server.api.persistence.v1.QueueSliceRange
13, // 5: temporal.server.api.persistence.v1.QueueSliceScope.predicate:type_name -> temporal.server.api.persistence.v1.Predicate
0, // 6: temporal.server.api.persistence.v1.QueueSliceRange.inclusive_min:type_name -> temporal.server.api.persistence.v1.TaskKey
0, // 7: temporal.server.api.persistence.v1.QueueSliceRange.exclusive_max:type_name -> temporal.server.api.persistence.v1.TaskKey
14, // 8: temporal.server.api.persistence.v1.HistoryTask.blob:type_name -> temporal.api.common.v1.DataBlob
11, // 9: temporal.server.api.persistence.v1.Queue.partitions:type_name -> temporal.server.api.persistence.v1.Queue.PartitionsEntry
2, // 10: temporal.server.api.persistence.v1.QueueState.ReaderStatesEntry.value:type_name -> temporal.server.api.persistence.v1.QueueReaderState
8, // 11: temporal.server.api.persistence.v1.Queue.PartitionsEntry.value:type_name -> temporal.server.api.persistence.v1.QueuePartition
12, // [12:12] is the sub-list for method output_type
12, // [12:12] is the sub-list for method input_type
12, // [12:12] is the sub-list for extension type_name
12, // [12:12] is the sub-list for extension extendee
0, // [0:12] is the sub-list for field type_name
}
func init() { file_temporal_server_api_persistence_v1_queues_proto_init() }
@@ -563,14 +621,13 @@ func file_temporal_server_api_persistence_v1_queues_proto_init() {
return
}
file_temporal_server_api_persistence_v1_predicates_proto_init()
file_temporal_server_api_persistence_v1_tasks_proto_init()
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_temporal_server_api_persistence_v1_queues_proto_rawDesc), len(file_temporal_server_api_persistence_v1_queues_proto_rawDesc)),
NumEnums: 0,
NumMessages: 11,
NumMessages: 12,
NumExtensions: 0,
NumServices: 0,
},

View File

@@ -227,35 +227,35 @@ func (this *SubqueueKey) Equal(that interface{}) bool {
return proto.Equal(this, that1)
}
// Marshal an object of type TaskKey to the protobuf v3 wire format
func (val *TaskKey) Marshal() ([]byte, error) {
// Marshal an object of type PartitionScaleState to the protobuf v3 wire format
func (val *PartitionScaleState) Marshal() ([]byte, error) {
return proto.Marshal(val)
}
// Unmarshal an object of type TaskKey from the protobuf v3 wire format
func (val *TaskKey) Unmarshal(buf []byte) error {
// Unmarshal an object of type PartitionScaleState from the protobuf v3 wire format
func (val *PartitionScaleState) Unmarshal(buf []byte) error {
return proto.Unmarshal(buf, val)
}
// Size returns the size of the object, in bytes, once serialized
func (val *TaskKey) Size() int {
func (val *PartitionScaleState) Size() int {
return proto.Size(val)
}
// Equal returns whether two TaskKey values are equivalent by recursively
// Equal returns whether two PartitionScaleState values are equivalent by recursively
// comparing the message's fields.
// For more information see the documentation for
// https://pkg.go.dev/google.golang.org/protobuf/proto#Equal
func (this *TaskKey) Equal(that interface{}) bool {
func (this *PartitionScaleState) Equal(that interface{}) bool {
if that == nil {
return this == nil
}
var that1 *TaskKey
var that1 *PartitionScaleState
switch t := that.(type) {
case *TaskKey:
case *PartitionScaleState:
that1 = t
case TaskKey:
case PartitionScaleState:
that1 = &t
default:
return false

View File

@@ -17,6 +17,7 @@ import (
v11 "go.temporal.io/server/api/taskqueue/v1"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
anypb "google.golang.org/protobuf/types/known/anypb"
timestamppb "google.golang.org/protobuf/types/known/timestamppb"
)
@@ -252,8 +253,10 @@ type TaskQueueInfo struct {
// After determinining that the inactive table has no more tasks left, then this
// can be cleared on the active table.
OtherHasTasks bool `protobuf:"varint,10,opt,name=other_has_tasks,json=otherHasTasks,proto3" json:"other_has_tasks,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
// Dynamic partition scaling state (root only):
PartitionScaleState *PartitionScaleState `protobuf:"bytes,11,opt,name=partition_scale_state,json=partitionScaleState,proto3" json:"partition_scale_state,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *TaskQueueInfo) Reset() {
@@ -356,6 +359,13 @@ func (x *TaskQueueInfo) GetOtherHasTasks() bool {
return false
}
func (x *TaskQueueInfo) GetPartitionScaleState() *PartitionScaleState {
if x != nil {
return x.PartitionScaleState
}
return nil
}
type SubqueueInfo struct {
state protoimpl.MessageState `protogen:"open.v1"`
// Key is the information used by a splitting algorithm to decide which tasks should go in
@@ -544,28 +554,43 @@ func (x *SubqueueKey) GetPriority() int32 {
return 0
}
type TaskKey struct {
state protoimpl.MessageState `protogen:"open.v1"`
FireTime *timestamppb.Timestamp `protobuf:"bytes,1,opt,name=fire_time,json=fireTime,proto3" json:"fire_time,omitempty"`
TaskId int64 `protobuf:"varint,2,opt,name=task_id,json=taskId,proto3" json:"task_id,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
type PartitionScaleState struct {
state protoimpl.MessageState `protogen:"open.v1"`
// Target number of partitions. Zero means managed scaling is not enabled.
Target int32 `protobuf:"varint,1,opt,name=target,proto3" json:"target,omitempty"`
// Highest value that target has ever been set to.
MaxTarget int32 `protobuf:"varint,2,opt,name=max_target,json=maxTarget,proto3" json:"max_target,omitempty"`
// Version number for target. This should change every time target changes.
TargetVersion int64 `protobuf:"fixed64,3,opt,name=target_version,json=targetVersion,proto3" json:"target_version,omitempty"`
// Bit field of partitions that may have backlog. Partition i is represented by:
// backlog_state[i/64] & (1 << i%64).
// (-- api-linter: core::0141::forbidden-types=disabled
//
// aip.dev/not-precedent: This is a bit field --)
BacklogState []uint64 `protobuf:"varint,4,rep,packed,name=backlog_state,json=backlogState,proto3" json:"backlog_state,omitempty"`
// Arbitrary state kept by the scaler implementation.
// (-- api-linter: core::0146::any=disabled
//
// aip.dev/not-precedent: This is not public. --)
PrivateScalerState *anypb.Any `protobuf:"bytes,100,opt,name=private_scaler_state,json=privateScalerState,proto3" json:"private_scaler_state,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *TaskKey) Reset() {
*x = TaskKey{}
func (x *PartitionScaleState) Reset() {
*x = PartitionScaleState{}
mi := &file_temporal_server_api_persistence_v1_tasks_proto_msgTypes[6]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *TaskKey) String() string {
func (x *PartitionScaleState) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*TaskKey) ProtoMessage() {}
func (*PartitionScaleState) ProtoMessage() {}
func (x *TaskKey) ProtoReflect() protoreflect.Message {
func (x *PartitionScaleState) ProtoReflect() protoreflect.Message {
mi := &file_temporal_server_api_persistence_v1_tasks_proto_msgTypes[6]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
@@ -577,30 +602,51 @@ func (x *TaskKey) ProtoReflect() protoreflect.Message {
return mi.MessageOf(x)
}
// Deprecated: Use TaskKey.ProtoReflect.Descriptor instead.
func (*TaskKey) Descriptor() ([]byte, []int) {
// Deprecated: Use PartitionScaleState.ProtoReflect.Descriptor instead.
func (*PartitionScaleState) Descriptor() ([]byte, []int) {
return file_temporal_server_api_persistence_v1_tasks_proto_rawDescGZIP(), []int{6}
}
func (x *TaskKey) GetFireTime() *timestamppb.Timestamp {
func (x *PartitionScaleState) GetTarget() int32 {
if x != nil {
return x.FireTime
return x.Target
}
return 0
}
func (x *PartitionScaleState) GetMaxTarget() int32 {
if x != nil {
return x.MaxTarget
}
return 0
}
func (x *PartitionScaleState) GetTargetVersion() int64 {
if x != nil {
return x.TargetVersion
}
return 0
}
func (x *PartitionScaleState) GetBacklogState() []uint64 {
if x != nil {
return x.BacklogState
}
return nil
}
func (x *TaskKey) GetTaskId() int64 {
func (x *PartitionScaleState) GetPrivateScalerState() *anypb.Any {
if x != nil {
return x.TaskId
return x.PrivateScalerState
}
return 0
return nil
}
var File_temporal_server_api_persistence_v1_tasks_proto protoreflect.FileDescriptor
const file_temporal_server_api_persistence_v1_tasks_proto_rawDesc = "" +
"\n" +
".temporal/server/api/persistence/v1/tasks.proto\x12\"temporal.server.api.persistence.v1\x1a\x1fgoogle/protobuf/timestamp.proto\x1a$temporal/api/common/v1/message.proto\x1a&temporal/api/enums/v1/task_queue.proto\x1a*temporal/server/api/clock/v1/message.proto\x1a.temporal/server/api/taskqueue/v1/message.proto\"\x8b\x01\n" +
".temporal/server/api/persistence/v1/tasks.proto\x12\"temporal.server.api.persistence.v1\x1a\x19google/protobuf/any.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a$temporal/api/common/v1/message.proto\x1a&temporal/api/enums/v1/task_queue.proto\x1a*temporal/server/api/clock/v1/message.proto\x1a.temporal/server/api/taskqueue/v1/message.proto\"\x8b\x01\n" +
"\x11AllocatedTaskInfo\x12@\n" +
"\x04data\x18\x01 \x01(\v2,.temporal.server.api.persistence.v1.TaskInfoR\x04data\x12\x1b\n" +
"\ttask_pass\x18\x03 \x01(\x03R\btaskPass\x12\x17\n" +
@@ -620,7 +666,7 @@ const file_temporal_server_api_persistence_v1_tasks_proto_rawDesc = "" +
"\x05stamp\x18\t \x01(\x05R\x05stamp\x12<\n" +
"\bpriority\x18\n" +
" \x01(\v2 .temporal.api.common.v1.PriorityR\bpriority\x12#\n" +
"\rcomponent_ref\x18\v \x01(\fR\fcomponentRef\"\x97\x04\n" +
"\rcomponent_ref\x18\v \x01(\fR\fcomponentRef\"\x84\x05\n" +
"\rTaskQueueInfo\x12!\n" +
"\fnamespace_id\x18\x01 \x01(\tR\vnamespaceId\x12\x12\n" +
"\x04name\x18\x02 \x01(\tR\x04name\x12A\n" +
@@ -633,7 +679,8 @@ const file_temporal_server_api_persistence_v1_tasks_proto_rawDesc = "" +
"\x19approximate_backlog_count\x18\b \x01(\x03R\x17approximateBacklogCount\x12N\n" +
"\tsubqueues\x18\t \x03(\v20.temporal.server.api.persistence.v1.SubqueueInfoR\tsubqueues\x12&\n" +
"\x0fother_has_tasks\x18\n" +
" \x01(\bR\rotherHasTasks\"\xc2\x03\n" +
" \x01(\bR\rotherHasTasks\x12k\n" +
"\x15partition_scale_state\x18\v \x01(\v27.temporal.server.api.persistence.v1.PartitionScaleStateR\x13partitionScaleState\"\xc2\x03\n" +
"\fSubqueueInfo\x12A\n" +
"\x03key\x18\x01 \x01(\v2/.temporal.server.api.persistence.v1.SubqueueKeyR\x03key\x12\x1b\n" +
"\tack_level\x18\x02 \x01(\x03R\backLevel\x12Q\n" +
@@ -645,10 +692,14 @@ const file_temporal_server_api_persistence_v1_tasks_proto_rawDesc = "" +
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
"\x05count\x18\x02 \x01(\x03R\x05count\")\n" +
"\vSubqueueKey\x12\x1a\n" +
"\bpriority\x18\x01 \x01(\x05R\bpriority\"[\n" +
"\aTaskKey\x127\n" +
"\tfire_time\x18\x01 \x01(\v2\x1a.google.protobuf.TimestampR\bfireTime\x12\x17\n" +
"\atask_id\x18\x02 \x01(\x03R\x06taskIdB6Z4go.temporal.io/server/api/persistence/v1;persistenceb\x06proto3"
"\bpriority\x18\x01 \x01(\x05R\bpriority\"\xe0\x01\n" +
"\x13PartitionScaleState\x12\x16\n" +
"\x06target\x18\x01 \x01(\x05R\x06target\x12\x1d\n" +
"\n" +
"max_target\x18\x02 \x01(\x05R\tmaxTarget\x12%\n" +
"\x0etarget_version\x18\x03 \x01(\x10R\rtargetVersion\x12#\n" +
"\rbacklog_state\x18\x04 \x03(\x04R\fbacklogState\x12F\n" +
"\x14private_scaler_state\x18d \x01(\v2\x14.google.protobuf.AnyR\x12privateScalerStateB6Z4go.temporal.io/server/api/persistence/v1;persistenceb\x06proto3"
var (
file_temporal_server_api_persistence_v1_tasks_proto_rawDescOnce sync.Once
@@ -670,7 +721,7 @@ var file_temporal_server_api_persistence_v1_tasks_proto_goTypes = []any{
(*SubqueueInfo)(nil), // 3: temporal.server.api.persistence.v1.SubqueueInfo
(*FairnessKeyCount)(nil), // 4: temporal.server.api.persistence.v1.FairnessKeyCount
(*SubqueueKey)(nil), // 5: temporal.server.api.persistence.v1.SubqueueKey
(*TaskKey)(nil), // 6: temporal.server.api.persistence.v1.TaskKey
(*PartitionScaleState)(nil), // 6: temporal.server.api.persistence.v1.PartitionScaleState
(*timestamppb.Timestamp)(nil), // 7: google.protobuf.Timestamp
(*v1.VectorClock)(nil), // 8: temporal.server.api.clock.v1.VectorClock
(*v11.TaskVersionDirective)(nil), // 9: temporal.server.api.taskqueue.v1.TaskVersionDirective
@@ -678,6 +729,7 @@ var file_temporal_server_api_persistence_v1_tasks_proto_goTypes = []any{
(v13.TaskQueueType)(0), // 11: temporal.api.enums.v1.TaskQueueType
(v13.TaskQueueKind)(0), // 12: temporal.api.enums.v1.TaskQueueKind
(*v11.FairLevel)(nil), // 13: temporal.server.api.taskqueue.v1.FairLevel
(*anypb.Any)(nil), // 14: google.protobuf.Any
}
var file_temporal_server_api_persistence_v1_tasks_proto_depIdxs = []int32{
1, // 0: temporal.server.api.persistence.v1.AllocatedTaskInfo.data:type_name -> temporal.server.api.persistence.v1.TaskInfo
@@ -691,16 +743,17 @@ var file_temporal_server_api_persistence_v1_tasks_proto_depIdxs = []int32{
7, // 8: temporal.server.api.persistence.v1.TaskQueueInfo.expiry_time:type_name -> google.protobuf.Timestamp
7, // 9: temporal.server.api.persistence.v1.TaskQueueInfo.last_update_time:type_name -> google.protobuf.Timestamp
3, // 10: temporal.server.api.persistence.v1.TaskQueueInfo.subqueues:type_name -> temporal.server.api.persistence.v1.SubqueueInfo
5, // 11: temporal.server.api.persistence.v1.SubqueueInfo.key:type_name -> temporal.server.api.persistence.v1.SubqueueKey
13, // 12: temporal.server.api.persistence.v1.SubqueueInfo.fair_ack_level:type_name -> temporal.server.api.taskqueue.v1.FairLevel
13, // 13: temporal.server.api.persistence.v1.SubqueueInfo.fair_max_read_level:type_name -> temporal.server.api.taskqueue.v1.FairLevel
4, // 14: temporal.server.api.persistence.v1.SubqueueInfo.top_k_fairness_counts:type_name -> temporal.server.api.persistence.v1.FairnessKeyCount
7, // 15: temporal.server.api.persistence.v1.TaskKey.fire_time:type_name -> google.protobuf.Timestamp
16, // [16:16] is the sub-list for method output_type
16, // [16:16] is the sub-list for method input_type
16, // [16:16] is the sub-list for extension type_name
16, // [16:16] is the sub-list for extension extendee
0, // [0:16] is the sub-list for field type_name
6, // 11: temporal.server.api.persistence.v1.TaskQueueInfo.partition_scale_state:type_name -> temporal.server.api.persistence.v1.PartitionScaleState
5, // 12: temporal.server.api.persistence.v1.SubqueueInfo.key:type_name -> temporal.server.api.persistence.v1.SubqueueKey
13, // 13: temporal.server.api.persistence.v1.SubqueueInfo.fair_ack_level:type_name -> temporal.server.api.taskqueue.v1.FairLevel
13, // 14: temporal.server.api.persistence.v1.SubqueueInfo.fair_max_read_level:type_name -> temporal.server.api.taskqueue.v1.FairLevel
4, // 15: temporal.server.api.persistence.v1.SubqueueInfo.top_k_fairness_counts:type_name -> temporal.server.api.persistence.v1.FairnessKeyCount
14, // 16: temporal.server.api.persistence.v1.PartitionScaleState.private_scaler_state:type_name -> google.protobuf.Any
17, // [17:17] is the sub-list for method output_type
17, // [17:17] is the sub-list for method input_type
17, // [17:17] is the sub-list for extension type_name
17, // [17:17] is the sub-list for extension extendee
0, // [0:17] is the sub-list for field type_name
}
func init() { file_temporal_server_api_persistence_v1_tasks_proto_init() }

View File

@@ -29,10 +29,11 @@ func (pc PartitionCounts) Valid() bool {
}
func (pc PartitionCounts) encode() (string, error) {
b, err := proto.Marshal(&taskqueuespb.ClientPartitionCounts{
cpc := taskqueuespb.ClientPartitionCounts{
Read: pc.Read,
Write: pc.Write,
})
}
b, err := proto.Marshal(&cpc)
if err != nil {
return "", err
}
@@ -55,6 +56,10 @@ func (pc PartitionCounts) SetTrailer(ctx context.Context) error {
return grpc.SetTrailer(ctx, metadata.Pairs(partitionCountsTrailerName, v))
}
func (pc PartitionCounts) Equal(other PartitionCounts) bool {
return pc.Read == other.Read && pc.Write == other.Write
}
func parsePartitionCounts(hdr string) (PartitionCounts, error) {
var cpc taskqueuespb.ClientPartitionCounts
err := proto.Unmarshal([]byte(hdr), &cpc)
@@ -120,7 +125,7 @@ func invokeWithPartitionCounts[Req, Res any](
logger.Info("partition count trailer parse error", tag.Error(parseErr))
// continue with zero value for newPc
}
if newPc != pc {
if !newPc.Equal(pc) {
cache.put(pkey, newPc)
pc = newPc
}

View File

@@ -1536,6 +1536,16 @@ default as namespace cardinality can be high and this requires a metrics collect
},
`How far off client partition scale values have to be to reject RPCs.`,
)
MatchingPartitionScaleManager = NewTaskQueueTypedSetting(
"matching.partitionScaleManager",
PartitionScaleManagerSettings{
MaxRate: 0.33,
BatchSize: 100,
BackgroundInterval: 23 * time.Second,
DrainBufferTime: 15 * time.Second,
},
`Settings for partition scale manager.`,
)
// Worker registry settings
MatchingWorkerRegistryNumBuckets = NewGlobalIntSetting(

View File

@@ -128,3 +128,21 @@ type PartitionScaleAllowedDrift struct {
Delta int32
Ratio float32
}
type PartitionScaleManagerSettings struct {
// MaxRate limits scale change frequency.
MaxRate float32
// BatchSize is the size of a batch to send to the partition scaler. (Needs task queue
// reload.)
BatchSize int32
// BackgroundInterval is the interval for background work:
// - send signals to the scaler even if not a full batch of tasks has been received yet
// - check drained partition state
BackgroundInterval time.Duration
// DrainBufferTime is how long to wait until after scaling down before we can consider
// draining queues. It's needed because there's a tiny window where tasks may be written
// after a scale down, since draining state is only checked at the start of an RPC. This
// should be set to the maximum time of an AddTask call that may write to a backlog. Note
// that query/nexus tasks will be processed without interruption even after scale down.
DrainBufferTime time.Duration
}

View File

@@ -106,6 +106,7 @@ var (
ComponentShardContext = component("shard-context")
ComponentShardEngine = component("shard-engine")
ComponentMatchingEngine = component("matching-engine")
ComponentPartitionScaler = component("partition-scaler")
ComponentReplicator = component("replicator")
ComponentReplicationTaskProcessor = component("replication-task-processor")
ComponentHSMStateReplicator = component("hsm-state-replicator")

View File

@@ -1245,6 +1245,9 @@ var (
"task_retry_transient",
WithDescription("Count of tasks that hit a transient error during match or forward and are retried immediately"),
)
PartitionScaleEvents = NewCounterDef("partition_scale_events")
PartitionScaleRead = NewGaugeDef("partition_scale_read")
PartitionScaleWrite = NewGaugeDef("partition_scale_write")
// ----------------------------------------------------------------------------------------------------------------
// Matching service: Metrics to track the health of worker registry.

View File

@@ -319,6 +319,8 @@ func IsServiceClientTransientError(err error) bool {
return err.Scope != enumspb.RESOURCE_EXHAUSTED_SCOPE_NAMESPACE
case *serviceerrors.ShardOwnershipLost:
return true
case *serviceerrors.StalePartitionCounts:
return true
default:
return false
}

View File

@@ -593,6 +593,7 @@ message DescribeTaskQueuePartitionRequest {
message DescribeTaskQueuePartitionResponse {
// contains k-v pairs of the type: buildID -> TaskQueueVersionInfoInternal
map<string, temporal.server.api.taskqueue.v1.TaskQueueVersionInfoInternal> versions_info_internal = 1;
temporal.server.api.taskqueue.v1.PartitionScaleInfo scale_info = 2;
}
message ForceUnloadTaskQueuePartitionRequest {

View File

@@ -312,6 +312,7 @@ message DescribeTaskQueuePartitionRequest {
message DescribeTaskQueuePartitionResponse {
map<string, temporal.server.api.taskqueue.v1.TaskQueueVersionInfoInternal> versions_info_internal = 1;
temporal.server.api.taskqueue.v1.PartitionScaleInfo scale_info = 2;
}
message ListTaskQueuePartitionsRequest {

View File

@@ -2,12 +2,17 @@ syntax = "proto3";
package temporal.server.api.persistence.v1;
import "google/protobuf/timestamp.proto";
import "temporal/api/common/v1/message.proto";
import "temporal/server/api/persistence/v1/predicates.proto";
import "temporal/server/api/persistence/v1/tasks.proto";
option go_package = "go.temporal.io/server/api/persistence/v1;persistence";
message TaskKey {
google.protobuf.Timestamp fire_time = 1;
int64 task_id = 2;
}
message QueueState {
map<int64, QueueReaderState> reader_states = 1;
TaskKey exclusive_reader_high_watermark = 2;

View File

@@ -2,6 +2,7 @@ syntax = "proto3";
package temporal.server.api.persistence.v1;
import "google/protobuf/any.proto";
import "google/protobuf/timestamp.proto";
import "temporal/api/common/v1/message.proto";
import "temporal/api/enums/v1/task_queue.proto";
@@ -72,6 +73,9 @@ message TaskQueueInfo {
// After determinining that the inactive table has no more tasks left, then this
// can be cleared on the active table.
bool other_has_tasks = 10;
// Dynamic partition scaling state (root only):
PartitionScaleState partition_scale_state = 11;
}
message SubqueueInfo {
@@ -104,7 +108,24 @@ message SubqueueKey {
int32 priority = 1;
}
message TaskKey {
google.protobuf.Timestamp fire_time = 1;
int64 task_id = 2;
message PartitionScaleState {
// Target number of partitions. Zero means managed scaling is not enabled.
int32 target = 1;
// Highest value that target has ever been set to.
int32 max_target = 2;
// Version number for target. This should change every time target changes.
sfixed64 target_version = 3;
// Bit field of partitions that may have backlog. Partition i is represented by:
// backlog_state[i/64] & (1 << i%64).
// (-- api-linter: core::0141::forbidden-types=disabled
// aip.dev/not-precedent: This is a bit field --)
repeated uint64 backlog_state = 4;
// Arbitrary state kept by the scaler implementation.
// (-- api-linter: core::0146::any=disabled
// aip.dev/not-precedent: This is not public. --)
google.protobuf.Any private_scaler_state = 100;
}

View File

@@ -1811,6 +1811,7 @@ func (adh *AdminHandler) DescribeTaskQueuePartition(
return &adminservice.DescribeTaskQueuePartitionResponse{
VersionsInfoInternal: resp.VersionsInfoInternal,
ScaleInfo: resp.ScaleInfo,
}, nil
}

View File

@@ -85,6 +85,7 @@ func (s *BacklogManagerTestSuite) SetupTest() {
s.ptqMgr.EXPECT().QueueKey().Return(queue).AnyTimes()
s.ptqMgr.EXPECT().ProcessSpooledTask(gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
s.ptqMgr.EXPECT().GetFairnessWeightOverrides().AnyTimes().Return(fairnessWeightOverrides{ /* To avoid deadlock with gomock method */ })
s.ptqMgr.EXPECT().StartScaleManager(gomock.Any()).AnyTimes()
var ctx context.Context
ctx, s.cancelCtx = context.WithCancel(context.Background())

46
service/matching/bits.go Normal file
View File

@@ -0,0 +1,46 @@
package matching
import "math/bits"
// bitSet encapsulates simple bit manipulation functions on a uint64 slice.
type bitSet []uint64
// len returns the index of the highest set bit.
func (bs bitSet) len() int32 {
i := len(bs) - 1
if i < 0 {
return 0
}
return int32(bits.Len64(bs[i]) + i*64)
}
// get returns true if a specific bit is set.
func (bs bitSet) get(i int32) bool {
if len(bs) < int(i)/64+1 {
return false
}
return bs[i/64]&(1<<(i%64)) != 0
}
// set sets a bit, growing the slice if necessary.
// note: caller must assign the result, as with append.
func (bs bitSet) set(i int32) bitSet {
for len(bs) < int(i)/64+1 {
bs = append(bs, 0)
}
bs[i/64] |= 1 << (i % 64)
return bs
}
// clear clears a bit (and shrinks the slice to the minimum required).
// note: caller must assign the result, as with append.
func (bs bitSet) clear(i int32) bitSet {
if len(bs) < int(i)/64+1 {
return bs
}
bs[i/64] &^= 1 << (i % 64)
for len(bs) > 0 && bs[len(bs)-1] == 0 {
bs = bs[:len(bs)-1]
}
return bs
}

View File

@@ -0,0 +1,60 @@
package matching
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestBitSet(t *testing.T) {
var bs bitSet
// setting and reading individual bits
require.False(t, bs.get(0))
bs = bs.set(0)
require.Equal(t, bitSet{1}, bs)
require.Equal(t, int32(1), bs.len())
require.True(t, bs.get(0))
require.False(t, bs.get(1))
bs = bs.set(5)
require.Equal(t, bitSet{0b100001}, bs)
require.Equal(t, int32(6), bs.len())
require.True(t, bs.get(5))
require.False(t, bs.get(4))
// bit in second word
bs = bs.set(64)
require.Equal(t, bitSet{0b100001, 1}, bs)
require.Equal(t, int32(65), bs.len())
require.True(t, bs.get(64))
require.False(t, bs.get(65))
// bit at word boundary
bs = bs.set(63)
require.Equal(t, bitSet{0b100001 | (1 << 63), 1}, bs)
require.Equal(t, int32(65), bs.len())
// clear high bit, read should drop back
bs = bs.clear(64)
require.Equal(t, bitSet{0b100001 | (1 << 63)}, bs) // trailing zero word trimmed
require.Equal(t, int32(64), bs.len())
require.False(t, bs.get(64))
// clear all bits one by one
bs = bs.clear(63)
bs = bs.clear(5)
bs = bs.clear(0)
require.Empty(t, bs)
require.Equal(t, int32(0), bs.len())
require.False(t, bs.get(0))
// clearing a bit that's already clear or out of range is a no-op
bs = bs.clear(999)
require.Empty(t, bs)
// read on nil returns 0
var nilbs bitSet
require.Equal(t, int32(0), nilbs.len())
}

View File

@@ -134,8 +134,9 @@ type (
PollerScalingWaitTime dynamicconfig.DurationPropertyFnWithTaskQueueFilter
PollerScalingDecisionsPerSecond dynamicconfig.FloatPropertyFnWithTaskQueueFilter
FairnessCounter dynamicconfig.TypedPropertyFnWithTaskQueueFilter[counter.CounterParams]
PartitionScaleAllowedDrift dynamicconfig.TypedPropertyFnWithTaskQueueFilter[dynamicconfig.PartitionScaleAllowedDrift]
FairnessCounter dynamicconfig.TypedPropertyFnWithTaskQueueFilter[counter.CounterParams]
PartitionScaleAllowedDrift dynamicconfig.TypedPropertyFnWithTaskQueueFilter[dynamicconfig.PartitionScaleAllowedDrift]
PartitionScaleManagerSettings dynamicconfig.TypedPropertyFnWithTaskQueueFilter[dynamicconfig.PartitionScaleManagerSettings]
LogAllReqErrors dynamicconfig.BoolPropertyFnWithNamespaceFilter
}
@@ -223,8 +224,9 @@ type (
PollerScalingWaitTime func() time.Duration
PollerScalingDecisionsPerSecond func() float64
FairnessCounter func() counter.CounterParams
PartitionScaleAllowedDrift func() dynamicconfig.PartitionScaleAllowedDrift
FairnessCounter func() counter.CounterParams
PartitionScaleAllowedDrift func() dynamicconfig.PartitionScaleAllowedDrift
PartitionScaleManagerSettings func() dynamicconfig.PartitionScaleManagerSettings
loadCause loadCause
}
@@ -371,8 +373,9 @@ func NewConfig(
PollerScalingWaitTime: dynamicconfig.MatchingPollerScalingWaitTime.Get(dc),
PollerScalingDecisionsPerSecond: dynamicconfig.MatchingPollerScalingDecisionsPerSecond.Get(dc),
FairnessCounter: dynamicconfig.MatchingFairnessCounter.Get(dc),
PartitionScaleAllowedDrift: dynamicconfig.MatchingPartitionScaleAllowedDrift.Get(dc),
FairnessCounter: dynamicconfig.MatchingFairnessCounter.Get(dc),
PartitionScaleAllowedDrift: dynamicconfig.MatchingPartitionScaleAllowedDrift.Get(dc),
PartitionScaleManagerSettings: dynamicconfig.MatchingPartitionScaleManager.Get(dc),
LogAllReqErrors: dynamicconfig.LogAllReqErrors.Get(dc),
}

View File

@@ -45,6 +45,7 @@ type (
rangeID int64
subqueues []*dbSubqueue
otherHasTasks bool
scaleState *persistencespb.PartitionScaleState
// used to avoid unnecessary metadata writes:
lastChange time.Time // updated when metadata is changed in memory
@@ -62,6 +63,7 @@ type (
ackLevel int64 // TODO(pri): old matcher cleanup, delete later
subqueues []persistencespb.SubqueueInfo
otherHasTasks bool
scaleState *persistencespb.PartitionScaleState
}
subqueueIndex int
@@ -165,6 +167,7 @@ func (db *taskQueueDB) RenewLease(
ackLevel: db.subqueues[subqueueZero].AckLevel, // TODO(pri): cleanup, only used by old backlog manager
subqueues: db.cloneSubqueues(),
otherHasTasks: !db.isDraining && db.otherHasTasks,
scaleState: db.scaleState,
}, nil
}
@@ -187,6 +190,7 @@ func (db *taskQueueDB) takeOverTaskQueueLocked(
response.TaskQueueInfo.AckLevel,
response.TaskQueueInfo.ApproximateBacklogCount,
)
db.scaleState = response.TaskQueueInfo.PartitionScaleState
err := db.updateTaskQueueLocked(ctx, true)
if err != nil {
db.rangeID = 0
@@ -492,6 +496,19 @@ func (db *taskQueueDB) SetOtherHasTasks(ctx context.Context, value bool) error {
return db.updateTaskQueueLocked(ctx, false)
}
// UpdateScaleState sets the partition scale state (in memory). If syncToDB is true, it also tries to persist it to the DB.
// If syncToDB is false, ctx is not used.
func (db *taskQueueDB) UpdateScaleState(ctx context.Context, scaleState *persistencespb.PartitionScaleState, syncToDB bool) error {
db.Lock()
defer db.Unlock()
db.scaleState = scaleState
db.lastChange = time.Now()
if syncToDB {
return db.updateTaskQueueLocked(ctx, false)
}
return nil
}
// CreateTasks creates a batch of given tasks for this task queue
func (db *taskQueueDB) CreateTasks(
ctx context.Context,
@@ -795,6 +812,7 @@ func (db *taskQueueDB) cachedQueueInfo() *persistencespb.TaskQueueInfo {
ApproximateBacklogCount: db.subqueues[subqueueZero].ApproximateBacklogCount, // backwards compatibility
Subqueues: infos,
OtherHasTasks: db.otherHasTasks,
PartitionScaleState: db.scaleState,
}
}

View File

@@ -143,6 +143,12 @@ func (c *fairBacklogManagerImpl) initState(state taskQueueState, err error) {
return
}
// Pass scale info back to physical tq from unversioned (default) queue.
// This must be done before c.initializedError.Set().
if c.queueKey().Partition().IsRoot() && !c.queueKey().IsVersioned() {
c.pqMgr.StartScaleManager(state.scaleState)
}
if state.otherHasTasks {
c.pqMgr.SetupDraining()
}

View File

@@ -50,6 +50,7 @@ var Module = fx.Options(
fx.Provide(ServiceResolverProvider),
fx.Provide(ServerProvider),
fx.Provide(NewService),
fx.Provide(func() PartitionScalerFactory { return nil }),
fx.Invoke(ServiceLifetimeHooks),
)

View File

@@ -74,6 +74,7 @@ type (
WorkersRegistry workers.Registry
Serializer serialization.Serializer
TaskHookFactories []hooks.TaskHookFactory `group:"TaskHookFactories"`
PartitionScalerFactory PartitionScalerFactory
}
)
@@ -117,6 +118,7 @@ func NewHandler(
params.RateLimiter,
params.Serializer,
params.TaskHookFactories,
params.PartitionScalerFactory,
),
namespaceRegistry: params.NamespaceRegistry,
workersRegistry: params.WorkersRegistry,

View File

@@ -167,6 +167,7 @@ type (
partitions map[tqid.PartitionKey]taskQueuePartitionManager
gaugeMetrics gaugeMetrics // per-namespace task queue counters
config *Config
partitionScalerFactory PartitionScalerFactory
versionChecker headers.VersionChecker
testHooks testhooks.TestHooks
// queryResults maps query TaskID (which is a UUID generated in QueryWorkflow() call) to a channel
@@ -282,6 +283,7 @@ func NewEngine(
rateLimiter TaskDispatchRateLimiter,
historySerializer serialization.Serializer,
taskHookFactories []hooks.TaskHookFactory,
partitionScalerFactory PartitionScalerFactory,
) Engine {
scopedMetricsHandler := metricsHandler.WithTags(metrics.OperationTag(metrics.MatchingEngineScope))
e := &matchingEngineImpl{
@@ -326,6 +328,7 @@ func NewEngine(
userDataUpdateBatchers: collection.NewSyncMap[namespace.ID, *stream_batcher.Batcher[*userDataUpdate, error]](),
rateLimiter: rateLimiter,
taskHookFactories: taskHookFactories,
partitionScalerFactory: partitionScalerFactory,
}
e.nexusEndpointsOwnershipLostCh.Store(make(chan struct{}))
e.reachabilityCache = newReachabilityCache(

View File

@@ -0,0 +1,50 @@
//go:generate mockgen -package $GOPACKAGE -source $GOFILE -destination partition_scaler_mock.go
package matching
import (
enumspb "go.temporal.io/api/enums/v1"
"go.temporal.io/server/common/namespace"
"google.golang.org/protobuf/types/known/anypb"
)
// PartitionScalerFactory is a pluggable interface to control partition scaling.
type PartitionScalerFactory interface {
// New will be called for a new root partition. It should return a new PartitionScaler
// (or nil to disable).
New(nsName namespace.Name, tqName string, tqType enumspb.TaskQueueType) PartitionScaler
}
// PartitionScaler is an instance of a scaler for one task queue.
type PartitionScaler interface {
// OnTasks will be called once per batch of tasks added, either sync match or async. The
// NumTasks count may be estimated based on assumptions of load balancing across
// partitions.
//
// It will be given the current partition count target and its
// private state (optional). It returns a PartitionScalerDecision, which will usually be
// "no change". To make a change, it should return a number in NewTarget. Zero means
// disable managed scaling.
//
// Changes may be ignored if values are out of range, if changed too often, or other
// reasons. Private state will not be updated if target doesn't also change, or if the
// change is ignored due to rate limit or any other reason.
//
// It will also be called periodically with less than a full batch, or even zero, to allow
// timely scale down when there are no/few tasks.
OnTasks(PartitionScalerInput) PartitionScalerDecision
// Stop will be called when unloading the partition.
Stop()
}
type PartitionScalerInput struct {
NumTasks int // new tasks added since last call
CurrentTarget int
PrivateState *anypb.Any
}
type PartitionScalerDecision struct {
NoChange bool // if true, don't do anything
NewTarget int // if zero, disable managed scaling, otherwise set partition target
PrivateState *anypb.Any // optional private state to be stored with target
}

View File

@@ -0,0 +1,106 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: partition_scaler_interface.go
//
// Generated by this command:
//
// mockgen -package matching -source partition_scaler_interface.go -destination partition_scaler_mock.go
//
// Package matching is a generated GoMock package.
package matching
import (
reflect "reflect"
enums "go.temporal.io/api/enums/v1"
namespace "go.temporal.io/server/common/namespace"
gomock "go.uber.org/mock/gomock"
)
// MockPartitionScalerFactory is a mock of PartitionScalerFactory interface.
type MockPartitionScalerFactory struct {
ctrl *gomock.Controller
recorder *MockPartitionScalerFactoryMockRecorder
isgomock struct{}
}
// MockPartitionScalerFactoryMockRecorder is the mock recorder for MockPartitionScalerFactory.
type MockPartitionScalerFactoryMockRecorder struct {
mock *MockPartitionScalerFactory
}
// NewMockPartitionScalerFactory creates a new mock instance.
func NewMockPartitionScalerFactory(ctrl *gomock.Controller) *MockPartitionScalerFactory {
mock := &MockPartitionScalerFactory{ctrl: ctrl}
mock.recorder = &MockPartitionScalerFactoryMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockPartitionScalerFactory) EXPECT() *MockPartitionScalerFactoryMockRecorder {
return m.recorder
}
// New mocks base method.
func (m *MockPartitionScalerFactory) New(nsName namespace.Name, tqName string, tqType enums.TaskQueueType) PartitionScaler {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "New", nsName, tqName, tqType)
ret0, _ := ret[0].(PartitionScaler)
return ret0
}
// New indicates an expected call of New.
func (mr *MockPartitionScalerFactoryMockRecorder) New(nsName, tqName, tqType any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "New", reflect.TypeOf((*MockPartitionScalerFactory)(nil).New), nsName, tqName, tqType)
}
// MockPartitionScaler is a mock of PartitionScaler interface.
type MockPartitionScaler struct {
ctrl *gomock.Controller
recorder *MockPartitionScalerMockRecorder
isgomock struct{}
}
// MockPartitionScalerMockRecorder is the mock recorder for MockPartitionScaler.
type MockPartitionScalerMockRecorder struct {
mock *MockPartitionScaler
}
// NewMockPartitionScaler creates a new mock instance.
func NewMockPartitionScaler(ctrl *gomock.Controller) *MockPartitionScaler {
mock := &MockPartitionScaler{ctrl: ctrl}
mock.recorder = &MockPartitionScalerMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockPartitionScaler) EXPECT() *MockPartitionScalerMockRecorder {
return m.recorder
}
// OnTasks mocks base method.
func (m *MockPartitionScaler) OnTasks(arg0 PartitionScalerInput) PartitionScalerDecision {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "OnTasks", arg0)
ret0, _ := ret[0].(PartitionScalerDecision)
return ret0
}
// OnTasks indicates an expected call of OnTasks.
func (mr *MockPartitionScalerMockRecorder) OnTasks(arg0 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OnTasks", reflect.TypeOf((*MockPartitionScaler)(nil).OnTasks), arg0)
}
// Stop mocks base method.
func (m *MockPartitionScaler) Stop() {
m.ctrl.T.Helper()
m.ctrl.Call(m, "Stop")
}
// Stop indicates an expected call of Stop.
func (mr *MockPartitionScalerMockRecorder) Stop() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Stop", reflect.TypeOf((*MockPartitionScaler)(nil).Stop))
}

View File

@@ -352,6 +352,20 @@ func (c *physicalTaskQueueManagerImpl) WaitUntilInitialized(ctx context.Context)
return err
}
// StartScaleManager is called by backlog manager after it's loaded metadata from the default queue. (New matcher only.)
func (c *physicalTaskQueueManagerImpl) StartScaleManager(scaleState *persistencespb.PartitionScaleState) {
c.partitionMgr.StartScaleManager(scaleState)
}
func (c *physicalTaskQueueManagerImpl) UpdateScaleState(scaleState *persistencespb.PartitionScaleState, syncToDB bool) error {
if !syncToDB {
return c.backlogMgr.getDB().UpdateScaleState(c.tqCtx /* unused */, scaleState, false)
}
ctx, cancel := context.WithTimeout(c.tqCtx, ioTimeout)
defer cancel()
return c.backlogMgr.getDB().UpdateScaleState(ctx, scaleState, true)
}
// Call this to set up dual-read from the other table.
// Must be called by the active backlog manager before it sets itself initialized.
// Must only be called when using new matcher.

View File

@@ -18,6 +18,11 @@ type (
Start()
Stop(unloadCause)
WaitUntilInitialized(context.Context) error
// StartScaleManager is called by backlog manager after it's loaded metadata from the
// default queue. (New matcher only.)
StartScaleManager(*persistencespb.PartitionScaleState)
// UpdateScaleState is called from the scale manager to update the scale state in the db.
UpdateScaleState(*persistencespb.PartitionScaleState, bool) error
SetupDraining()
// FinishedDraining is called by a draining backlog manager when it has fully drained.
FinishedDraining()

View File

@@ -357,6 +357,18 @@ func (mr *MockphysicalTaskQueueManagerMockRecorder) Start() *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Start", reflect.TypeOf((*MockphysicalTaskQueueManager)(nil).Start))
}
// StartScaleManager mocks base method.
func (m *MockphysicalTaskQueueManager) StartScaleManager(arg0 *persistence.PartitionScaleState) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "StartScaleManager", arg0)
}
// StartScaleManager indicates an expected call of StartScaleManager.
func (mr *MockphysicalTaskQueueManagerMockRecorder) StartScaleManager(arg0 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StartScaleManager", reflect.TypeOf((*MockphysicalTaskQueueManager)(nil).StartScaleManager), arg0)
}
// Stop mocks base method.
func (m *MockphysicalTaskQueueManager) Stop(arg0 unloadCause) {
m.ctrl.T.Helper()
@@ -420,6 +432,20 @@ func (mr *MockphysicalTaskQueueManagerMockRecorder) UpdateRemotePriorityBacklogs
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateRemotePriorityBacklogs", reflect.TypeOf((*MockphysicalTaskQueueManager)(nil).UpdateRemotePriorityBacklogs), arg0)
}
// UpdateScaleState mocks base method.
func (m *MockphysicalTaskQueueManager) UpdateScaleState(arg0 *persistence.PartitionScaleState, arg1 bool) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UpdateScaleState", arg0, arg1)
ret0, _ := ret[0].(error)
return ret0
}
// UpdateScaleState indicates an expected call of UpdateScaleState.
func (mr *MockphysicalTaskQueueManagerMockRecorder) UpdateScaleState(arg0, arg1 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateScaleState", reflect.TypeOf((*MockphysicalTaskQueueManager)(nil).UpdateScaleState), arg0, arg1)
}
// UserDataChanged mocks base method.
func (m *MockphysicalTaskQueueManager) UserDataChanged() {
m.ctrl.T.Helper()

View File

@@ -156,6 +156,12 @@ func (c *priBacklogManagerImpl) initState(state taskQueueState, err error) {
return
}
// Pass scale info back to physical tq from unversioned (default) queue.
// This must be done before c.initializedError.Set().
if c.queueKey().Partition().IsRoot() && !c.queueKey().IsVersioned() {
c.pqMgr.StartScaleManager(state.scaleState)
}
if state.otherHasTasks {
c.pqMgr.SetupDraining()
}

View File

@@ -0,0 +1,357 @@
//go:generate mockgen -package $GOPACKAGE -source $GOFILE -destination scale_manager_mock.go
package matching
import (
"context"
"sync/atomic"
"time"
taskqueuepb "go.temporal.io/api/taskqueue/v1"
"go.temporal.io/server/api/matchingservice/v1"
persistencespb "go.temporal.io/server/api/persistence/v1"
taskqueuespb "go.temporal.io/server/api/taskqueue/v1"
"go.temporal.io/server/common"
"go.temporal.io/server/common/backoff"
"go.temporal.io/server/common/clock"
"go.temporal.io/server/common/dynamicconfig"
"go.temporal.io/server/common/goro"
"go.temporal.io/server/common/log"
"go.temporal.io/server/common/log/tag"
"go.temporal.io/server/common/metrics"
"go.temporal.io/server/common/tqid"
"google.golang.org/protobuf/proto"
)
// scaleManager keeps some state and manages the interaction with partitionScaler.
// scaleManager runs on the root partition only.
//
// All scaler/state work is funneled through a single background goroutine. That
// goroutine owns scaleState/scaleDB/lastDecision and is the only caller of
// partitionScaler, so the scaler implementation can rely on serial calls and
// scaleState needs no lock. AddedTasks talks to the worker only via the atomic
// batch counter and the wakeup channel, so it never blocks.
type scaleManager struct {
partition tqid.Partition
logger log.Logger
metricsHandler metrics.Handler
userDataManager userDataManager
matchingClient matchingservice.MatchingServiceClient
partitionScaler PartitionScaler
batchSize int64 // fixed at creation time
settings dynamicconfig.TypedPropertyFn[dynamicconfig.PartitionScaleManagerSettings]
getWritePartitions dynamicconfig.IntPropertyFn
emitGaugeMetrics dynamicconfig.BoolPropertyFn
timeSource clock.TimeSource
background *goro.Handle
// owned by the worker goroutine after Start starts it
scaleState *persistencespb.PartitionScaleState
scaleDB scaleDB
nextDecision time.Time
batch atomic.Int64
wakeup chan struct{}
}
// scaleDB is used to write scale state to persistence. It's a sub-interface of
// physicalTaskQueueManager (for the default queue).
type scaleDB interface {
UpdateScaleState(*persistencespb.PartitionScaleState, bool) error
}
func newScaleManager(
baseCtx context.Context,
partition tqid.Partition,
logger log.Logger,
metricsHandler metrics.Handler,
userDataManager userDataManager,
matchingClient matchingservice.MatchingServiceClient,
partitionScaler PartitionScaler,
timeSource clock.TimeSource,
settings dynamicconfig.TypedPropertyFn[dynamicconfig.PartitionScaleManagerSettings],
getWritePartitions dynamicconfig.IntPropertyFn,
emitGaugeMetrics dynamicconfig.BoolPropertyFn,
) *scaleManager {
return &scaleManager{
partition: partition,
logger: log.With(logger, tag.ComponentPartitionScaler),
metricsHandler: metricsHandler,
userDataManager: userDataManager,
matchingClient: matchingClient,
partitionScaler: partitionScaler,
batchSize: int64(settings().BatchSize),
settings: settings,
getWritePartitions: getWritePartitions,
emitGaugeMetrics: emitGaugeMetrics,
timeSource: timeSource,
background: goro.NewHandle(baseCtx),
wakeup: make(chan struct{}, 1),
}
}
func (sm *scaleManager) Stop() {
if sm == nil {
return
}
sm.background.Cancel()
sm.partitionScaler.Stop()
if sm.emitGaugeMetrics() {
// this is unfortunate but at least allows max() across pods to get the right value
metrics.PartitionScaleRead.With(sm.metricsHandler).Record(float64(-1))
metrics.PartitionScaleWrite.With(sm.metricsHandler).Record(float64(-1))
}
}
// Start is called when the root partitions's default queue has loaded its metadata.
// Must be called at most once.
func (sm *scaleManager) Start(scaleState *persistencespb.PartitionScaleState, scaleDB scaleDB) {
if sm == nil {
return
}
// backgroundWork can assume sm.scaleDB is set since we set it before starting it.
sm.scaleDB = scaleDB
sm.setState(scaleState)
sm.background.Go(sm.backgroundWork)
}
// AddedTasks is called on a batch of tasks added.
// This is called in the task add path, so it shouldn't block.
func (sm *scaleManager) AddedTasks(numTasks int) {
if sm == nil {
return
}
// scale target batch size by numTasks (since numTasks is scaled by partitions)
batchSize := int64(numTasks) * sm.batchSize
if sm.batch.Add(int64(numTasks)) < batchSize {
return // not enough for a batch yet
}
// non-blocking signal
select {
case sm.wakeup <- struct{}{}:
default:
}
}
func (sm *scaleManager) backgroundWork(ctx context.Context) error {
timerCh := func() <-chan time.Time {
ch, _ := sm.timeSource.NewTimer(backoff.Jitter(sm.settings().BackgroundInterval, 0.05))
return ch
}
for {
select {
case <-ctx.Done():
return ctx.Err()
case <-sm.wakeup:
sm.callScaler()
case <-timerCh():
// call scaler even if batch == 0, to allow scale down when no tasks are coming in
sm.callScaler()
// check child partitions periodically
sm.updateDrainState(ctx)
}
}
}
// callScaler runs the scaler on the accumulated batch and persists the resulting state if it
// changed.
// Called from backgroundWork only.
func (sm *scaleManager) callScaler() {
// don't bother calling during cooldown period
if !sm.nextDecision.IsZero() && sm.timeSource.Now().Before(sm.nextDecision) {
return
}
// grab current batch (may be zero)
tasks := int(sm.batch.Swap(0))
decision := sm.partitionScaler.OnTasks(PartitionScalerInput{
NumTasks: tasks,
CurrentTarget: int(sm.scaleState.GetTarget()),
PrivateState: sm.scaleState.GetPrivateScalerState(),
})
if decision.NoChange ||
decision.NewTarget == int(sm.scaleState.GetTarget()) {
return
}
target := int32(decision.NewTarget)
newState := common.CloneProto(sm.scaleState)
if newState == nil {
newState = &persistencespb.PartitionScaleState{}
}
prevTarget := newState.Target
newState.Target = target
newState.MaxTarget = max(newState.MaxTarget, target)
newState.TargetVersion = sm.timeSource.Now().UnixNano()
newState.PrivateScalerState = decision.PrivateState
mayHaveBacklog := target
if prevTarget == 0 {
// Turning on managed partition scaling: consider all partitions from dynamic
// config as having backlog also.
mayHaveBacklog = max(mayHaveBacklog, int32(sm.getWritePartitions()))
}
for i := range mayHaveBacklog {
newState.BacklogState = bitSet(newState.BacklogState).set(i)
}
// we must succesfully write to the db before making new state active
if err := sm.scaleDB.UpdateScaleState(newState, true); err != nil {
sm.logger.Error("failed to update state", tag.Error(err), tag.Operation("scale"))
return
}
cooldown := time.Duration(float32(time.Second) / sm.settings().MaxRate)
sm.nextDecision = sm.timeSource.Now().Add(cooldown)
sm.logger.Info("new target",
tag.Int32("target", target),
tag.Int32("prev-target", prevTarget),
tag.Int32("max-target", newState.MaxTarget))
metrics.PartitionScaleEvents.With(sm.metricsHandler).Record(1)
sm.setState(newState)
}
// setState updates the current scale state and syncs it to ephemeral data.
// This should only be called _after_ the state is persisted to the db.
// Called from backgroundWork or LoadedMetadata only.
func (sm *scaleManager) setState(newState *persistencespb.PartitionScaleState) {
prevInfo := scaleStateToInfo(sm.scaleState)
sm.scaleState = newState
newInfo := scaleStateToInfo(sm.scaleState)
// only push ephemeral data if _info_ changed, not on any state change
if !proto.Equal(prevInfo, newInfo) {
sm.userDataManager.SetPartitionScale(newInfo)
}
if sm.emitGaugeMetrics() {
metrics.PartitionScaleRead.With(sm.metricsHandler).Record(float64(newInfo.Read))
metrics.PartitionScaleWrite.With(sm.metricsHandler).Record(float64(newInfo.Write))
}
}
func (sm *scaleManager) describeRequest(id int32) *matchingservice.DescribeTaskQueuePartitionRequest {
return &matchingservice.DescribeTaskQueuePartitionRequest{
NamespaceId: sm.partition.NamespaceId(),
TaskQueuePartition: &taskqueuespb.TaskQueuePartition{
TaskQueue: sm.partition.TaskQueue().Name(),
TaskQueueType: sm.partition.TaskType(),
PartitionId: &taskqueuespb.TaskQueuePartition_NormalPartitionId{NormalPartitionId: id},
},
Versions: &taskqueuepb.TaskQueueVersionSelection{
Unversioned: true,
// TODO(dp)(carlydf): deal with inactive/deleted versions (use user data version info)
AllActive: true,
},
ReportInternalTaskQueueStatus: true,
}
}
func (sm *scaleManager) updateDrainState(ctx context.Context) {
scaleState := sm.scaleState
read := scaleStateToReadCount(scaleState)
if read == 0 {
return
}
// check if we should evaluate drain state
target := scaleState.GetTarget()
checkDrain := target > 0 &&
sm.timeSource.Since(time.Unix(0, scaleState.GetTargetVersion())) >= sm.settings().DrainBufferTime
info := scaleStateToInfo(scaleState)
var toClear []int32
for id := range read {
// note: right now, this call is useless if checkDrain is false, or for partitions < write.
// later we'll need these calls to update backlog and other stats, so we just do it always
// now to simplify the future diff.
callCtx, cancel := context.WithTimeout(ctx, ioTimeout)
res, err := sm.matchingClient.DescribeTaskQueuePartition(callCtx, sm.describeRequest(id))
cancel()
if err != nil {
continue
}
// check drain state for partitions in the draining range
if checkDrain &&
id >= target &&
bitSet(scaleState.BacklogState).get(id) &&
partitionIsFullyDrained(res, info) {
toClear = append(toClear, id)
}
}
if len(toClear) == 0 {
return
}
newState := common.CloneProto(scaleState)
if newState == nil {
newState = &persistencespb.PartitionScaleState{}
}
for _, i := range toClear {
newState.BacklogState = bitSet(newState.BacklogState).clear(i)
}
// sync to db (must be persisted before taking effect)
if err := sm.scaleDB.UpdateScaleState(newState, true); err != nil {
sm.logger.Error("failed to update state", tag.Error(err), tag.Operation("drain"))
return
}
sm.logger.Info("drain",
tag.Any("drained-partitions", toClear),
tag.Int32("target", info.Write),
tag.Int32("prev-read", info.Read),
tag.Int32("read", bitSet(newState.BacklogState).len()))
sm.setState(newState)
}
func partitionIsFullyDrained(
res *matchingservice.DescribeTaskQueuePartitionResponse,
info *taskqueuespb.PartitionScaleInfo,
) bool {
// Require that the partition agrees with the current scale state, i.e. it knows that
// it's draining, i.e. it knows it can't accept any new tasks. We include the version
// as well as just the read+write counts to avoid an ABA problem.
resInfo := res.GetScaleInfo()
if resInfo == nil ||
resInfo.Version != info.Version ||
resInfo.Read != info.Read ||
resInfo.Write != info.Write {
return false
}
for _, v := range res.GetVersionsInfoInternal() {
for _, q := range v.GetPhysicalTaskQueueInfo().GetInternalTaskQueueStatus() {
if !q.GetBacklogDrained() {
return false
}
}
}
return true
}
func scaleStateToReadCount(scaleState *persistencespb.PartitionScaleState) int32 {
return max(scaleState.GetTarget(), bitSet(scaleState.GetBacklogState()).len())
}
func scaleStateToInfo(scaleState *persistencespb.PartitionScaleState) *taskqueuespb.PartitionScaleInfo {
// note if scaleState == nil, read and write will both be 0
return &taskqueuespb.PartitionScaleInfo{
Read: scaleStateToReadCount(scaleState),
Write: scaleState.GetTarget(),
Version: scaleState.GetTargetVersion(),
}
}

View File

@@ -0,0 +1,55 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: scale_manager.go
//
// Generated by this command:
//
// mockgen -package matching -source scale_manager.go -destination scale_manager_mock.go
//
// Package matching is a generated GoMock package.
package matching
import (
reflect "reflect"
persistence "go.temporal.io/server/api/persistence/v1"
gomock "go.uber.org/mock/gomock"
)
// MockscaleDB is a mock of scaleDB interface.
type MockscaleDB struct {
ctrl *gomock.Controller
recorder *MockscaleDBMockRecorder
isgomock struct{}
}
// MockscaleDBMockRecorder is the mock recorder for MockscaleDB.
type MockscaleDBMockRecorder struct {
mock *MockscaleDB
}
// NewMockscaleDB creates a new mock instance.
func NewMockscaleDB(ctrl *gomock.Controller) *MockscaleDB {
mock := &MockscaleDB{ctrl: ctrl}
mock.recorder = &MockscaleDBMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockscaleDB) EXPECT() *MockscaleDBMockRecorder {
return m.recorder
}
// UpdateScaleState mocks base method.
func (m *MockscaleDB) UpdateScaleState(arg0 *persistence.PartitionScaleState, arg1 bool) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UpdateScaleState", arg0, arg1)
ret0, _ := ret[0].(error)
return ret0
}
// UpdateScaleState indicates an expected call of UpdateScaleState.
func (mr *MockscaleDBMockRecorder) UpdateScaleState(arg0, arg1 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateScaleState", reflect.TypeOf((*MockscaleDB)(nil).UpdateScaleState), arg0, arg1)
}

View File

@@ -0,0 +1,521 @@
package matching
import (
"context"
"errors"
"sync/atomic"
"testing"
"time"
"github.com/stretchr/testify/suite"
enumspb "go.temporal.io/api/enums/v1"
"go.temporal.io/server/api/matchingservice/v1"
"go.temporal.io/server/api/matchingservicemock/v1"
persistencespb "go.temporal.io/server/api/persistence/v1"
taskqueuespb "go.temporal.io/server/api/taskqueue/v1"
"go.temporal.io/server/common"
"go.temporal.io/server/common/clock"
"go.temporal.io/server/common/dynamicconfig"
"go.temporal.io/server/common/log"
"go.temporal.io/server/common/metrics"
"go.temporal.io/server/common/testing/await"
"go.temporal.io/server/common/testing/protorequire"
"go.temporal.io/server/common/testing/protoutils"
"go.temporal.io/server/common/tqid"
"go.uber.org/mock/gomock"
"google.golang.org/protobuf/types/known/wrapperspb"
)
type ScaleManagerSuite struct {
suite.Suite
protorequire.ProtoAssertions
controller *gomock.Controller
scaler *MockPartitionScaler
scaleDB *MockscaleDB
userData *MockuserDataManager
matching *matchingservicemock.MockMatchingServiceClient
timeSource *clock.EventTimeSource
sm *scaleManager
settings dynamicconfig.PartitionScaleManagerSettings
}
func TestScaleManagerSuite(t *testing.T) {
suite.Run(t, new(ScaleManagerSuite))
}
func (s *ScaleManagerSuite) SetupTest() {
s.ProtoAssertions = protorequire.New(s.T())
s.controller = gomock.NewController(s.T())
s.scaler = NewMockPartitionScaler(s.controller)
s.scaleDB = NewMockscaleDB(s.controller)
s.userData = NewMockuserDataManager(s.controller)
s.matching = matchingservicemock.NewMockMatchingServiceClient(s.controller)
s.timeSource = clock.NewEventTimeSource()
// scaler.Stop is called from sm.Stop in TearDownTest, but only if the test
// actually started the manager.
s.scaler.EXPECT().Stop().AnyTimes()
s.settings = dynamicconfig.PartitionScaleManagerSettings{
MaxRate: 10, // 100ms cooldown
BatchSize: 1,
BackgroundInterval: time.Hour, // disabled by default
DrainBufferTime: time.Hour, // disabled by default
}
}
func (s *ScaleManagerSuite) TearDownTest() {
if s.sm != nil {
s.sm.Stop()
// wait for the worker to actually exit, otherwise mock callbacks may
// race with controller.Finish
<-s.sm.background.Done()
s.sm = nil
}
}
// startManager builds and starts the scaleManager. Call this after configuring
// EXPECTs and tweaking s.settings. Pass initial=nil to start with no prior state.
func (s *ScaleManagerSuite) startManager(writePartitions int, initial *persistencespb.PartitionScaleState) {
s.sm = newScaleManager(
context.Background(),
tqid.MustNormalPartitionFromRpcName("tq", "ns-id", enumspb.TASK_QUEUE_TYPE_WORKFLOW),
log.NewTestLogger(),
metrics.NoopMetricsHandler,
s.userData,
s.matching,
s.scaler,
s.timeSource,
dynamicconfig.GetTypedPropertyFn(s.settings),
dynamicconfig.GetIntPropertyFn(writePartitions),
dynamicconfig.GetBoolPropertyFn(false),
)
s.sm.Start(initial, s.scaleDB)
}
// fireBackgroundTimer waits for the worker to register its periodic timer and
// then advances the fake clock past the timer's deadline.
func (s *ScaleManagerSuite) fireBackgroundTimer() {
await.RequireTruef(s.T(), func() bool { return s.timeSource.NumTimers() >= 1 },
time.Second, time.Millisecond,
"worker never registered a periodic timer")
// BackgroundInterval has up to 5% jitter; advance comfortably past it.
s.timeSource.Advance(s.settings.BackgroundInterval*2 + time.Millisecond)
}
// waitRecv blocks until ch yields a value, failing the test on timeout.
func waitRecv[T any](s *ScaleManagerSuite, ch <-chan T, msg string) T {
s.T().Helper()
select {
case v := <-ch:
return v
case <-time.After(2 * time.Second):
s.FailNow("timed out waiting", msg)
var zero T
return zero
}
}
// assertNoRecv asserts that ch yields no value within d.
func assertNoRecv[T any](s *ScaleManagerSuite, ch <-chan T, d time.Duration, msg string) {
s.T().Helper()
select {
case <-ch:
s.FailNow("unexpected event", msg)
case <-time.After(d):
}
}
// --- tests ---
// TestAddedTasksWakesScalerOnFullBatch verifies that the scaler is only called
// once cumulative batch reaches numTasks*BatchSize.
func (s *ScaleManagerSuite) TestAddedTasksWakesScalerOnFullBatch() {
s.settings.BatchSize = 5
inputs := make(chan PartitionScalerInput, 1)
s.scaler.EXPECT().OnTasks(gomock.Any()).
Do(func(in PartitionScalerInput) { inputs <- in }).
Return(PartitionScalerDecision{NoChange: true}).AnyTimes()
s.startManager(4, nil)
for range 4 {
s.sm.AddedTasks(1)
}
assertNoRecv(s, inputs, 30*time.Millisecond, "scaler called below threshold")
s.sm.AddedTasks(1) // cumulative 5 hits threshold
in := waitRecv(s, inputs, "scaler never called")
s.Equal(5, in.NumTasks, "all 5 tasks should accumulate before firing")
}
// TestPeriodicTimerCallsScaler verifies that the scaler is called periodically
// even with no AddedTasks activity (so we can scale down).
func (s *ScaleManagerSuite) TestPeriodicTimerCallsScaler() {
s.settings.BackgroundInterval = 50 * time.Millisecond
inputs := make(chan PartitionScalerInput, 4)
s.scaler.EXPECT().OnTasks(gomock.Any()).
Do(func(in PartitionScalerInput) {
select {
case inputs <- in:
default: // ignore the rest
}
}).
Return(PartitionScalerDecision{NoChange: true}).
MinTimes(1)
s.startManager(4, nil)
s.fireBackgroundTimer()
in := waitRecv(s, inputs, "scaler never called")
s.Zero(in.NumTasks)
}
// TestDecisionPersistsAndUpdatesEphemeralData verifies the full happy path.
// When prevTarget was 0, BacklogState should also cover all partitions from
// dynamic config (not just the new target).
func (s *ScaleManagerSuite) TestDecisionPersistsAndUpdatesEphemeralData() {
s.scaler.EXPECT().OnTasks(gomock.Any()).
Return(PartitionScalerDecision{NewTarget: 2})
dbWrites := make(chan *persistencespb.PartitionScaleState, 1)
s.scaleDB.EXPECT().UpdateScaleState(gomock.Any(), gomock.Any()).
Do(func(state *persistencespb.PartitionScaleState, _ bool) {
dbWrites <- common.CloneProto(state)
}).
Return(nil)
scaleInfos := make(chan *taskqueuespb.PartitionScaleInfo, 1)
s.userData.EXPECT().SetPartitionScale(gomock.Any()).
Do(func(info *taskqueuespb.PartitionScaleInfo) { scaleInfos <- info })
s.startManager(4, nil) // 4 write partitions in dynamic config
s.sm.AddedTasks(1)
state := waitRecv(s, dbWrites, "no db write")
s.Equal(int32(2), state.Target)
s.Equal(int32(2), state.MaxTarget)
s.Equal(s.timeSource.Now().UnixNano(), state.TargetVersion)
s.Equal(int32(4), bitSet(state.BacklogState).len(),
"BacklogState should cover all dynamic-config write partitions on initial scale-up")
info := waitRecv(s, scaleInfos, "no ephemeral data update")
s.Equal(int32(4), info.Read)
s.Equal(int32(2), info.Write)
}
// TestNoChangeDecisionSkipsWrite covers two skip paths: explicit NoChange, and
// a decision whose NewTarget equals the current target. Neither should cause
// a DB write or an ephemeral data update.
func (s *ScaleManagerSuite) TestNoChangeDecisionSkipsWrite() {
inputs := make(chan PartitionScalerInput, 2)
gomock.InOrder(
s.scaler.EXPECT().OnTasks(gomock.Any()).
Do(func(in PartitionScalerInput) { inputs <- in }).
Return(PartitionScalerDecision{NoChange: true}),
s.scaler.EXPECT().OnTasks(gomock.Any()).
Do(func(in PartitionScalerInput) { inputs <- in }).
Return(PartitionScalerDecision{NewTarget: 3}), // same as current
)
var dbWrites, scalePushes atomic.Int32
s.scaleDB.EXPECT().UpdateScaleState(gomock.Any(), gomock.Any()).
DoAndReturn(func(*persistencespb.PartitionScaleState, bool) error {
dbWrites.Add(1)
return nil
}).AnyTimes()
s.userData.EXPECT().SetPartitionScale(gomock.Any()).
Do(func(*taskqueuespb.PartitionScaleInfo) { scalePushes.Add(1) }).AnyTimes()
s.startManager(4, &persistencespb.PartitionScaleState{Target: 3})
// Start pushes ephemeral data once (Read/Write change 0 → 3).
await.RequireTrue(s.T(), func() bool { return scalePushes.Load() >= 1 },
time.Second, time.Millisecond)
baseline := scalePushes.Load()
s.sm.AddedTasks(1)
waitRecv(s, inputs, "first decision never delivered")
s.sm.AddedTasks(1)
waitRecv(s, inputs, "second decision never delivered")
s.Require().Never(func() bool {
return dbWrites.Load() > 0 || scalePushes.Load() > baseline
}, 30*time.Millisecond, time.Millisecond, "unexpected DB write or ephemeral push")
}
// TestCooldown verifies that after a decision the scaler is not called again
// until 1/MaxRate of fake time has elapsed, and that batched tasks accumulate
// across the cooldown window rather than being dropped.
func (s *ScaleManagerSuite) TestCooldown() {
// MaxRate=10 → 100ms cooldown.
inputs := make(chan PartitionScalerInput, 2)
gomock.InOrder(
s.scaler.EXPECT().OnTasks(gomock.Any()).
Do(func(in PartitionScalerInput) { inputs <- in }).
Return(PartitionScalerDecision{NewTarget: 2}),
s.scaler.EXPECT().OnTasks(gomock.Any()).
Do(func(in PartitionScalerInput) { inputs <- in }).
Return(PartitionScalerDecision{NewTarget: 4}),
)
dbWrites := make(chan *persistencespb.PartitionScaleState, 2)
s.scaleDB.EXPECT().UpdateScaleState(gomock.Any(), gomock.Any()).
Do(func(state *persistencespb.PartitionScaleState, _ bool) {
dbWrites <- common.CloneProto(state)
}).
Return(nil).Times(2)
s.userData.EXPECT().SetPartitionScale(gomock.Any()).AnyTimes()
s.startManager(4, nil)
// First decision lands immediately (lastDecision is zero, no cooldown).
s.sm.AddedTasks(1)
in1 := waitRecv(s, inputs, "first decision never delivered")
s.Equal(1, in1.NumTasks)
waitRecv(s, dbWrites, "first db write missing")
// Within cooldown: another wakeup carrying 7 tasks. Scaler must NOT be
// called, and tasks must accumulate (batch is only swapped when scaler runs).
s.sm.AddedTasks(7)
assertNoRecv(s, inputs, 30*time.Millisecond, "scaler called inside cooldown")
// Advance fake time past cooldown. Trigger another wakeup; the scaler
// should now be called with the accumulated 8 tasks.
s.timeSource.Advance(110 * time.Millisecond)
s.sm.AddedTasks(1)
in2 := waitRecv(s, inputs, "second decision never delivered")
s.Equal(8, in2.NumTasks)
w2 := waitRecv(s, dbWrites, "second db write missing")
s.Equal(int32(4), w2.Target)
}
// TestPrivateStatePropagation verifies that PrivateState produced by one
// decision is fed back to the scaler on the next call (and persisted).
func (s *ScaleManagerSuite) TestPrivateStatePropagation() {
priv1 := protoutils.MarshalAny(s.T(), wrapperspb.String("after-decision-1"))
priv2 := protoutils.MarshalAny(s.T(), wrapperspb.String("after-decision-2"))
inputs := make(chan PartitionScalerInput, 2)
gomock.InOrder(
s.scaler.EXPECT().OnTasks(gomock.Any()).
Do(func(in PartitionScalerInput) { inputs <- in }).
Return(PartitionScalerDecision{NewTarget: 2, PrivateState: priv1}),
s.scaler.EXPECT().OnTasks(gomock.Any()).
Do(func(in PartitionScalerInput) { inputs <- in }).
Return(PartitionScalerDecision{NewTarget: 4, PrivateState: priv2}),
)
dbWrites := make(chan *persistencespb.PartitionScaleState, 2)
s.scaleDB.EXPECT().UpdateScaleState(gomock.Any(), gomock.Any()).
Do(func(state *persistencespb.PartitionScaleState, _ bool) {
dbWrites <- common.CloneProto(state)
}).
Return(nil).Times(2)
s.userData.EXPECT().SetPartitionScale(gomock.Any()).AnyTimes()
s.startManager(4, nil)
s.sm.AddedTasks(1)
in1 := waitRecv(s, inputs, "first call missing")
s.Nil(in1.PrivateState, "first call should have no private state")
waitRecv(s, dbWrites, "first db write missing")
s.timeSource.Advance(110 * time.Millisecond) // past 100ms cooldown
s.sm.AddedTasks(1)
in2 := waitRecv(s, inputs, "second call missing")
s.ProtoEqual(priv1, in2.PrivateState)
w2 := waitRecv(s, dbWrites, "second db write missing")
s.ProtoEqual(priv2, w2.PrivateScalerState)
}
// TestDrainClearsBacklogBits verifies that partitions which report as fully
// drained have their BacklogState bits cleared on the next periodic drain check.
func (s *ScaleManagerSuite) TestDrainClearsBacklogBits() {
s.settings.BackgroundInterval = 50 * time.Millisecond
s.settings.DrainBufferTime = 0
initial := &persistencespb.PartitionScaleState{
Target: 2,
MaxTarget: 4,
TargetVersion: 0, // fake clock starts at Unix zero
BacklogState: bitSet(nil).set(0).set(1).set(2).set(3),
}
// scaleStateToInfo for the initial state has Read=4, Write=2.
drainedResp := &matchingservice.DescribeTaskQueuePartitionResponse{
ScaleInfo: &taskqueuespb.PartitionScaleInfo{Read: 4, Write: 2, Version: 0},
VersionsInfoInternal: map[string]*taskqueuespb.TaskQueueVersionInfoInternal{
"v1": {
PhysicalTaskQueueInfo: &taskqueuespb.PhysicalTaskQueueInfo{
InternalTaskQueueStatus: []*taskqueuespb.InternalTaskQueueStatus{
{BacklogDrained: true},
},
},
},
},
}
s.matching.EXPECT().DescribeTaskQueuePartition(gomock.Any(), gomock.Any()).
Return(drainedResp, nil).AnyTimes()
// Timer-driven path also calls the scaler (with NumTasks=0). It returns
// NoChange so no decision-driven DB write happens — only the drain write.
s.scaler.EXPECT().OnTasks(gomock.Any()).
Return(PartitionScalerDecision{NoChange: true}).AnyTimes()
dbWrites := make(chan *persistencespb.PartitionScaleState, 1)
s.scaleDB.EXPECT().UpdateScaleState(gomock.Any(), gomock.Any()).
Do(func(state *persistencespb.PartitionScaleState, _ bool) {
dbWrites <- common.CloneProto(state)
}).
Return(nil)
// One ephemeral push from Start (0 → Read=4/Write=2), one from the drain (Read drops to 2).
s.userData.EXPECT().SetPartitionScale(gomock.Any()).Times(2)
s.startManager(4, initial)
s.fireBackgroundTimer()
state := waitRecv(s, dbWrites, "drain never wrote")
s.Equal(int32(2), state.Target, "target should not change during drain")
// Drained ids 2 and 3 should be cleared; ids 0 and 1 stay set (id < target).
s.Equal(int32(2), bitSet(state.BacklogState).len())
s.True(bitSet(state.BacklogState).get(0))
s.True(bitSet(state.BacklogState).get(1))
s.False(bitSet(state.BacklogState).get(2))
s.False(bitSet(state.BacklogState).get(3))
}
// TestNoDrainWithBacklog verifies that partitions still reporting backlog
// (BacklogDrained=false) keep their bits set.
func (s *ScaleManagerSuite) TestNoDrainWithBacklog() {
s.settings.BackgroundInterval = 50 * time.Millisecond
s.settings.DrainBufferTime = 0
initial := &persistencespb.PartitionScaleState{
Target: 2,
MaxTarget: 4,
TargetVersion: 0,
BacklogState: bitSet(nil).set(0).set(1).set(2).set(3),
}
notDrainedResp := &matchingservice.DescribeTaskQueuePartitionResponse{
ScaleInfo: &taskqueuespb.PartitionScaleInfo{Read: 4, Write: 2, Version: 0},
VersionsInfoInternal: map[string]*taskqueuespb.TaskQueueVersionInfoInternal{
"v1": {
PhysicalTaskQueueInfo: &taskqueuespb.PhysicalTaskQueueInfo{
InternalTaskQueueStatus: []*taskqueuespb.InternalTaskQueueStatus{
{BacklogDrained: false},
},
},
},
},
}
s.matching.EXPECT().DescribeTaskQueuePartition(gomock.Any(), gomock.Any()).
Return(notDrainedResp, nil).AnyTimes()
s.scaler.EXPECT().OnTasks(gomock.Any()).
Return(PartitionScalerDecision{NoChange: true}).AnyTimes()
// Only the push on Start (0 → Read=4/Write=2) is expected.
s.userData.EXPECT().SetPartitionScale(gomock.Any()).AnyTimes()
var dbWrites atomic.Int32
s.scaleDB.EXPECT().UpdateScaleState(gomock.Any(), gomock.Any()).
DoAndReturn(func(*persistencespb.PartitionScaleState, bool) error {
dbWrites.Add(1)
return nil
}).AnyTimes()
s.startManager(4, initial)
s.fireBackgroundTimer()
s.Require().Never(func() bool { return dbWrites.Load() > 0 },
30*time.Millisecond, time.Millisecond, "drain wrote despite backlog")
}
// TestDrainSkippedDuringBufferTime verifies that drain is not evaluated until
// DrainBufferTime has passed since the last target change.
func (s *ScaleManagerSuite) TestDrainSkippedDuringBufferTime() {
s.settings.BackgroundInterval = 50 * time.Millisecond
s.settings.DrainBufferTime = time.Hour // never elapses
initial := &persistencespb.PartitionScaleState{
Target: 2,
MaxTarget: 4,
TargetVersion: 0,
BacklogState: bitSet(nil).set(0).set(1).set(2).set(3),
}
s.matching.EXPECT().DescribeTaskQueuePartition(gomock.Any(), gomock.Any()).
Return(&matchingservice.DescribeTaskQueuePartitionResponse{}, nil).AnyTimes()
s.scaler.EXPECT().OnTasks(gomock.Any()).
Return(PartitionScalerDecision{NoChange: true}).AnyTimes()
s.userData.EXPECT().SetPartitionScale(gomock.Any()).AnyTimes()
var dbWrites atomic.Int32
s.scaleDB.EXPECT().UpdateScaleState(gomock.Any(), gomock.Any()).
DoAndReturn(func(*persistencespb.PartitionScaleState, bool) error {
dbWrites.Add(1)
return nil
}).AnyTimes()
s.startManager(4, initial)
s.fireBackgroundTimer()
s.Require().Never(func() bool { return dbWrites.Load() > 0 },
30*time.Millisecond, time.Millisecond, "drain wrote inside DrainBufferTime")
}
// TestDBWriteFailureKeepsState verifies that when persistence fails the
// in-memory state and ephemeral data are not advanced. A subsequent successful
// write should then update both.
func (s *ScaleManagerSuite) TestDBWriteFailureKeepsState() {
inputs := make(chan PartitionScalerInput, 2)
gomock.InOrder(
s.scaler.EXPECT().OnTasks(gomock.Any()).
Do(func(in PartitionScalerInput) { inputs <- in }).
Return(PartitionScalerDecision{NewTarget: 2}),
s.scaler.EXPECT().OnTasks(gomock.Any()).
Do(func(in PartitionScalerInput) { inputs <- in }).
Return(PartitionScalerDecision{NewTarget: 3}),
)
dbWrites := make(chan *persistencespb.PartitionScaleState, 1)
gomock.InOrder(
s.scaleDB.EXPECT().UpdateScaleState(gomock.Any(), gomock.Any()).
Return(errors.New("boom")),
s.scaleDB.EXPECT().UpdateScaleState(gomock.Any(), gomock.Any()).
Do(func(state *persistencespb.PartitionScaleState, _ bool) {
dbWrites <- common.CloneProto(state)
}).
Return(nil),
)
// Only the successful write triggers an ephemeral push (first attempt is
// dropped before setState).
scaleInfos := make(chan *taskqueuespb.PartitionScaleInfo, 1)
s.userData.EXPECT().SetPartitionScale(gomock.Any()).
Do(func(info *taskqueuespb.PartitionScaleInfo) { scaleInfos <- info })
s.startManager(4, nil)
// First decision: scaler called, DB write fails. lastDecision is not set,
// so cooldown is not engaged.
s.sm.AddedTasks(1)
waitRecv(s, inputs, "first decision missing")
// No ephemeral push yet; the failed write must not have advanced state.
assertNoRecv(s, scaleInfos, 30*time.Millisecond, "ephemeral data pushed after failed write")
// Recovery: trigger another decision; this one persists.
s.sm.AddedTasks(1)
waitRecv(s, inputs, "second decision missing")
state := waitRecv(s, dbWrites, "second db write missing")
s.Equal(int32(3), state.Target, "second decision's target landed (first was dropped)")
waitRecv(s, scaleInfos, "no ephemeral data push after recovery")
}

View File

@@ -102,6 +102,8 @@ type (
// rateLimitManager is used to manage the rate limit for task queues.
rateLimitManager *rateLimitManager
scaleManager *scaleManager
// loadTime tracks when this partition manager was started, used to prevent
// false positives in the no-recent-poller metric for newly loaded queues
loadTime time.Time
@@ -144,6 +146,32 @@ func newTaskQueuePartitionManager(
}
}
// create partition scaler + manager if root
var scaleManager *scaleManager
if partition.IsRoot() && e.partitionScalerFactory != nil {
partitionScaler := e.partitionScalerFactory.New(
ns.Name(),
partition.TaskQueue().Name(),
partition.TaskQueue().TaskType(),
)
if partitionScaler != nil {
baseCtx := headers.SetCallerInfo(context.Background(), headers.NewBackgroundLowCallerInfo(ns.Name().String()))
scaleManager = newScaleManager(
baseCtx,
partition,
logger,
metricsHandler,
userDataManager,
e.matchingRawClient,
partitionScaler,
e.timeSource,
tqConfig.PartitionScaleManagerSettings,
tqConfig.NumWritePartitions,
tqConfig.BreakdownMetricsByTaskQueue,
)
}
}
pm := &taskQueuePartitionManagerImpl{
engine: e,
partition: partition,
@@ -156,6 +184,7 @@ func newTaskQueuePartitionManager(
versionedQueues: make(map[PhysicalTaskQueueVersion]physicalTaskQueueManager),
userDataManager: userDataManager,
rateLimitManager: rateLimitManager,
scaleManager: scaleManager,
defaultQueueFuture: future.NewFuture[physicalTaskQueueManager](),
autoEnableRateLimiter: quotas.NewRateLimiter(1.0/60, 1),
taskHooks: taskHooks,
@@ -290,6 +319,7 @@ func (pm *taskQueuePartitionManagerImpl) Stop(unloadCause unloadCause) {
if pm.cancelAutoEnableSub != nil {
pm.cancelAutoEnableSub()
}
pm.scaleManager.Stop()
pm.versionedQueuesLock.Lock()
for version, vq := range pm.versionedQueues {
@@ -313,7 +343,13 @@ func (pm *taskQueuePartitionManagerImpl) Stop(unloadCause unloadCause) {
pm.goroGroup.Cancel()
}
func (pm *taskQueuePartitionManagerImpl) checkPartitionCounts(ctx context.Context, forWrite, forwarded bool) error {
func (pm *taskQueuePartitionManagerImpl) StartScaleManager(scaleState *persistencespb.PartitionScaleState) {
// Note that this must be called before defaultQueue is marked initialized!
// Otherwise child partitions will see empty scale info in their first ephemeral data update.
pm.scaleManager.Start(scaleState, pm.defaultQueue())
}
func (pm *taskQueuePartitionManagerImpl) checkPartitionCounts(ctx context.Context, forWrite bool) error {
normal, ok := pm.partition.(*tqid.NormalPartition)
if !ok {
return nil // only normal partitions do dynamic scaling
@@ -392,6 +428,26 @@ func validatePartitionScaleDrift(
return errPartitionCountsStale
}
// signalPartitionScaler sends a signal to the partition scaler that a new task has arrived
// (directly from history, not forwarded).
func (pm *taskQueuePartitionManagerImpl) signalPartitionScaler() {
if pm.scaleManager == nil {
return // only run on root partition
}
scaleInfo := pm.userDataManager.PartitionScale()
effectiveWrite := int(scaleInfo.GetWrite())
// if no target is set yet, get effective count from dynamic config (matches client behavior)
if effectiveWrite == 0 {
effectiveWrite = max(1, pm.config.NumWritePartitions())
}
// we assume that tasks are balanced uniformly across partitions, so if the root has
// seen 1 task then all have seen ~1 task, so the whole queue has seen 'effective'
// tasks in total.
// TODO(dp): this will change when we add non-uniform load balancing. we should eventually
// aggregate real stats instead of assuming
pm.scaleManager.AddedTasks(effectiveWrite)
}
func (pm *taskQueuePartitionManagerImpl) sendPartitionCountTrailer(ctx context.Context) {
// note this sends the trailer even if there is no scale info (i.e. dynamic partition
// scaling is not enabled). that will instruct clients to fall back to dynamic config.
@@ -401,7 +457,7 @@ func (pm *taskQueuePartitionManagerImpl) sendPartitionCountTrailer(ctx context.C
Write: scaleInfo.GetWrite(),
}.SetTrailer(ctx)
if err != nil {
// TODO: this is very noisy in unit tests, figure out how to log it only in non-test
// TODO(dp): this is very noisy in unit tests, figure out how to log it only in non-test
pm.throttledLogger.Debug("error setting partition count trailer", tag.Error(err))
}
}
@@ -495,9 +551,12 @@ func (pm *taskQueuePartitionManagerImpl) AddTask(
params addTaskParams,
) (buildId string, syncMatched bool, err error) {
defer pm.sendPartitionCountTrailer(ctx)
if err := pm.checkPartitionCounts(ctx, true, params.forwardInfo != nil); err != nil {
if err := pm.checkPartitionCounts(ctx, true); err != nil {
return "", false, err
}
if params.forwardInfo == nil {
pm.signalPartitionScaler()
}
var spoolQueue, syncMatchQueue physicalTaskQueueManager
directive := params.taskInfo.GetVersionDirective()
@@ -656,7 +715,7 @@ func (pm *taskQueuePartitionManagerImpl) PollTask(
pollMetadata *pollMetadata,
) (*internalTask, bool, error) {
defer pm.sendPartitionCountTrailer(ctx)
if err := pm.checkPartitionCounts(ctx, false, pollMetadata.forwardedFrom != ""); err != nil {
if err := pm.checkPartitionCounts(ctx, false); err != nil {
return nil, false, err
}
@@ -935,11 +994,15 @@ func (pm *taskQueuePartitionManagerImpl) DispatchQueryTask(
taskID string,
request *matchingservice.QueryWorkflowRequest,
) (*matchingservice.QueryWorkflowResponse, error) {
// query counts as "write" for partition load balancing
// TODO(dp): if this partition becomes invalid while the query is blocked, we should cancel the match
defer pm.sendPartitionCountTrailer(ctx)
if err := pm.checkPartitionCounts(ctx, true, request.ForwardInfo != nil); err != nil {
// query counts as "write" for partition load balancing
if err := pm.checkPartitionCounts(ctx, true); err != nil {
return nil, err
}
if request.ForwardInfo == nil {
pm.signalPartitionScaler()
}
task := newInternalQueryTask(taskID, request)
pm.config.setDefaultPriority(task)
@@ -983,11 +1046,15 @@ func (pm *taskQueuePartitionManagerImpl) DispatchNexusTask(
taskId string,
request *matchingservice.DispatchNexusTaskRequest,
) (*matchingservice.DispatchNexusTaskResponse, error) {
// nexus counts as "write" for partition load balancing
// TODO(dp): if this partition becomes invalid while the query is blocked, we should cancel the match
defer pm.sendPartitionCountTrailer(ctx)
if err := pm.checkPartitionCounts(ctx, true, request.ForwardInfo != nil); err != nil {
// nexus counts as "write" for partition load balancing
if err := pm.checkPartitionCounts(ctx, true); err != nil {
return nil, err
}
if request.ForwardInfo == nil {
pm.signalPartitionScaler()
}
deadline, _ := ctx.Deadline() // If not set by user, our client will set a default.
var opDeadline time.Time
@@ -1374,6 +1441,7 @@ func (pm *taskQueuePartitionManagerImpl) describe(
return &matchingservice.DescribeTaskQueuePartitionResponse{
VersionsInfoInternal: versionsInfo,
ScaleInfo: pm.userDataManager.PartitionScale(),
}, nil
}
@@ -1775,6 +1843,7 @@ func (pm *taskQueuePartitionManagerImpl) callerInfoContext(ctx context.Context)
}
// ForceLoadAllChildPartitions force-loads known child (read) partitions in new goroutines.
// TODO(dp): consider moving this into scaleManager.backgroundWork after auto-scaling is enabled everywhere.
func (pm *taskQueuePartitionManagerImpl) ForceLoadAllChildPartitions() {
if !pm.partition.IsRoot() {
return

View File

@@ -1,3 +1,5 @@
//go:generate mockgen -package $GOPACKAGE -source $GOFILE -destination user_data_manager_mock.go
package matching
import (

View File

@@ -0,0 +1,180 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: user_data_manager.go
//
// Generated by this command:
//
// mockgen -package matching -source user_data_manager.go -destination user_data_manager_mock.go
//
// Package matching is a generated GoMock package.
package matching
import (
context "context"
reflect "reflect"
matchingservice "go.temporal.io/server/api/matchingservice/v1"
persistence "go.temporal.io/server/api/persistence/v1"
taskqueue "go.temporal.io/server/api/taskqueue/v1"
gomock "go.uber.org/mock/gomock"
)
// MockuserDataManager is a mock of userDataManager interface.
type MockuserDataManager struct {
ctrl *gomock.Controller
recorder *MockuserDataManagerMockRecorder
isgomock struct{}
}
// MockuserDataManagerMockRecorder is the mock recorder for MockuserDataManager.
type MockuserDataManagerMockRecorder struct {
mock *MockuserDataManager
}
// NewMockuserDataManager creates a new mock instance.
func NewMockuserDataManager(ctrl *gomock.Controller) *MockuserDataManager {
mock := &MockuserDataManager{ctrl: ctrl}
mock.recorder = &MockuserDataManagerMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockuserDataManager) EXPECT() *MockuserDataManagerMockRecorder {
return m.recorder
}
// CheckTaskQueueUserDataPropagation mocks base method.
func (m *MockuserDataManager) CheckTaskQueueUserDataPropagation(arg0 context.Context, arg1 int64, arg2, arg3 int) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CheckTaskQueueUserDataPropagation", arg0, arg1, arg2, arg3)
ret0, _ := ret[0].(error)
return ret0
}
// CheckTaskQueueUserDataPropagation indicates an expected call of CheckTaskQueueUserDataPropagation.
func (mr *MockuserDataManagerMockRecorder) CheckTaskQueueUserDataPropagation(arg0, arg1, arg2, arg3 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CheckTaskQueueUserDataPropagation", reflect.TypeOf((*MockuserDataManager)(nil).CheckTaskQueueUserDataPropagation), arg0, arg1, arg2, arg3)
}
// GetUserData mocks base method.
func (m *MockuserDataManager) GetUserData() (*persistence.VersionedTaskQueueUserData, chan struct{}, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetUserData")
ret0, _ := ret[0].(*persistence.VersionedTaskQueueUserData)
ret1, _ := ret[1].(chan struct{})
ret2, _ := ret[2].(error)
return ret0, ret1, ret2
}
// GetUserData indicates an expected call of GetUserData.
func (mr *MockuserDataManagerMockRecorder) GetUserData() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUserData", reflect.TypeOf((*MockuserDataManager)(nil).GetUserData))
}
// HandleGetUserDataRequest mocks base method.
func (m *MockuserDataManager) HandleGetUserDataRequest(ctx context.Context, req *matchingservice.GetTaskQueueUserDataRequest) (*matchingservice.GetTaskQueueUserDataResponse, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "HandleGetUserDataRequest", ctx, req)
ret0, _ := ret[0].(*matchingservice.GetTaskQueueUserDataResponse)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// HandleGetUserDataRequest indicates an expected call of HandleGetUserDataRequest.
func (mr *MockuserDataManagerMockRecorder) HandleGetUserDataRequest(ctx, req any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HandleGetUserDataRequest", reflect.TypeOf((*MockuserDataManager)(nil).HandleGetUserDataRequest), ctx, req)
}
// LocalBacklogPriorityChanged mocks base method.
func (m *MockuserDataManager) LocalBacklogPriorityChanged(arg0 map[PhysicalTaskQueueVersion]int64) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "LocalBacklogPriorityChanged", arg0)
}
// LocalBacklogPriorityChanged indicates an expected call of LocalBacklogPriorityChanged.
func (mr *MockuserDataManagerMockRecorder) LocalBacklogPriorityChanged(arg0 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LocalBacklogPriorityChanged", reflect.TypeOf((*MockuserDataManager)(nil).LocalBacklogPriorityChanged), arg0)
}
// PartitionScale mocks base method.
func (m *MockuserDataManager) PartitionScale() *taskqueue.PartitionScaleInfo {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "PartitionScale")
ret0, _ := ret[0].(*taskqueue.PartitionScaleInfo)
return ret0
}
// PartitionScale indicates an expected call of PartitionScale.
func (mr *MockuserDataManagerMockRecorder) PartitionScale() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PartitionScale", reflect.TypeOf((*MockuserDataManager)(nil).PartitionScale))
}
// SetPartitionScale mocks base method.
func (m *MockuserDataManager) SetPartitionScale(arg0 *taskqueue.PartitionScaleInfo) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "SetPartitionScale", arg0)
}
// SetPartitionScale indicates an expected call of SetPartitionScale.
func (mr *MockuserDataManagerMockRecorder) SetPartitionScale(arg0 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetPartitionScale", reflect.TypeOf((*MockuserDataManager)(nil).SetPartitionScale), arg0)
}
// Start mocks base method.
func (m *MockuserDataManager) Start() {
m.ctrl.T.Helper()
m.ctrl.Call(m, "Start")
}
// Start indicates an expected call of Start.
func (mr *MockuserDataManagerMockRecorder) Start() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Start", reflect.TypeOf((*MockuserDataManager)(nil).Start))
}
// Stop mocks base method.
func (m *MockuserDataManager) Stop() {
m.ctrl.T.Helper()
m.ctrl.Call(m, "Stop")
}
// Stop indicates an expected call of Stop.
func (mr *MockuserDataManagerMockRecorder) Stop() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Stop", reflect.TypeOf((*MockuserDataManager)(nil).Stop))
}
// UpdateUserData mocks base method.
func (m *MockuserDataManager) UpdateUserData(ctx context.Context, options UserDataUpdateOptions, updateFn UserDataUpdateFunc) (int64, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UpdateUserData", ctx, options, updateFn)
ret0, _ := ret[0].(int64)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// UpdateUserData indicates an expected call of UpdateUserData.
func (mr *MockuserDataManagerMockRecorder) UpdateUserData(ctx, options, updateFn any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateUserData", reflect.TypeOf((*MockuserDataManager)(nil).UpdateUserData), ctx, options, updateFn)
}
// WaitUntilInitialized mocks base method.
func (m *MockuserDataManager) WaitUntilInitialized(ctx context.Context) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "WaitUntilInitialized", ctx)
ret0, _ := ret[0].(error)
return ret0
}
// WaitUntilInitialized indicates an expected call of WaitUntilInitialized.
func (mr *MockuserDataManagerMockRecorder) WaitUntilInitialized(ctx any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WaitUntilInitialized", reflect.TypeOf((*MockuserDataManager)(nil).WaitUntilInitialized), ctx)
}