Initializing Systems...

Medium Risk

Zero-Downtime Deployments: The Myth vs Reality

We tested 5 popular deployment strategies under real production load. The results might change how you ship code.

“Zero downtime” is the promise every DevOps tool makes. It’s on every landing page, in every conference talk, embedded in every CI/CD pipeline tutorial. But after deploying to production 2,847 times last year, we’ve learned that “zero” is more of a marketing term than a technical reality.

This isn’t a takedown piece. These strategies work. But they work differently than the docs suggest, and the gap between theory and practice cost us more debugging hours than we’d like to admit.

The Experiment

We run a B2B SaaS platform with approximately 12,000 concurrent users during peak hours. Our stack:

Backend Node.js microservices (14 services)
Orchestration Kubernetes 1.28 on AWS EKS
Traffic ~3,200 requests/second at peak
Deploy Frequency 8-12 times per day

Over six months, we systematically tested five deployment strategies, measuring three things:

  • Error Rate During Deploy — HTTP 5xx responses during the deployment window
  • Latency Impact — P99 latency change during deployment
  • True Deployment Time — From git push to fully stable (not just “rollout complete”)

Here’s what we found.

Strategy 1: Rolling Updates

Complexity
True Zero-Downtime? Sometimes

The Kubernetes default. Old pods are gradually replaced with new ones. Simple, built-in, and the strategy most teams use without thinking about it.

deployment.yaml YAML
spec:
  replicas: 6
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 2
      maxUnavailable: 1

Our Results

0.3% Error rate during deploy
+180ms P99 latency spike
4-6 min True deployment time

The 0.3% error rate doesn’t sound bad until you realize that’s roughly 10 errors per second during the 4-minute window. For our users, that’s failed form submissions, lost data, and support tickets.

⚠

The Hidden Problem

Kubernetes considers a pod “ready” when the readiness probe passes. But Node.js apps often pass the probe before the JIT compiler has warmed up. First requests to new pods were 3-4x slower than normal.

The fix: We added a startup delay and pre-warming script that makes synthetic requests before the pod accepts real traffic.

prewarm.sh Bash
#!/bin/bash
# Hit critical endpoints to warm up V8
for i in {1..100}; do
  curl -s http://localhost:3000/api/health > /dev/null
  curl -s http://localhost:3000/api/users/warm > /dev/null
  curl -s http://localhost:3000/api/products/warm > /dev/null
done
echo "Prewarm complete"

Strategy 2: Blue-Green Deployment

Complexity
True Zero-Downtime? Yes*

Run two identical environments. Deploy to the inactive one, test it, then switch traffic. The classic “instant rollback” strategy.

Blue-Green Traffic Flow
  ┌─────────────┐
  │   Users     │
  └──────┬──────┘
         │
         ▼
  ┌─────────────┐
  │ Load Balancer│
  └──────┬──────┘
         │
    ┌────┴────┐
    ▼         ▼
┌───────┐ ┌───────┐
│ BLUE  │ │ GREEN │
│ v2.1  │ │ v2.2  │
│(live) │ │(idle) │
└───────┘ └───────┘

Our Results

0.02% Error rate during deploy
+12ms P99 latency spike
8-10 min True deployment time

The asterisk on “Yes” is important. Blue-green works beautifully for stateless services. But we hit problems:

  • 1 Database migrations — If v2.2 needs a schema change, you can’t just switch back to v2.1
  • 2 WebSocket connections — Existing connections don’t magically move to the new environment
  • 3 Cost — Running 2x infrastructure adds up ($4,200/month in our case)
💡

Our Approach

