You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
WorkerResourceTemplate renders an identical autoscaler for every version — the template cannot express anything conditioned on version state. With the KEDA ScaledObject flavor (v1.8.0, #351) this forces a lose-lose choice for minReplicaCount:
minReplicaCount >= 1 — KEDA enforces the floor on every version, including Drained ones (KEDA applies minReplicaCount regardless of trigger activity). The controller force-zeroes drained versions at sunset ("Drained versions and inactive versions that are not the rollout target are always scaled to zero during sunset"), so keda-operator and the controller fight a ~30s scale-up/scale-down write war per drained version — observed live, same family as [Bug] HorizontalPodAutoscaler continuously rescales due to controller interference #350. Worse: getDeleteDeployments requires replicas == 0 before deleting, so a floored autoscaler blocks version Deployment deletion indefinitely.
minReplicaCount: 0 — the war disappears, but the Current version scales to zero too. The scaler's metric is ApproximateBacklogCount only; a keeping-up worker reads backlog ≈ 0 at nearly every 30s poll, so one cooldownPeriod of samples that miss the bursts parks the Current version, and the next task pays a ~2min cold start (poll interval + pod boot). Measured on our production queues over 14 days: a bursty queue had 70+ windows of ≥60 min with zero visible backlog (parks ~5×/day at the 3600s cooldown we run); even a queue with ~1.5k tasks/hour parked several times a day purely via sampling-miss. Related failure mode: a version deployed while traffic is idle is held at 0 pollers and can fail promotion ([Bug] New version can get stuck INACTIVE and never promote when a scale-to-zero autoscaler holds its Deployment at 0 replicas #438).
The semantic actually needed is state-aware:
minReplicas: N while the version is Current (or Ramping); 0 once it is Draining/Drained.
Verified nothing supports this today: v1.8.0 RenderWorkerResourceTemplate passes only (wrt, deployment, buildID, temporalNamespace) — no version state — and the KEDA temporal scaler (pkg/scalers/temporal_scaler.go, v2.20.1 == main) contains zero references to version status/drainage.
The data already exists in the scaler's response
In deployment-version mode the KEDA scaler calls DescribeWorkerDeploymentVersion with ReportTaskQueueStats: true and reads only version_task_queues. The same response carries worker_deployment_version_info — including status (CURRENT / RAMPING / DRAINING / DRAINED), ramp and drainage info — which is currently discarded.
Options
A. KEDA scaler (smallest change, covers floor = 1): an opt-in trigger param (e.g. activateOnCurrentVersion: true) that reports the trigger active whenever the version status is CURRENT/RAMPING, regardless of backlog. With minReplicaCount: 0, activation + the HPA's implicit minReplicas: 1 keep the Current version warm; Draining/Drained versions activate only on real backlog and rest at 0, so the controller's sunset is unopposed. Uses data the scaler already fetches. (Happy to file this on kedacore/keda — flagging here first since this repo owns the versioning semantics; note KEDA declined an adjacent ask in kedacore/keda#7368.)
B. Controller (general, covers floor = N): per-version-state rendering/patching — e.g. a currentVersionMinReplicas field or per-state template overrides, where the controller sets minReplicaCount on the Current version's ScaledObject and removes it on the transition to Draining. This is essentially #104 ("Per-Version-Status min/max Replicas" — closed, but the knob never shipped) and fits the requirements list in #14 ("Scale independently per-version", "Scale to 0 after drain", "Preemptively scale up target version before cutting over"); the same mechanism applied to the rollout target version would also address #438.
C. Composite activity metrics (kedacore/keda#7459) or per-version metric-label templating (#355) mitigate the premature-park half but not the drained-floor half.
Workaround we run meanwhile
minReplicaCount: 0 + a very long cooldownPeriod (86400): the Current version effectively never parks (any active sample resets the clock), and Drained versions are unaffected because the controller zeroes them at scaledownDelay and KEDA never scales up without activation — cooldownPeriod gates only KEDA's own scale-down. Verified live: with scaledownDelay (15m) far below the cooldown, rollouts zero freshly-drained versions with unexpired cooldowns and no flap-back occurs. Cost: Draining versions holding open pinned workflows keep one warm pod up to 24h after their last straggler task.
Problem
WorkerResourceTemplaterenders an identical autoscaler for every version — the template cannot express anything conditioned on version state. With the KEDA ScaledObject flavor (v1.8.0, #351) this forces a lose-lose choice forminReplicaCount:minReplicaCount >= 1— KEDA enforces the floor on every version, including Drained ones (KEDA appliesminReplicaCountregardless of trigger activity). The controller force-zeroes drained versions at sunset ("Drained versions and inactive versions that are not the rollout target are always scaled to zero during sunset"), so keda-operator and the controller fight a ~30s scale-up/scale-down write war per drained version — observed live, same family as [Bug] HorizontalPodAutoscaler continuously rescales due to controller interference #350. Worse:getDeleteDeploymentsrequiresreplicas == 0before deleting, so a floored autoscaler blocks version Deployment deletion indefinitely.minReplicaCount: 0— the war disappears, but the Current version scales to zero too. The scaler's metric isApproximateBacklogCountonly; a keeping-up worker reads backlog ≈ 0 at nearly every 30s poll, so onecooldownPeriodof samples that miss the bursts parks the Current version, and the next task pays a ~2min cold start (poll interval + pod boot). Measured on our production queues over 14 days: a bursty queue had 70+ windows of ≥60 min with zero visible backlog (parks ~5×/day at the 3600s cooldown we run); even a queue with ~1.5k tasks/hour parked several times a day purely via sampling-miss. Related failure mode: a version deployed while traffic is idle is held at 0 pollers and can fail promotion ([Bug] New version can get stuck INACTIVE and never promote when a scale-to-zero autoscaler holds its Deployment at 0 replicas #438).The semantic actually needed is state-aware:
Verified nothing supports this today: v1.8.0
RenderWorkerResourceTemplatepasses only (wrt, deployment, buildID, temporalNamespace) — no version state — and the KEDA temporal scaler (pkg/scalers/temporal_scaler.go, v2.20.1 == main) contains zero references to version status/drainage.The data already exists in the scaler's response
In deployment-version mode the KEDA scaler calls
DescribeWorkerDeploymentVersionwithReportTaskQueueStats: trueand reads onlyversion_task_queues. The same response carriesworker_deployment_version_info— includingstatus(CURRENT / RAMPING / DRAINING / DRAINED), ramp and drainage info — which is currently discarded.Options
A. KEDA scaler (smallest change, covers floor = 1): an opt-in trigger param (e.g.
activateOnCurrentVersion: true) that reports the trigger active whenever the version status is CURRENT/RAMPING, regardless of backlog. WithminReplicaCount: 0, activation + the HPA's implicitminReplicas: 1keep the Current version warm; Draining/Drained versions activate only on real backlog and rest at 0, so the controller's sunset is unopposed. Uses data the scaler already fetches. (Happy to file this on kedacore/keda — flagging here first since this repo owns the versioning semantics; note KEDA declined an adjacent ask in kedacore/keda#7368.)B. Controller (general, covers floor = N): per-version-state rendering/patching — e.g. a
currentVersionMinReplicasfield or per-state template overrides, where the controller setsminReplicaCounton the Current version's ScaledObject and removes it on the transition to Draining. This is essentially #104 ("Per-Version-Status min/max Replicas" — closed, but the knob never shipped) and fits the requirements list in #14 ("Scale independently per-version", "Scale to 0 after drain", "Preemptively scale up target version before cutting over"); the same mechanism applied to the rollout target version would also address #438.C. Composite activity metrics (kedacore/keda#7459) or per-version metric-label templating (#355) mitigate the premature-park half but not the drained-floor half.
Workaround we run meanwhile
minReplicaCount: 0+ a very longcooldownPeriod(86400): the Current version effectively never parks (any active sample resets the clock), and Drained versions are unaffected because the controller zeroes them atscaledownDelayand KEDA never scales up without activation —cooldownPeriodgates only KEDA's own scale-down. Verified live: withscaledownDelay(15m) far below the cooldown, rollouts zero freshly-drained versions with unexpired cooldowns and no flap-back occurs. Cost: Draining versions holding open pinned workflows keep one warm pod up to 24h after their last straggler task.Related: #14, #104, #210, #332, #350, #355, #438; kedacore/keda#7368, kedacore/keda#7459.