← Back to DevBytes

How to Handle Model Upgrades with Zero Downtime

How to Handle Model Upgrades with Zero Downtime

Machine learning models are not static artifacts. They evolve over time as you retrain on new data, tune hyperparameters, or swap architectures entirely. But deploying a new model version into production carries risk: a bad rollout can degrade predictions, break downstream consumers, or take your service offline entirely. Zero-downtime model upgrades ensure that users never experience an interruption while you transition from one model version to the next.

What Is a Zero-Downtime Model Upgrade?

A zero-downtime model upgrade is a deployment strategy that swaps an old model (version N) for a new one (version N+1) without interrupting inference availability. Throughout the upgrade, the service continues to respond to prediction requests. If the new model misbehaves, you can roll back instantly without users noticing.

This is distinct from a simple redeploy, which typically involves stopping the inference server, loading the new weights, and restarting. That approach introduces a window of unavailability — unacceptable for production-facing systems.

Why It Matters

Core Strategies

There are several complementary techniques. In practice, you combine them.

How to Use It: A Practical Example

Below we walk through a canary-style upgrade using a lightweight model server in Python. The server keeps both the old and new model loaded, routes traffic based on a configurable percentage, and exposes a health endpoint for orchestration.

1. Define the Model Server

# server.py
import os
import random
import time
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
import joblib

app = FastAPI()

# Configuration via environment variables
OLD_MODEL_PATH = os.getenv("OLD_MODEL_PATH", "/models/v1/model.joblib")
NEW_MODEL_PATH = os.getenv("NEW_MODEL_PATH", "/models/v2/model.joblib")
CANARY_PERCENT = float(os.getenv("CANARY_PERCENT", "0"))  # 0-100

# Load both models at startup so switching is instant
print("Loading models...")
old_model = joblib.load(OLD_MODEL_PATH)
new_model = joblib.load(NEW_MODEL_PATH)
print("Models loaded.")

class PredictRequest(BaseModel):
    features: list[float]

class PredictResponse(BaseModel):
    prediction: float
    model_version: str

def select_model():
    """Return (model, version_label) based on canary percentage."""
    if CANARY_PERCENT <= 0:
        return old_model, "v1"
    if CANARY_PERCENT >= 100:
        return new_model, "v2"
    if random.random() * 100 < CANARY_PERCENT:
        return new_model, "v2"
    return old_model, "v1"

@app.post("/predict", response_model=PredictResponse)
def predict(req: PredictRequest):
    model, version = select_model()
    try:
        pred = float(model.predict([req.features])[0])
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))
    return PredictResponse(prediction=pred, model_version=version)

@app.get("/health")
def health():
    return {"status": "ok", "canary_percent": CANARY_PERCENT}

@app.get("/config/canary")
def get_canary():
    return {"canary_percent": CANARY_PERCENT}

@app.post("/config/canary")
def set_canary(percent: float):
    global CANARY_PERCENT
    CANARY_PERCENT = max(0.0, min(100.0, percent))
    return {"canary_percent": CANARY_PERCENT}

2. Run the Server

export OLD_MODEL_PATH=/models/v1/model.joblib
export NEW_MODEL_PATH=/models/v2/model.joblib
export CANARY_PERCENT=0

uvicorn server:app --host 0.0.0.0 --port 8000

At this point, 100% of traffic is served by the old model. The new model is loaded but idle.

3. Begin the Canary Rollout

# Route 5% of traffic to the new model
curl -X POST http://localhost:8000/config/canary \
     -H "Content-Type: application/json" \
     -d '{"percent": 5}'

# Send a test prediction
curl -X POST http://localhost:8000/predict \
     -H "Content-Type: application/json" \
     -d '{"features": [1.2, 3.4, 0.8]}'

Each response includes model_version, so you can attribute predictions to v1 or v2 in your logs and metrics.

4. Monitor and Ramp Up

# After validating metrics at 5%, increase gradually
for pct in 10 25 50 75 100; do
  curl -X POST http://localhost:8000/config/canary \
       -H "Content-Type: application/json" \
       -d "{\"percent\": $pct}"
  echo "Set canary to $pct%, waiting 10 minutes..."
  sleep 600
done

Between each step, observe key metrics: latency, error rate, business KPIs (click-through rate, fraud catch rate, etc.). If anything degrades, immediately roll back.

5. Roll Back If Needed

# Instant rollback — all traffic back to v1
curl -X POST http://localhost:8000/config/canary \
     -H "Content-Type: application/json" \
     -d '{"percent": 0}'

Because the old model is still loaded in memory, rollback is instantaneous. No restart, no reload.

Best Practices

Conclusion

Zero-downtime model upgrades are a discipline, not a single tool. By keeping multiple model versions loaded, routing traffic incrementally, tagging every prediction with its version, and automating both the ramp-up and the rollback, you turn model deployment from a risky event into a routine operation. The investment pays off every time you ship an improvement without a 3 a.m. incident — and every time you catch a bad model before your users do.

— Ad —

Google AdSense will appear here after approval

← Back to all articles