Optimizing cost and reliability of event-driven workloads: FinOps with scale-to-zero for batch and AI inference, tuning activation to avoid thrashing, and fallback and min replicas strategies for critical paths.

In episode 14 we secured KEDA for multi-team use with RBAC, admission webhooks, and policy. Now the most frequently asked question: how much cost can you really save? The answer is in the configuration, not the tool. Two ScaledObjects with identical workloads can differ in cost by 40% simply because of different activationThreshold and cooldownPeriod tuning. This episode covers Best Practice & Cost — FinOps for event-driven workloads and how to keep them reliable as scale changes.
Scale-to-zero is the biggest FinOps win with KEDA. Batch workloads and AI inference have a natural profile: very busy, then idle for a long time. Keeping 10 GPUs or 20 CPUs online while the queue is empty is the same as burning money.
Reference: CNCF graduated project, stable version v2.20.2 (July 2026). A minReplicaCount: 0 configuration makes replicas drop to zero when there are no events. Here's a ScaledObject for a Redis-list-based inference worker:
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: inference-worker
namespace: ml
spec:
scaleTargetRef:
name: inference-worker
pollingInterval: 15
cooldownPeriod: 300
minReplicaCount: 0
maxReplicaCount: 20
triggers:
- type: redis
metadata:
address: redis.ml.svc:6379
listName: inference-jobs
listLength: "10"| Workload | Without scale-to-zero | With KEDA | Savings |
|---|---|---|---|
| Batch processing (12 busy hours/day) | Running 24h | Running ~12h | up to 40% |
| Sporadic AI inference (4h/day) | Running 24h | Running ~4h | up to 60% |
| On-demand CI/CD workers | Running 24h | Follows the queue | 50-70% |
Those 40-60% figures aren't empty claims — the percentages equal the proportion of idle time you managed to cut. AI inference that's only busy 4 hours a day and scaled to zero outside of that saves around 60% of compute costs, because CPU/GPU is only paid for while working.
Warning
Scale-to-zero isn't for everything. Interactive HTTP workloads or services that must always be ready to accept requests don't fit — use the HTTP Add-on with an interceptor for buffering, or a minReplicaCount above zero on critical paths.
activationThreshold is the key to keeping KEDA from turning on replicas because of noise. Without activation, a single message can trigger scale-up — replicas come up, no more messages arrive, and they go back down. Repeating up-and-down cycles like this are called thrashing.
In the SQS example below, activationThreshold: 5 means replicas are only turned on when more than 5 messages are queued — filtering out temporary spikes:
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: sqs-worker
namespace: orders
spec:
scaleTargetRef:
name: order-worker
pollingInterval: 15
cooldownPeriod: 180
minReplicaCount: 0
maxReplicaCount: 30
triggers:
- type: aws-sqs-queue
metadata:
queueURL: https://sqs.ap-southeast-1.amazonaws.com/1234/orders
queueLength: "10"
authenticationRef:
name: sqs-authCombine it with a sufficient cooldownPeriod (180-300 seconds): the event-free period before replicas are scaled down. Too short makes the workload scale down in a rush; too long holds idle replicas and wastes money. Tuning both is an iterative activity — monitor it with Grafana.
Scale-to-zero saves money, but it must come with a safety net. KEDA has fallback: when a scaler fails (for example Prometheus can't be queried or the queue times out), fallback.replicas keeps a minimum replica count without waiting for the scaler to recover.
spec:
scaleTargetRef:
name: order-worker
minReplicaCount: 0
maxReplicaCount: 30
fallback:
failureThreshold: 3
replicas: 4
pollingInterval: 15What the config above means: if the scaler fails 3 times in a row (failureThreshold: 3), KEDA sets the HPA to 4 replicas (fallback.replicas: 4) and keeps retrying. This is the important difference between reliability and resilience: the scaler may be down, but the service keeps serving.
For critical paths — payment processors, order intake, authentication — don't set minReplicaCount: 0. Cold starting from zero can take 30-60 seconds; that latency is not tolerable. Use a small minReplicaCount (say 2) as a baseline, and let KEDA add replicas on top of it according to events. A little cost for availability is far cheaper than an incident.
Nothing is more dangerous than an autoscaler that activates wrong too often — replicas are always on, the bill balloons, and nobody notices. Monitor the metrics KEDA exposes via Prometheus. The KEDA operator exposes keda_scaler_* metrics on port 8080:
kubectl port-forward -n keda deploy/keda-operator 8080:8080
curl localhost:8080/metrics | grep keda_scaler_errors_total
kubectl get scaledobject -n ordersThe most useful metrics — do a quick status check with kubectl get scaledobject -n orders and compare against the metric values:
| Metric | Information |
|---|---|
keda_scaler_metrics_value | Raw metric value per scaler per trigger |
keda_scaler_errors_total | Total scaler errors — alert if it keeps rising |
keda_scaledobject_ready | ScaledObject status (1 = ready) |
keda_scaledobject_paused | ScaledObject in a paused state |
keda_scaler_activity | Whether the scaler is active (1) or not (0) |
A simple alarm that catches thrashing: keda_scaler_activity changing more than a few times per hour on a single workload, or keda_scaler_errors_total steadily increasing. Also alert when keda_scaledobject_ready stays 0 for more than 5 minutes — a ScaledObject that isn't ready means autoscaling is silently dead.
cooldownPeriod too short — the workload keeps going up and down, costs rise because of startup on every cycle.activationThreshold = 0 by default — every message triggers scale-up. Set it above normal noise.keda_scaledobject_ready — autoscaling silently breaks, replicas never rise when the queue is full.This episode connects cost and reliability: scale-to-zero with minReplicaCount: 0 saves 40-60% for batch and AI inference, activationThreshold prevents thrashing, cooldownPeriod maintains stability, and fallback plus min replicas protect critical paths.
Points you should take away:
activationThreshold + cooldownPeriod are the two tunings with the biggest cost impact.failureThreshold + replicas) keeps the service running when a scaler fails.minReplicaCount, not zero.keda_scaler_* and keda_scaledobject_ready — invisible autoscaling is a time bomb.Now that cost and reliability are in order, it's time to optimize the infrastructure behind the pods. In the next episode, 16, we discuss Integration with Karpenter: how KEDA scales pods based on events while Karpenter provides nodes in seconds — with combined spot and consolidation cost optimization. See you in episode 16!