Oldest-message-age is the primary trigger and depth is secondary. Depth alone is the wrong signal: 200,000 cheap messages and 200 expensive ones look identical, and only one of them is late.
The trigger is scaled directly against the SLO — p95 task scheduling under one second — so the autoscaler is defending a stated commitment rather than a comfortable-looking queue length.
Scale up fast and scale in slow, with a 5-minute cooldown. Thrashing a worker fleet costs more in cold starts and abandoned leases than the compute it saves.
Numbers
queueLength 20 per replica, worker-http bounded 2 to 60, worker-customer360 2 to 30, worker-rag 0 to 20 with scale-to-zero and a 25-second cold start, worker-notify 2 to 15.
Peak drain target 900 tasks per second sustained. Cluster autoscaler adds spot nodes first and falls back to on-demand when spot capacity is unavailable.
PodDisruptionBudget keeps a minimum of 2 replicas during node drain, so a scale-in and a node upgrade cannot combine to empty a queue's consumers.
Risks
Scaling into a downstream ceiling is the most likely way this platform causes an outage somewhere else. When the circuit opens, KEDA stops scaling that deployment — autoscaling is subordinated to the circuit breaker, not the other way round.
Per-tenant concurrency leases still cap throughput after scale-out, so adding pods for a single saturated tenant achieves nothing. Fairness deliberately survives elasticity.
Scale-to-zero on worker-rag trades 25 seconds of cold start for idle cost. It is correct for a batch-shaped workload and wrong for an interactive one, so it is per-deployment configuration.