Airflow 3This feature is only available for Airflow 3.x Deployments.
syncSlots value sets that number, and each concurrent task uses one slot. A worker that has no free slot does not accept more tasks, and the extra tasks stay in the queue.
Tasks that wait on an external system, such as a warehouse query or an API call, use very little CPU and memory. These tasks fill every slot on a worker while CPU utilization stays low. A Horizontal Pod Autoscaler (HPA) that scales on CPU or memory does not add replicas in this state, so the queue grows and task latency increases.
This document shows how to scale worker Pods on queue depth instead. Queue depth is the number of tasks in the queued and running states, which each agent worker reports in the astro_agent_client_queue_stats metric. It covers two setups: prometheus-adapter with the HPA that the Helm chart creates, and KEDA for clusters that do not run prometheus-adapter.
Slot capacity and queue depth
The task capacity of a worker Deployment issyncSlots multiplied by the number of replicas. A Deployment with syncSlots: 20 and two replicas runs 40 tasks at the same time.
Use this capacity to size the autoscaler:
- Set
maxReplicaCountto your peak number of concurrent tasks divided bysyncSlots, rounded up. For a peak of 200 concurrent tasks andsyncSlots: 20, setmaxReplicaCount: 10. - Set the metric target to
syncSlots. The autoscaler then adds one replica for eachsyncSlotstasks of work in the queue. - Set
minReplicaCountto the capacity you want available before the autoscaler reacts. Each scaling decision takes at least one metric interval, and a new worker Pod takes time to start.
syncSlots is the other way to add capacity for I/O-bound tasks. A slot holds a task process, so more slots need more memory on the worker Pod. Test a higher value against your own tasks before you use it in production.
Prerequisites
- A Remote Execution Agent that runs Agent Client version
1.7.0or later, which exposes the/metricsendpoint. See Register and configure agents. - A Prometheus instance that scrapes the agent worker Pods. See Scrape metrics from Remote Execution Agents.
- Permission to install cluster components and to run
helm upgradeagainst the agent release. - One of the following:
- prometheus-adapter, which serves the metric through the Kubernetes custom metrics API.
- KEDA, which reads Prometheus directly and creates its own HPA.
Choose a scaling method
Do not use both methods for the same worker Deployment. Two autoscalers that target one Deployment overwrite each other’s decisions.
Scale with prometheus-adapter
1
Scrape the workers with the Deployment name in the job label
The adapter rule in the next step maps the If you use a standalone Prometheus with static scrape configs, relabel the target so that
job label to a Kubernetes Deployment, so job must hold the name of the worker Deployment. The Helm chart creates one Service for each worker, labeled deploymentName: <resourceNamePrefix>-worker-<name>, which a ServiceMonitor can copy into job:worker-servicemonitor.yaml
job holds the same value.2
Publish the queue metric through prometheus-adapter
Add the following rule to your prometheus-adapter configuration:The counts come from the Astro orchestration plane in the worker heartbeat response, so every Pod of a worker Deployment reports the same values.
prometheus-adapter-values.yaml
max by (job, queue, state) removes the duplicate series, and sum by (job) adds the queued and running series together into one queue-depth value for each worker Deployment.3
Confirm that the custom metric is available
Query the custom metrics API for the worker Deployment. Replace The response contains a
re with your namespace and astro-worker-default-worker with your Deployment name:value field with the current queue depth. An error means that the adapter rule or the job label does not match. Fix this before you enable the HPA.4
Enable the HPA on the worker
Configure the worker in The
values.yaml:values.yaml
averageValue target of 20 matches syncSlots: 20, so the HPA runs one replica for each 20 tasks in the queue. See Metric target types for why queue depth needs an AverageValue target.The chart ignores replicas when hpa.enabled is true.5
Apply the configuration and check the HPA
TARGETS column shows the current queue depth against the target. If it shows <unknown>, run kubectl describe hpa -n re to see which metric the HPA cannot read.Scale with KEDA
KEDA queries Prometheus directly, so the cluster needs no custom metrics adapter. KEDA creates the HPA for the worker Deployment and owns it.1
Turn off the HPA in the Helm chart
Set Keep
hpa.enabled: false for the worker, so that the chart and KEDA do not create two autoscalers for one Deployment:values.yaml
replicas equal to the minReplicaCount you set in the next step. Each helm upgrade writes replicas back to the Deployment, and KEDA restores the scaled count at its next poll.2
Create a ScaledObject for the worker Deployment
worker-scaledobject.yaml
scaleTargetRef.name is the name of the worker Deployment, which the chart builds as <resourceNamePrefix>-worker-<worker name>.threshold matches syncSlots, and the default AverageValue metric type divides the query result by the threshold. KEDA therefore runs one replica for each 20 tasks in the queue.The query must return a single value. Adjust the label matchers to your own scrape configuration, and add a matcher for the worker Deployment when one Prometheus instance scrapes several of them. Read the labels from a worker’s /metrics endpoint before you write the query.3
Apply the ScaledObject and check the scaling
READY and ACTIVE columns of the ScaledObject report whether KEDA can read the metric. KEDA names the HPA that it creates keda-hpa-<scaled-object-name>.Other KEDA scalers
Theprometheus trigger fits any platform that keeps the agent metrics in Prometheus or in a Prometheus-compatible service, such as Amazon Managed Service for Prometheus, Azure Monitor managed service for Prometheus, or Google Cloud Managed Service for Prometheus.
If your queue depth lives in another system, KEDA can read it with an external trigger, which calls a gRPC service that you run. The arithmetic stays the same: report the number of queued and running tasks, and set the threshold to syncSlots. For the list of triggers, see the KEDA scalers reference.
Metric target types
Kubernetes computes the replica count differently for each target type. UseAverageValue for queue depth.
A
Value target multiplies the ratio by the current replica count. With a queue-depth metric, the replica count therefore multiplies again in each scaling cycle for as long as the queue stays above the target, until the HPA reaches maxReplicaCount. For the full algorithm, see the Kubernetes HPA documentation.
Combine queue depth with resource metrics
Queue depth measures outstanding work, not the load on a Pod. Tasks that use a lot of memory can still exhaust a worker before its slots are full. Add resource metrics withhpa.extraMetrics, which takes Kubernetes HPA metric objects:
values.yaml
resources.requests on the worker, because the HPA calculates utilization against the request.
With KEDA, add a second trigger of type memory or cpu to the same ScaledObject.
Scale down without task failures
A worker that stops during a task fails that task. Two settings protect running work:terminationGracePeriodSecondson the worker gives a Pod time to finish its tasks before Kubernetes stops it. The default is600. Set it higher than your longest task if your tasks run for more than 10 minutes.behavior.scaleDown.stabilizationWindowSecondsmakes the autoscaler wait before it removes replicas. The examples on this page use300.
scaleDown.stabilizationWindowSeconds makes the HPA use the highest replica count it recommended over the trailing window, so a brief dip does not remove a worker.