MVP Factory
ai startup development

Kubernetes HPA vs KEDA: taming spiky mobile backend traffic

KW
Krystian Wiewiór · · 5 min read

Meta description: Why CPU-based HPA fails during push notification campaigns, and how KEDA’s event-driven autoscaling cuts the cold-start penalty that wrecks p99 latency.

Tags: kubernetes backend mobile microservices devops


TL;DR

Horizontal Pod Autoscaler reacts to what already happened. KEDA reacts to what is happening. For mobile backends where traffic spikes from zero to thousands of RPS in seconds, that distinction is the difference between a smooth App Store featuring event and a p99 latency catastrophe. What follows covers how to configure KEDA with the right stabilization windows, scale-down policies, and replica floors to stay ahead of your users.


The core problem with HPA for mobile traffic

In my experience building production systems that serve mobile clients, the traffic profile is fundamentally unlike web or API traffic. Push notification campaigns, App Store featuring events, and viral social moments share one characteristic: they are step functions, not ramps.

HPA’s default signal chain looks like this:

Traffic spikes → CPU/memory climbs → HPA observes metric → 
Scaling decision made → Pod scheduled → Container starts → Ready

That chain takes anywhere from 60 to 180 seconds in a typical cluster. For a push notification campaign delivering 2 million notifications in under 30 seconds, your backend has already absorbed the full burst before the first new pod is ready to serve traffic.

CPU-based HPA operates on a 15-second scrape interval by default, with a 30-second evaluation window. By the time the autoscaler acts, your existing pods are already saturated.


KEDA’s event-driven approach

KEDA (Kubernetes Event-Driven Autoscaling) inverts this model. Instead of reacting to resource exhaustion, it scales on the leading indicators of load — queue depth, message lag, incoming RPS — before your pods are under stress.

Scaling on queue depth

apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: mobile-notification-processor
spec:
  scaleTargetRef:
    name: notification-service
  minReplicaCount: 3
  maxReplicaCount: 50
  triggers:
    - type: rabbitmq
      metadata:
        queueName: push-notifications
        value: "100"  # target messages per replica
  advanced:
    stabilizationWindowSeconds: 30
    horizontalPodAutoscalerConfig:
      behavior:
        scaleUp:
          stabilizationWindowSeconds: 0
          policies:
            - type: Pods
              value: 10
              periodSeconds: 15
        scaleDown:
          stabilizationWindowSeconds: 300
          policies:
            - type: Percent
              value: 10
              periodSeconds: 60

The asymmetry is intentional.


The three parameters that prevent p99 degradation

ParameterHPA DefaultKEDA RecommendationWhy It Matters
scaleUp.stabilizationWindowSeconds0 (but CPU lag adds ~60s)0 explicitReact immediately to queue growth
scaleDown.stabilizationWindowSeconds300s300–600sPrevent oscillation after burst
minReplicaCount0 or 13+Eliminate cold-start entirely for warm floor
scaleUp policy (Pods)4 pods/60s10 pods/15sMatch burst velocity of push campaigns
scaleDown policy (Percent)—10%/60sGradual drain prevents rebound spikes

The replica floor is non-negotiable

Most teams treat minReplicaCount: 0 as a cost optimization, then wonder why their p99 spikes from 120ms to 4 seconds during the first minute of a campaign. That’s the cold-start penalty, and it’s entirely self-inflicted.

A replica floor of 3–5 pods, sized to handle your baseline traffic comfortably, eliminates it for the initial burst wave. The incremental cost is trivial compared to the user-facing latency impact.

Stabilization windows: asymmetric by design

Scale-up stabilization should be zero or near-zero. The cost of one unnecessary pod for 60 seconds is negligible. The cost of under-provisioning during a featuring event is a 1-star review wave.

Scale-down stabilization should be aggressive — 5 to 10 minutes minimum. Mobile traffic bursts are rarely clean step-down functions. App Store featuring events drive long tails of organic discovery traffic for hours after the initial spike.


How it fits together

For a typical mobile push campaign backend, KEDA watches SQS queue depth or Kafka consumer lag and makes a scaling decision within 5 seconds of queue growth. The 3 warm replicas on the floor absorb the initial burst with no latency penalty. The scale-up policy (10 pods per 15 seconds) matches the backend load curve of a 2M notification campaign. Scale-down is deliberately slow — 10% per 60 seconds — to prevent the oscillation that causes secondary latency spikes after the burst.


What to change

Replace CPU-based HPA with KEDA for any mobile backend that receives push-triggered traffic. Queue depth and consumer lag are leading indicators; CPU is a lagging one. The 60–90 second lag in CPU-based scaling is unacceptable for step-function mobile traffic.

Set minReplicaCount to at least 3 and size those pods to handle your P50 load. The cold-start penalty isn’t a Kubernetes problem — it’s a configuration choice. A warm replica floor eliminates it for the burst’s first wave.

Use asymmetric stabilization windows: zero on scale-up, 300+ seconds on scale-down. The oscillation pattern that wrecks p99 latency in the minutes after a burst comes from aggressive scale-down followed by a rebound. Conservative scale-down is the fix, and the cost is a few extra pods for a few extra minutes.


Share: Twitter LinkedIn