Files
1e31c76ed1 Versioning cherry-pick for OSS v1.30.2 (#9543)
## What changed?
Cherry-pick versioning PRs
- #9168
  - Cache for system protection
- #9262 
  - Cache for system protection
- #9239 
- Critical PR to enable sending `TargetVersionChanged` flag for
Upgrade-on-CaN feature
- #9147 
- Tracks version drainage properly when version receives workflows via
`VersioningOverride`. Needed for automated worker controllers to
correctly scale versioned workers that received workflows via
`VersioningOverride`.
- #9300 
- Needed for `approximate_backlog_count` metric to track Current and
Ramping version tasks correctly
- #9316 
- Needed for `approximate_backlog_count` metric to track Current and
Ramping version tasks correctly
- #8957 
- Contains minor metric improvement. Included because it adds a test
harness that is used in the two metrics PRs above
- #9250 
  - Bug fix of task rescheduling edge case during AutoUpgrade Transition

## Why?
For OSS v1.30.2

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





---

> [!NOTE]
> **Medium Risk**
> Touches history/matching worker-versioning paths, adding new caches
and changing workflow task/start handling and backlog metric emission;
incorrect caching or signaling could affect dispatch/upgrade behavior
and observability.
> 
> **Overview**
> Adds new worker-versioning protections and upgrade signaling: workflow
task started events now persist a
`workflow_task_target_worker_deployment_version_changed` flag (and emit
a new `workflow_target_version_changed_count` metric) under a new
`EnableSendTargetVersionChanged` dynamic config.
> 
> Introduces two new caches with metrics and dynamic config knobs: a
`RoutingInfoCache` to avoid repeated `GetTaskQueueUserData` lookups
during activity start/transition logic, and a `ReactivationSignalCache`
plus `EnableVersionReactivationSignals` to dedupe and asynchronously
send “reactivation” signals when workflows are pinned (via
start/signal-with-start/reset/update-options) to potentially
drained/inactive worker versions.
> 
> Extends matching backlog metrics to support version-attributed
reporting by adding `BacklogMetricsEmitInterval` and switching queue DB
emission to *physical* backlog gauges (`physical_approximate_backlog_*`)
when attribution is enabled, while keeping legacy gauges when disabled.
> 
> Adds frontend scaffolding for a new visibility RPC `CountSchedules`
(client plumbing, interception/metadata/quota wiring) but leaves the
frontend handler unimplemented, and bumps `go.temporal.io/api` to
`v1.62.2`.
> 
> <sup>Written by [Cursor
Bugbot]() for commit
cb8ae14c86666654c51dd66ae91cb225722e1ba7. This will update automatically
on new commits. Configure
[here]().</sup>

---------

Co-authored-by: Shahab Tajik <[email protected]>
Co-authored-by: Shivam <[email protected]>
2026-03-17 17:19:47 -05:00

419 lines
14 KiB
Go

package history
import (
"context"
"time"
"go.temporal.io/server/api/historyservice/v1"
"go.temporal.io/server/chasm"
"go.temporal.io/server/chasm/lib/activity"
"go.temporal.io/server/common"
commoncache "go.temporal.io/server/common/cache"
"go.temporal.io/server/common/clock"
"go.temporal.io/server/common/config"
"go.temporal.io/server/common/dynamicconfig"
"go.temporal.io/server/common/log"
"go.temporal.io/server/common/log/tag"
"go.temporal.io/server/common/membership"
"go.temporal.io/server/common/metrics"
"go.temporal.io/server/common/namespace"
persistenceClient "go.temporal.io/server/common/persistence/client"
"go.temporal.io/server/common/persistence/visibility"
"go.temporal.io/server/common/persistence/visibility/manager"
"go.temporal.io/server/common/persistence/visibility/store/elasticsearch"
"go.temporal.io/server/common/primitives"
"go.temporal.io/server/common/quotas/calculator"
"go.temporal.io/server/common/resolver"
"go.temporal.io/server/common/resource"
"go.temporal.io/server/common/rpc/interceptor"
"go.temporal.io/server/common/searchattribute"
"go.temporal.io/server/common/tasktoken"
"go.temporal.io/server/common/worker_versioning"
"go.temporal.io/server/components/callbacks"
"go.temporal.io/server/components/nexusoperations"
nexusworkflow "go.temporal.io/server/components/nexusoperations/workflow"
"go.temporal.io/server/service"
"go.temporal.io/server/service/history/api"
"go.temporal.io/server/service/history/archival"
"go.temporal.io/server/service/history/configs"
"go.temporal.io/server/service/history/consts"
"go.temporal.io/server/service/history/events"
"go.temporal.io/server/service/history/hsm"
"go.temporal.io/server/service/history/replication"
"go.temporal.io/server/service/history/shard"
"go.temporal.io/server/service/history/workflow"
"go.temporal.io/server/service/history/workflow/cache"
"go.temporal.io/server/service/worker/workerdeployment"
"go.uber.org/fx"
"google.golang.org/grpc"
healthpb "google.golang.org/grpc/health/grpc_health_v1"
)
var Module = fx.Options(
resource.Module,
fx.Provide(hsm.NewRegistry),
workflow.Module,
shard.Module,
events.Module,
cache.Module,
archival.Module,
ChasmEngineModule,
fx.Provide(ConfigProvider), // might be worth just using provider for configs.Config directly
fx.Provide(workflow.NewCommandHandlerRegistry),
fx.Provide(RetryableInterceptorProvider),
fx.Provide(ErrorHandlerProvider),
fx.Provide(TelemetryInterceptorProvider),
fx.Provide(RateLimitInterceptorProvider),
fx.Provide(HealthSignalAggregatorProvider),
fx.Provide(HealthCheckInterceptorProvider),
fx.Provide(chasm.ChasmEngineInterceptorProvider),
fx.Provide(chasm.ChasmVisibilityInterceptorProvider),
fx.Provide(HistoryAdditionalInterceptorsProvider),
fx.Provide(service.GrpcServerOptionsProvider),
fx.Provide(ESProcessorConfigProvider),
fx.Provide(VisibilityManagerProvider),
fx.Provide(visibility.ChasmVisibilityManagerProvider),
fx.Provide(ThrottledLoggerRpsFnProvider),
fx.Provide(PersistenceRateLimitingParamsProvider),
service.PersistenceLazyLoadedServiceResolverModule,
fx.Provide(ServiceResolverProvider),
fx.Provide(EventNotifierProvider),
fx.Provide(HistoryEngineFactoryProvider),
fx.Provide(HandlerProvider),
fx.Provide(ServerProvider),
fx.Provide(NewService),
fx.Provide(ReplicationProgressCacheProvider),
fx.Provide(VersionMembershipCacheProvider),
fx.Provide(ReactivationSignalCacheProvider),
fx.Provide(workerdeployment.ClientProvider),
fx.Provide(RoutingInfoCacheProvider),
fx.Invoke(ServiceLifetimeHooks),
callbacks.Module,
nexusoperations.Module,
fx.Invoke(nexusworkflow.RegisterCommandHandlers),
activity.HistoryModule,
)
func ServerProvider(grpcServerOptions []grpc.ServerOption) *grpc.Server {
return grpc.NewServer(grpcServerOptions...)
}
func ServiceResolverProvider(
membershipMonitor membership.Monitor,
) (membership.ServiceResolver, error) {
return membershipMonitor.GetResolver(primitives.HistoryService)
}
func HandlerProvider(args NewHandlerArgs) *Handler {
handler := &Handler{
status: common.DaemonStatusInitialized,
config: args.Config,
tokenSerializer: tasktoken.NewSerializer(),
logger: args.Logger,
throttledLogger: args.ThrottledLogger,
persistenceExecutionManager: args.PersistenceExecutionManager,
persistenceShardManager: args.PersistenceShardManager,
persistenceVisibilityManager: args.PersistenceVisibilityManager,
persistenceHealthSignal: args.PersistenceHealthSignal,
healthServer: args.HealthServer,
historyHealthSignal: args.HistoryHealthSignal,
historyServiceResolver: args.HistoryServiceResolver,
metricsHandler: args.MetricsHandler,
payloadSerializer: args.PayloadSerializer,
timeSource: args.TimeSource,
namespaceRegistry: args.NamespaceRegistry,
saProvider: args.SaProvider,
clusterMetadata: args.ClusterMetadata,
archivalMetadata: args.ArchivalMetadata,
hostInfoProvider: args.HostInfoProvider,
controller: args.ShardController,
eventNotifier: args.EventNotifier,
tracer: args.TracerProvider.Tracer(consts.LibraryName),
taskQueueManager: args.TaskQueueManager,
taskCategoryRegistry: args.TaskCategoryRegistry,
dlqMetricsEmitter: args.DLQMetricsEmitter,
chasmEngine: args.ChasmEngine,
chasmRegistry: args.ChasmRegistry,
replicationTaskFetcherFactory: args.ReplicationTaskFetcherFactory,
replicationTaskConverterProvider: args.ReplicationTaskConverterFactory,
streamReceiverMonitor: args.StreamReceiverMonitor,
replicationServerRateLimiter: args.ReplicationServerRateLimiter,
}
return handler
}
func HistoryEngineFactoryProvider(
params HistoryEngineFactoryParams,
) shard.EngineFactory {
return &historyEngineFactory{
HistoryEngineFactoryParams: params,
}
}
func ConfigProvider(
dc *dynamicconfig.Collection,
persistenceConfig config.Persistence,
) *configs.Config {
return configs.NewConfig(
dc,
persistenceConfig.NumHistoryShards,
)
}
func ThrottledLoggerRpsFnProvider(serviceConfig *configs.Config) resource.ThrottledLoggerRpsFn {
return func() float64 { return float64(serviceConfig.ThrottledLogRPS()) }
}
func RetryableInterceptorProvider() *interceptor.RetryableInterceptor {
return interceptor.NewRetryableInterceptor(
common.CreateHistoryHandlerRetryPolicy(),
api.IsRetryableError,
)
}
func ErrorHandlerProvider(
logger log.Logger,
serviceConfig *configs.Config,
) *interceptor.RequestErrorHandler {
return interceptor.NewRequestErrorHandler(
logger,
serviceConfig.LogAllReqErrors,
)
}
func TelemetryInterceptorProvider(
logger log.Logger,
namespaceRegistry namespace.Registry,
metricsHandler metrics.Handler,
serviceConfig *configs.Config,
requestErrorHandler *interceptor.RequestErrorHandler,
) *interceptor.TelemetryInterceptor {
return interceptor.NewTelemetryInterceptor(
namespaceRegistry,
metricsHandler,
logger,
serviceConfig.LogAllReqErrors,
requestErrorHandler,
)
}
func HealthSignalAggregatorProvider(
dynamicCollection *dynamicconfig.Collection,
logger log.ThrottledLogger,
) interceptor.HealthSignalAggregator {
return interceptor.NewHealthSignalAggregator(
logger,
dynamicconfig.HistoryHealthSignalMetricsEnabled.Get(dynamicCollection),
dynamicconfig.PersistenceHealthSignalWindowSize.Get(dynamicCollection)(),
dynamicconfig.PersistenceHealthSignalBufferSize.Get(dynamicCollection)(),
)
}
func HealthCheckInterceptorProvider(
healthSignalAggregator interceptor.HealthSignalAggregator,
) *interceptor.HealthCheckInterceptor {
return interceptor.NewHealthCheckInterceptor(
healthSignalAggregator,
)
}
func HistoryAdditionalInterceptorsProvider(
healthCheckInterceptor *interceptor.HealthCheckInterceptor,
chasmRequestEngineInterceptor *chasm.ChasmEngineInterceptor,
chasmRequestVisibilityInterceptor *chasm.ChasmVisibilityInterceptor,
) []grpc.UnaryServerInterceptor {
return []grpc.UnaryServerInterceptor{
healthCheckInterceptor.UnaryIntercept,
chasmRequestEngineInterceptor.Intercept,
chasmRequestVisibilityInterceptor.Intercept,
}
}
func RateLimitInterceptorProvider(
serviceConfig *configs.Config,
) *interceptor.RateLimitInterceptor {
return interceptor.NewRateLimitInterceptor(
configs.NewPriorityRateLimiter(func() float64 { return float64(serviceConfig.RPS()) }, serviceConfig.OperatorRPSRatio),
map[string]int{
healthpb.Health_Check_FullMethodName: 0, // exclude health check requests from rate limiting.
historyservice.HistoryService_DeepHealthCheck_FullMethodName: 0, // exclude deep health check requests from rate limiting.
},
)
}
func ESProcessorConfigProvider(
serviceConfig *configs.Config,
) *elasticsearch.ProcessorConfig {
return &elasticsearch.ProcessorConfig{
IndexerConcurrency: serviceConfig.IndexerConcurrency,
ESProcessorNumOfWorkers: serviceConfig.ESProcessorNumOfWorkers,
ESProcessorBulkActions: serviceConfig.ESProcessorBulkActions,
ESProcessorBulkSize: serviceConfig.ESProcessorBulkSize,
ESProcessorFlushInterval: serviceConfig.ESProcessorFlushInterval,
ESProcessorAckTimeout: serviceConfig.ESProcessorAckTimeout,
}
}
func PersistenceRateLimitingParamsProvider(
serviceConfig *configs.Config,
persistenceLazyLoadedServiceResolver service.PersistenceLazyLoadedServiceResolver,
ownershipBasedQuotaScaler shard.LazyLoadedOwnershipBasedQuotaScaler,
logger log.SnTaggedLogger,
) service.PersistenceRateLimitingParams {
hostCalculator := calculator.NewLoggedCalculator(
shard.NewOwnershipAwareQuotaCalculator(
ownershipBasedQuotaScaler,
persistenceLazyLoadedServiceResolver,
serviceConfig.PersistenceMaxQPS,
serviceConfig.PersistenceGlobalMaxQPS,
),
log.With(logger, tag.ComponentPersistence, tag.ScopeHost),
)
namespaceCalculator := calculator.NewLoggedNamespaceCalculator(
shard.NewOwnershipAwareNamespaceQuotaCalculator(
ownershipBasedQuotaScaler,
persistenceLazyLoadedServiceResolver,
serviceConfig.PersistenceNamespaceMaxQPS,
serviceConfig.PersistenceGlobalNamespaceMaxQPS,
),
log.With(logger, tag.ComponentPersistence, tag.ScopeNamespace),
)
return service.PersistenceRateLimitingParams{
PersistenceMaxQps: func() int {
return int(hostCalculator.GetQuota())
},
PersistenceNamespaceMaxQps: func(namespace string) int {
return int(namespaceCalculator.GetQuota(namespace))
},
PersistencePerShardNamespaceMaxQPS: persistenceClient.PersistencePerShardNamespaceMaxQPS(serviceConfig.PersistencePerShardNamespaceMaxQPS),
OperatorRPSRatio: persistenceClient.OperatorRPSRatio(serviceConfig.OperatorRPSRatio),
PersistenceBurstRatio: persistenceClient.PersistenceBurstRatio(serviceConfig.PersistenceQPSBurstRatio),
DynamicRateLimitingParams: persistenceClient.DynamicRateLimitingParams(serviceConfig.PersistenceDynamicRateLimitingParams),
}
}
func VisibilityManagerProvider(
logger log.Logger,
metricsHandler metrics.Handler,
persistenceConfig *config.Persistence,
customVisibilityStoreFactory visibility.VisibilityStoreFactory,
esProcessorConfig *elasticsearch.ProcessorConfig,
serviceConfig *configs.Config,
persistenceServiceResolver resolver.ServiceResolver,
searchAttributesMapperProvider searchattribute.MapperProvider,
saProvider searchattribute.Provider,
namespaceRegistry namespace.Registry,
chasmRegistry *chasm.Registry,
) (manager.VisibilityManager, error) {
return visibility.NewManager(
*persistenceConfig,
persistenceServiceResolver,
customVisibilityStoreFactory,
esProcessorConfig,
saProvider,
searchAttributesMapperProvider,
namespaceRegistry,
chasmRegistry,
serviceConfig.VisibilityPersistenceMaxReadQPS,
serviceConfig.VisibilityPersistenceMaxWriteQPS,
serviceConfig.OperatorRPSRatio,
serviceConfig.VisibilityPersistenceSlowQueryThreshold,
serviceConfig.EnableReadFromSecondaryVisibility,
serviceConfig.VisibilityEnableShadowReadMode,
serviceConfig.SecondaryVisibilityWritingMode,
serviceConfig.VisibilityDisableOrderByClause,
serviceConfig.VisibilityEnableManualPagination,
serviceConfig.VisibilityEnableUnifiedQueryConverter,
metricsHandler,
logger,
)
}
func ChasmVisibilityManagerProvider(
chasmRegistry *chasm.Registry,
visibilityManager manager.VisibilityManager,
) chasm.VisibilityManager {
return visibility.NewChasmVisibilityManager(
chasmRegistry,
visibilityManager,
)
}
func EventNotifierProvider(
timeSource clock.TimeSource,
metricsHandler metrics.Handler,
config *configs.Config,
) events.Notifier {
return events.NewNotifier(
timeSource,
metricsHandler,
config.GetShardID,
)
}
func ServiceLifetimeHooks(lc fx.Lifecycle, svc *Service) {
lc.Append(fx.StartStopHook(svc.Start, svc.Stop))
}
func ReplicationProgressCacheProvider(
serviceConfig *configs.Config,
logger log.Logger,
handler metrics.Handler,
) replication.ProgressCache {
return replication.NewProgressCache(serviceConfig, logger, handler)
}
func VersionMembershipCacheProvider(
lc fx.Lifecycle,
serviceConfig *configs.Config,
metricsHandler metrics.Handler,
) worker_versioning.VersionMembershipCache {
c := commoncache.New(serviceConfig.VersionMembershipCacheMaxSize(), &commoncache.Options{
TTL: max(1*time.Second, serviceConfig.VersionMembershipCacheTTL()),
})
lc.Append(fx.Hook{
OnStop: func(context.Context) error {
c.Stop()
return nil
},
})
return worker_versioning.NewVersionMembershipCache(c, metricsHandler)
}
func ReactivationSignalCacheProvider(
lc fx.Lifecycle,
serviceConfig *configs.Config,
metricsHandler metrics.Handler,
) worker_versioning.ReactivationSignalCache {
c := commoncache.New(serviceConfig.VersionReactivationSignalCacheMaxSize(), &commoncache.Options{
TTL: max(1*time.Second, serviceConfig.VersionReactivationSignalCacheTTL()),
})
lc.Append(fx.Hook{
OnStop: func(context.Context) error {
c.Stop()
return nil
},
})
return worker_versioning.NewReactivationSignalCache(c, metricsHandler)
}
func RoutingInfoCacheProvider(
lc fx.Lifecycle,
serviceConfig *configs.Config,
metricsHandler metrics.Handler,
) worker_versioning.RoutingInfoCache {
c := commoncache.New(serviceConfig.RoutingInfoCacheMaxSize(), &commoncache.Options{
TTL: max(1*time.Second, serviceConfig.RoutingInfoCacheTTL()),
})
lc.Append(fx.Hook{
OnStop: func(context.Context) error {
c.Stop()
return nil
},
})
return worker_versioning.NewRoutingInfoCache(c, metricsHandler)
}