Module 5 — Model registry and promotion stages
Every run of module 3 produces a candidate model, and DVC now guarantees each candidate is reproducible. But an application serving churn predictions must not pick a random run — it must pick the anointed one, with a clear name and a documented history of how it got there. That is the job of the model registry.
What a registry is, and what it is not
The MLflow registry is a versioned catalog that sits next to the tracking server. A registered model has a stable name (churn-classifier), a sequence of numbered versions (v1, v2, v3…), and metadata: description, tags, transitions, comments. A serving application does not reference a run ID — it references churn-classifier@production, and the registry resolves that alias to the current version.
The registry is not:
- A copy of every experiment run. Only a fraction of runs deserve registration.
- A backup of your artifacts. The bytes still live in the artifact store; the registry references them.
- A permissions system in itself. You still need auth on the tracking server.
Registering a model from a run
Given a promising run whose artifact path is runs:/<run_id>/model, registration is one call:
import mlflow
result = mlflow.register_model(
model_uri=f"runs:/{run_id}/model",
name="churn-classifier",
)
# result.version == "7"
MLflow creates the model on first call and adds a new version afterwards. Every version carries the run ID it came from, so tracking and registry stay tied.
Aliases versus stages
The old MLflow API used four stages: None, Staging, Production, Archived. Modern MLflow (2.x and 3.x) prefers aliases: arbitrary labels you attach to a version. @production points at v7 today, at v8 tomorrow. @shadow can point at a candidate being evaluated in parallel. @baseline can freeze last quarter's model for regression tests.
Aliases are strictly better than stages, for one reason: they support A/B testing and shadow deployments without fighting the tool. Two versions can be simultaneously served, one under @production and one under @shadow, and monitoring compares them.
client = mlflow.MlflowClient()
client.set_registered_model_alias("churn-classifier", "production", version=7)
client.set_registered_model_alias("churn-classifier", "shadow", version=8)
The approval gate
Registering a version and pointing an alias at it are two distinct actions. The gap between them is the approval gate. In a mature project, it is not a person clicking a button — it is a checklist executed automatically before the alias moves:
- Reproducibility check: the model rebuilds from its logged commit and data hash.
- Validation metrics on a held-out slice and on the latest week of production data.
- Comparison to the current
@production: no metric may regress beyond a stated threshold. - Fairness or bias tests, when applicable.
- Latency test: the model responds under the SLA on the target hardware.
- Documentation completeness: signature, sample input, description filled in.
Module 7 wires all six into GitHub Actions. Until then, they live as a checklist next to the pull request.
Promotion without validation: the pitfall this module is really about
The most common failure mode of a registry is treating it as a folder. Someone finds a good ROC AUC on a Monday, calls set_alias("production", ...) from a notebook, and by Wednesday production is returning worse predictions than before. Two symptoms of this pathology:
- The alias moves outside of a pipeline. If the only trail is a Slack message, the change is not auditable.
- Metrics were computed on the training-time validation set, which is not the current data distribution. A model that beats v7 on August data can lose to v7 on September data.
Fixing the pathology requires two changes: only CI moves aliases (no manual promotion from a notebook), and the validation compares against the live champion on a recent slice, not against the training-time metric.
Rolling back in one command
Every promotion carries an implicit contract: it can be undone quickly. With aliases, rollback is trivial in principle:
mlflow.set_registered_model_alias("churn-classifier", "production", version=7)
That moves @production back to v7. What makes this cheap is that v7's bytes are still in the artifact store, its metadata is intact, and the serving layer reads the alias at inference time. What makes it hard is when serving cached the model at startup: rollback then requires a redeploy, delaying recovery to minutes. Module 10 returns to this trade-off.
Metadata a registered version must carry
A registered version without metadata is a bomb waiting six months. Insist that each version records:
- The git commit and data hash (from module 4).
- A short description of what changed since the previous version.
- The validation metrics used at promotion time.
- The training environment (framework version, Python version), captured by
log_model. - The owner: a team, not a person, so a promotion can be reviewed when the author has left.
- Any known limitations (segments the model performs poorly on, minimum required latency).
client.update_model_version(
name="churn-classifier", version=7,
description=(
"Adds contract_duration_bucket feature. "
"ROC AUC on Aug slice: 0.884 vs 0.871 (v6). "
"Owner: retention-ml. Regressions: none over 5 seeds."
),
)
client.set_model_version_tag("churn-classifier", "7", "owner", "retention-ml")
The temptation to register every run "just in case" turns the registry into noise. Register only what a promotion pipeline would consider — typically 5–10 % of runs. The rest stay in the tracking server, findable by search.
Summary
- A registered model has a stable name and numbered versions; aliases like
@productionand@shadowpoint at those versions and are what serving code references. - Prefer aliases over stages: they support A/B and shadow deployments natively.
- The gap between registration and alias is the approval gate: reproducibility, comparison against the current champion on recent data, latency, documentation.
- Never promote from a notebook; only CI moves aliases, and every version carries enough metadata (commit, data hash, owner, limitations) to be reviewed months later.
Next module: containerization — building the artifact that the CI/CD pipeline will actually deploy.