We use blue-green only for our API gateway and stateless services. Database-touching services use a different strategy (see #4).

Strategy 3: Canary Releases

Complexity
True Zero-Downtime? Yes

Route a small percentage of traffic to the new version. Monitor. Gradually increase. This is the strategy everyone says they want to implement “someday.”

canary-ingress.yaml YAML
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: api-canary
  annotations:
    nginx.ingress.kubernetes.io/canary: "true"
    nginx.ingress.kubernetes.io/canary-weight: "10"
spec:
  rules:
  - host: api.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: api-canary
            port: 
              number: 80

Our Results

0.01% Error rate during deploy
+8ms P99 latency spike
25-40 min True deployment time

Canary gave us the best error rates, but the deployment time killed our velocity. With 8-12 deploys per day, spending 30+ minutes per deploy wasn’t sustainable.

We also learned that canary requires serious observability investment:

□ Per-version error tracking
□ Per-version latency dashboards
□ Automated rollback triggers
□ Traffic percentage automation

Without automation, someone has to babysit every deploy. We built the automation eventually, but it took two engineers three weeks.

Strategy 4: Feature Flags + Rolling

Complexity
True Zero-Downtime? Yes

Decouple deployment from release. Ship the code with features disabled, then enable them gradually via flags. This changed everything for us.

feature-check.js JavaScript
const LaunchDarkly = require('launchdarkly-node-server-sdk');

async function getSearchResults(query, userId) {
  const useNewAlgorithm = await ldClient.variation(
    'new-search-algorithm',
    { key: userId },
    false  // default: use old algorithm
  );
  
  if (useNewAlgorithm) {
    return newSearchService.search(query);
  }
  return legacySearchService.search(query);
}

Our Results

0.05% Error rate during deploy
+15ms P99 latency spike
3-4 min True deployment time

The key insight: deployments become boring. You’re just shipping code that doesn’t do anything new yet. The risky part—enabling the feature—happens separately, and you control exactly when and for whom.

Aspect Canary Release Feature Flags
Rollback speed 2-5 minutes <1 second
Targeting Random % Specific users/segments
Database migrations Complicated Manageable
Code complexity Lower Higher (flag cleanup needed)

Strategy 5: Shadow/Dark Launching

Complexity
True Zero-Downtime? Yes

Mirror production traffic to the new version without returning its responses to users. Compare results. Only switch when confident. This is the nuclear option.

Shadow Traffic Flow
        ┌─────────────┐
        │   Request   │
        └──────┬──────┘
               │
               ▼
        ┌─────────────┐
        │   Router    │
        └──────┬──────┘
               │
      ┌────────┼────────┐
      │        │        │
      ▼        │        ▼
  ┌───────┐    │    ┌───────┐
  │ v2.1  │    │    │ v2.2  │
  │ LIVE  │    │    │SHADOW │
  └───┬───┘    │    └───┬───┘
      │        │        │
      ▼        │        ▼
  Response ◄───┘    Logged &
  to User           Compared

Our Results

0.00% Error rate during deploy
+0ms P99 latency spike
2-24 hours True deployment time

Technically perfect. Practically… we use this maybe twice a year for major rewrites. The infrastructure overhead and analysis time make it impractical for daily deploys.

⚠

Watch Out For

Shadow traffic can double your database load and third-party API calls. We accidentally burned through a month of Stripe API quota in two days during a shadow test.

What We Actually Use

After six months of testing, here’s our current deployment matrix:

API Gateway
Blue-Green
Stateless, instant rollback critical
Core Services
Feature Flags + Rolling
Balance of speed and safety
Background Workers
Rolling
Low user impact, simple works
Major Rewrites
Shadow + Canary
Worth the overhead for big changes

The honest answer is: there’s no single “best” strategy. The right choice depends on your service, your risk tolerance, and how fast you need to ship.

Key Takeaways

01

“Zero Downtime” Is a Spectrum

0.01% errors is not zero. For high-traffic apps, even tiny percentages mean real user impact. Define what “acceptable” means for your business.

02

Warm-Up Time Is Real

JIT compilers, connection pools, caches—new instances are always slower than warmed ones. Account for this in your readiness checks.

03

Deployment ≠ Release

Feature flags let you separate “code is running” from “feature is live.” This single change eliminated 80% of our deployment anxiety.

04

Observability Is Non-Negotiable

Every advanced strategy requires knowing what’s happening. Invest in per-version metrics before investing in fancy deployment tooling.

📊

Want the Raw Data?

We’ve published our full dataset including error logs, latency percentiles, and deployment timestamps. Check our GitHub repo for the complete analysis.