maxSurge Doesn’t Actually Add Capacity➕ Why maxSurge Doesn’t Actually Add Capacity
(Spring Boot + Kubernetes Production Reality)
“We set
maxSurge: 1.
So we had extra capacity during deploys… right?”
No.
You had extra pods, not extra usable capacity.
Get Dolly’s stories in your inbox
Join Medium for free to get updates from this writer.
And those are very different things.
🧩 The comforting assumption
Most teams deploy with:
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 0
maxSurge: 1Which sounds like:
“Add one pod first.
Then remove one.
Safe.”
Reality:
That new pod contributes almost nothing when it matters most.
🔍 What Kubernetes actually guarantees
maxSurge means:
- Kubernetes may create N extra pods
- It does not guarantee:
- Readiness usefulness
- Warm capacity
- Traffic absorption
The scheduler’s job ends at:
“Pod is running.”
Your problems start after that.
💥 What we observed in production
During deploy:
- New pod started
- Readiness went green
- Traffic redistributed
Yet:
- Latency increased
- Timeouts spiked
- Error rate climbed
The “extra” pod wasn’t helping.
🧠 Why surge pods don’t help under load
1️⃣ Cold JVM penalty
New pod:
- Cold JIT
- Cold class loading
- Cold heap
- Cold GC behavior
Under traffic:
- Slower response
- More GC pauses
- Less throughput
2️⃣ Empty connection pools
Spring Boot starts with:
- Empty DB pools
- Empty HTTP pools
First requests:
- Pay connection setup cost
- Block threads
- Increase latency
3️⃣ Cache miss amplification
If you cache:
- Redis
- In-memory
- Caffeine
New pod:
- Misses everything
- Hits DB / downstream harder
- Increases load instead of reducing it
4️⃣ Kafka lag reality
Kafka consumers:
- Join group late
- Trigger rebalances
- Don’t consume immediately
So:
- Surge pod ≠ more consumption capacity
🧨 The illusion of readiness
readinessProbe:
httpGet:
path: /actuator/health/readinessSpring Boot says “ready”
long before:
- Pools are warm
- Caches are primed
- Latency is stable
Ready ≠ Useful.
🔥 The worst-case outcome
You think:
“We added capacity.”
But actually:
- Existing pods handle the load
- New pod adds overhead
- Load shifts unevenly
- Latency spikes
- Retries kick in
Surge pod becomes a liability.
🛑 Why HPA doesn’t fix this
Autoscaling:
- Reacts slowly
- Needs metrics
- Can’t pre-warm pods
Deploy storms happen faster than HPA can think.
✅ How to make surge pods actually useful
1️⃣ Delay readiness until warm
@Component
public class WarmupReadiness {
private volatile boolean warmed = false;
@PostConstruct
public void warm() {
warmDb();
warmCaches();
warmed = true;
}
@Readiness
public Health readiness() {
return warmed
? Health.up().build()
: Health.down().build();
}
}No traffic until useful.
2️⃣ Pre-scale before deploy
kubectl scale deploy app --replicas=+2Warm pods first.
Deploy second.
3️⃣ Overprovision intentionally
Design steady-state to use:
- 60–70% capacity
Deploys consume the rest.
4️⃣ Reduce retries during deploys
Retries turn cold starts into storms.
🧾 The rule we now follow
Capacity must be warm, not just present.
Pods are not capacity.
Throughput is.
❤️ Final truth
maxSurgecreates the illusion of safety,
not actual safety.
If your system depends on surge pods to survive deploys,
it’s already too close to the edge.










