3.2.2. Event-Driven Scaling with KEDA
💡 First Principle: CPU is a lagging signal — by the time it rises, users are already waiting. The queue is a leading signal: backlog counts work that hasn't happened yet. KEDA scales on leading signals from event sources, which is why it can do what threshold-on-CPU autoscaling can't: match capacity to demand before latency degrades, and shut down entirely — scale to zero — when demand is zero.
Kubernetes Event-driven Autoscaling (KEDA) is built into Container Apps as its scale-rule engine (no installation — you just declare rules). A scale rule names an event source, the connection to watch it, and the threshold per replica:
az containerapp create --name embed-worker ... \
--min-replicas 0 --max-replicas 30 \
--scale-rule-name sb-backlog \
--scale-rule-type azure-servicebus \
--scale-rule-metadata queueName=embed-jobs messageCount=20 \
--scale-rule-auth connection=sb-conn
This declares: keep roughly one replica per 20 pending messages, between 0 and 30 replicas. The lifecycle:
Scalers exist for the whole event menagerie — Service Bus queues/topics, Event Hubs, Cosmos DB change feed lag, Redis lists, cron schedules, plus HTTP concurrency and CPU/memory rules for ordinary web traffic. The AI pattern this enables is the one promised in 1.1.1's anatomy: the bursty, expensive work (embedding generation, document processing) rides a queue, workers materialize with the backlog and evaporate after — you pay for work performed, not for waiting.
Note the composition with 2.2.5: thirty workers appearing in a burst is precisely the connection-storm scenario that PgBouncer absorbs. Exam scenarios love chaining these: KEDA scale-out → PostgreSQL connection exhaustion → pooling answer.
⚠️ Exam Trap: "Scale the queue-processing workers on CPU utilization" is the planted wrong answer — queue consumers can idle at low CPU while backlog explodes (each message triggers a slow external model call). Backlog-based KEDA rules are correct for queue work; CPU rules are for request-serving compute. And scale-to-zero belongs to event-driven rules — CPU rules can't reach zero.
Two mechanics matter beyond the rule declaration. First, when several scale rules exist on one app, KEDA evaluates each independently and the highest replica demand wins — a queue rule and an HTTP rule coexist safely. Second, scaling has cadence: the platform polls scalers on an interval and applies a cooldown before scaling in, so replica counts trail the queue by design rather than thrashing. Scale-rule authentication references app secrets (--scale-rule-auth connection=sb-conn), which is one more reason secret hygiene from 3.2.1 matters.
Reflection Question: Why can an embedding worker sit at 5% CPU while its queue grows unboundedly — and what does that say about choosing scale signals generally?