BitBootcampBitBootcamp ← all explainers 🔄 Review ⚙ Settings

Drill 90 — The Rollback With No Model

Theme: Model Lifecycle & MLOps · Format: 20-min drill · Difficulty: core

The story

A bank's card-fraud service scores every transaction in real time — a few hundred milliseconds to decide approve, challenge, or decline. The model is retrained nightly on the last ninety days of labelled transactions; the training job writes its output to s3://models/fraud/latest.pkl, and the scoring service loads whatever sits at that path on boot and on a rolling hourly refresh. This has run cleanly for two years. Nobody thinks about the artifact path because nobody has ever needed to.

Tuesday's nightly run picks up a feature-pipeline change merged the afternoon before, and the freshly trained model — call it the Tuesday model — goes live at the 06:00 refresh. By 09:30 the fraud-ops queue is on fire: the decline rate on legitimate card-present transactions has roughly doubled, call-centre hold times are climbing, and a payments partner has opened a Sev-2 asking why good customers are being blocked at the terminal. The model hasn't crashed. Latency is flat, error rate is zero, every health check is green. It is simply wrong — too aggressive — and it is declining real customers at scale.

The incident commander makes the obvious, correct call: roll back to yesterday's model. Monday's model was fine; put it back and stop the bleeding while we investigate. An engineer goes to fetch it — and there is nothing to fetch. latest.pkl is Monday's model until 06:00 and Tuesday's after, because the training job overwrites the same key every night. The S3 bucket has no object versioning. The registry stores a single pointer called "current," and it points at latest.pkl. Monday's artifact was overwritten in place six hours ago. There is no previous model anywhere on disk, in the bucket, or in the registry.

The room realizes there is no rollback. The only path back to Monday's behaviour is to retrain Monday's model — but the feature code has changed, the ninety-day training window has rolled forward a day, and the person who knows the old pipeline is asleep. The bleed continues while the team tries to reconstruct a model that existed, and worked, this morning.

⏸ Stop here and ask the room: the fix everyone agrees on is "roll back to yesterday's model." What has to have been true, before the incident, for that sentence to be executable in under five minutes — and which of those things does this team have?

Run it — facilitator script

Timing for a 20-minute session: 2 min story · 10 min staged questions · 3 min reveal · 5 min transfer.

The decision is "roll back." Why can't they?

The service is 200-OK and green. So what is actually broken, and why did no monitor catch it?

You've retrained under fire and it's back. What do you change so the next bad model is a two-minute rollback, not an outage?

The reveal

There was no previous model because every night's training job wrote to the same key and the bucket kept no versions — the deploy that shipped the bad model had already deleted the good one. Recovery took just over three hours: the team pinned the feature code back to Monday's commit, reran the training job against a reconstructed ninety-day window, eyeballed that the decline rate looked sane, and promoted the result — effectively retraining a model that had existed and worked six hours earlier. The permanent fix had nothing to do with the bad model itself: they switched to immutable, versioned artifacts (fraud/2024-06-11T06:00/model.pkl, never overwritten), kept the last three known-good versions loaded and warm in the service, and made rollback a single command that repoints the current alias to a prior version and completes in under two minutes — then rehearsed it. The bad Tuesday model was never the hard part. The hard part was that "put yesterday's model back" turned out to be a sentence the system could not execute.

Why this happens — the mechanics

A trained model is a data artifact, not source code, and it does not live in your version-control system. Code rollback is a solved, reflexive problem — every team can revert a commit and redeploy — so teams unconsciously assume model rollback is the same reflex. It is not. Reverting the training code to yesterday's commit does not give you yesterday's model: retraining reruns against a different data window, non-deterministic ordering, new library versions, and a moved-forward label set, and produces a different model that merely resembles the old one. The only thing that is truly "yesterday's model" is the exact bytes that were serving yesterday — and if the training job overwrote them, or you only ever stored "latest," those bytes no longer exist. Rollback is impossible not because the tooling failed but because the artifact was never retained.

The organizational root is that the training pipeline and the deployment story were built by different reflexes at different times. The training job was written to produce a good model, and "write to latest" is the simplest possible sink — it works perfectly until the first time someone needs the previous one. Deployment safety — versioned artifacts, a registry, staged promotion, one-command rollback — is a separate discipline that nobody added because, for two years, no model was ever bad enough to need it. The failure is a best practice (retrain nightly, always serve the freshest model) applied without the matching safety net (never destroy the model you're replacing). And it is invisible until the exact moment it matters, because a rollback capability you never exercised looks identical to one you have — right up until you reach for it during a Sev-2 and find the shelf empty.

Diagrams

Problem — the deploy that destroyed its own fallback:

sequenceDiagram
    autonumber
    participant TR as Nightly train job
    participant ART as Artifact store<br/>(only "latest")
    participant SVC as Fraud model<br/>service
    participant OPS as On-call
    Note over TR,ART: nightly job OVERWRITES<br/>latest.pkl in place
    TR->>ART: write latest (Tuesday)
    ART->>SVC: serve Tuesday model
    Note over SVC: legit-decline rate 2x,<br/>200 OK, zero errors
    SVC--xOPS: good customers declined
    Note over OPS: decision — roll back<br/>to Monday
    OPS->>ART: fetch previous model
    ART--xOPS: no prior artifact
    Note over OPS,ART: nothing to roll back to —<br/>retrain under fire

Solution — where the guardrails go:

flowchart TD
    A["Train job writes a new<br/>immutable version"] --> B["Registry: promote by<br/>version, never overwrite"]
    B --> C["Keep last N=3 good<br/>models loaded and hot"]
    C -.-> AL["One-command rollback:<br/>repoint current alias"]
    D["Regression detected<br/>(legit-decline 2x baseline)"] -.-> AL
    AL --> E["Quarterly game-day<br/>rehearses the rollback"]
    E -.-> AL

What good looks like

The lesson

A model is a data artifact, not code — you can only roll back to a version you deliberately kept, hot and reachable, and only if you've rehearsed the switch. Never let a deploy overwrite the model it replaces, and prove the rollback works before the incident that needs it.

Named concepts: immutable versioned artifacts, model registry, instant rollback (hot standby), rollback game-day.

Real-world echoes

Transfer

For your highest-stakes model: if it regressed right now and the call were "put the previous version back," could an on-call engineer do it in under five minutes — and does the previous version's exact bytes still exist, hot and reachable? Trace where your training job writes its output and ask what happens to the artifact it replaced.

Make it a ticket: switch the model's training output to immutable versioned paths with bucket versioning on, retain the last 3 production versions hot, write and time a one-command rollback runbook, and schedule a quarterly game-day that promotes a known-bad model and rehearses the rollback to a measured under-5-minute target.

Related