# How to Handle Traffic Spikes with Autoscaling
The email goes out, the post hits the front page, the campaign goes live — and your traffic 20×'s in ten minutes. Whether that's a triumph or an outage depends almost entirely on whether your infrastructure can scale to meet demand and shrink back afterward. That's autoscaling. Here's how to do it well.
Vertical vs horizontal scaling
There are two ways to add capacity:
- Vertical (scale up): give one instance more CPU/RAM. Simple, but there's a ceiling and it usually means downtime to resize.
- Horizontal (scale out): add more instances behind a load balancer. Effectively unlimited, no downtime, and the foundation of cloud autoscaling.
Horizontal scaling is the answer for traffic spikes — but it only works if your app is stateless (any instance can handle any request). If your app keeps per-user state in memory or writes to local disk, adding instances won't help. Externalize state first (see stateless app design).
How horizontal autoscaling works
An autoscaler watches a metric, compares it to a target, and adjusts the replica count:
observe metric → compare to target → add/remove replicas → repeatKubernetes' Horizontal Pod Autoscaler (HPA) is the canonical example. A simple CPU-based policy:
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: web
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: web
minReplicas: 2
maxReplicas: 50
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70This keeps average CPU near 70% by adding replicas up to 50 and scaling back down to 2 when load drops.
Pick the right scaling metric
CPU is the default, but it's often the wrong signal. The best metric reflects what's actually saturating:
| Metric | Good for |
|---|---|
| CPU utilization | CPU-bound work |
| Memory | Memory-bound services |
| Requests per second | Web tiers |
| Request latency (p95) | Latency-sensitive APIs |
| Queue depth | Async workers / job processors |
For a worker consuming a queue, queue depth is far better than CPU — it scales on the backlog directly. Event-driven autoscalers like [KEDA](https://keda.sh/) scale on dozens of external sources (queue length, Kafka lag, Redis list size) and can even scale to zero when there's nothing to do.
Tune for spikes, not just averages
Defaults are conservative; spikes need aggressive scale-up and cautious scale-down:
- Scale up fast so you don't fall behind a sharp ramp. Allow large step increases.
- Scale down slowly (a stabilization window) to avoid flapping — repeatedly adding and removing instances as load wobbles.
- Set a sane
minReplicasso there's warm capacity to absorb the *start* of a spike before scaling kicks in. - Set
maxReplicasas a guardrail against a runaway bill or overwhelming a downstream dependency.
The parts that don't autoscale
This is where launches actually fail. Scaling the web tier just moves the bottleneck downstream:
- The database. Most databases don't scale out as easily as stateless web tiers. 50 app replicas can exhaust the connection pool and bring the DB to its knees. Use a connection pooler (PgBouncer), read replicas for read-heavy load, and caching to take pressure off.
- Third-party APIs and rate limits. Your scaled fleet can blow through a payment provider's rate limit. Add backoff and queueing.
- Cold starts. New instances take time to boot and warm up. Pre-scale ahead of a *known* event (a scheduled launch) rather than relying purely on reactive scaling.
A useful framing: autoscaling protects the tier it's on, and exposes the tier behind it. Always ask what's downstream.
Reactive vs proactive scaling
- Reactive scaling responds to current load. Great for unpredictable spikes, but always slightly behind.
- Proactive/scheduled scaling pre-provisions for known events — a Black Friday sale, a scheduled email blast. If you *know* the spike is coming, warm up beforehand.
Use both: scheduled scaling for the known, reactive for the surprises.
Load-test before you need it
Don't discover your scaling limits during the real spike. Run a load test (k6, Locust, Gatling) that ramps to your expected peak and beyond. Watch where it breaks — usually the database or a downstream API, not the web tier — and fix that *before* the launch.
# k6 example: ramp to 1000 virtual users
k6 run --vus 1000 --duration 5m load-test.jsGraceful degradation
When you do hit a limit, fail gracefully: shed non-essential load (disable expensive recommendations), serve cached/stale content, queue writes, and return a friendly "high traffic" page rather than 500s. A degraded experience beats an outage.
Autoscaling on PandaStack
PandaStack runs apps on multi-region GKE with KEDA-driven autoscaling, and free-tier apps scale to zero on spot nodes when idle — so you pay nothing during quiet periods and scale up automatically when traffic arrives. Managed databases (via KubeBlocks) sit behind your stateless app tier as the durable layer, and the CDN/edge (Cloudflare + Kong) absorbs static load. The horizontal-scaling, stateless-app, scale-on-demand model this article recommends is the platform's default.
References
- [Kubernetes — Horizontal Pod Autoscaler](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/)
- [KEDA — Kubernetes Event-driven Autoscaling](https://keda.sh/)
- [k6 — load testing](https://k6.io/docs/)
- [Google SRE Book — Handling Overload](https://sre.google/sre-book/handling-overload/)
- [AWS Auto Scaling documentation](https://docs.aws.amazon.com/autoscaling/)
Want autoscaling that scales to zero when idle and up under load — without writing HPA YAML? PandaStack's free tier does it by default. [Start at dashboard.pandastack.io](https://dashboard.pandastack.io).