Databricks Tips #5: MLflow + Unity Catalog — from experiment to model in production
Fifth installment of Databricks Tips. If you’re preparing for the ML Professional certification or work with models on Databricks, here’s the modern MLflow workflow integrated with Unity Catalog.
The shift: from Workspace Registry to Unity Catalog
The old model (Workspace Model Registry) used stages: None → Staging → Production → Archived. The new model (Unity Catalog) uses aliases:
import mlflow
# Old (do NOT use): workspace registry with stages
mlflow.set_registry_uri("databricks")
# Model registered at workspace level, no governance
# New: Unity Catalog registry
mlflow.set_registry_uri("databricks-uc")
# Model registered as catalog.schema.model_nameWhy it matters: with UC, models get the same permissions, lineage and auditing as your Delta tables. An analyst can see which tables a model was trained on without having to ask.
Model registration: the full workflow
import mlflow
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.metrics import f1_score
mlflow.set_registry_uri("databricks-uc")
# 1. Train with tracking
with mlflow.start_run(run_name="gbm_v2") as run:
model = GradientBoostingClassifier(
n_estimators=200,
max_depth=5,
learning_rate=0.1
)
model.fit(X_train, y_train)
# Metrics
preds = model.predict(X_test)
f1 = f1_score(y_test, preds)
mlflow.log_metric("f1_score", f1)
mlflow.log_param("n_estimators", 200)
# Log the dataset for lineage
dataset = mlflow.data.from_spark(
training_df,
table_name="catalog.gold.features",
version="3"
)
mlflow.log_input(dataset, context="training")
# 2. Register in Unity Catalog
model_uri = f"runs:/{run.info.run_id}/model"
mv = mlflow.register_model(
model_uri,
"catalog.ml.churn_predictor"
)
print(f"Version: {mv.version}")Aliases: the replacement for stages
Forget about transition_model_version_stage(). Aliases are the way now:
from mlflow import MlflowClient
client = MlflowClient()
# Assign alias (replaces stages)
client.set_registered_model_alias(
name="catalog.ml.churn_predictor",
alias="champion",
version=3
)
client.set_registered_model_alias(
name="catalog.ml.churn_predictor",
alias="challenger",
version=4
)
# Load model by alias (in production)
model = mlflow.pyfunc.load_model(
"models:/catalog.ml.churn_predictor@champion"
)
# Compare champion vs challenger
champion = mlflow.pyfunc.load_model(
"models:/catalog.ml.churn_predictor@champion"
)
challenger = mlflow.pyfunc.load_model(
"models:/catalog.ml.churn_predictor@challenger"
)The advantage of aliases: you can have multiple aliases at the same time (champion, challenger, shadow, rollback). With stages you only had one per stage.
Data-to-model lineage
When you log the dataset with mlflow.log_input(), Unity Catalog connects the feature table to the trained model. That answers questions like:
- “Which tables were used to train this model?” → visible in the lineage
- “If I change the
gold.featurestable, which models are affected?” → reverse lineage - “Which version of the data was model v3 trained on?” → run metadata
# View a model's inputs
run = client.get_run(run_id)
for input_data in run.inputs.dataset_inputs:
print(f"Dataset: {input_data.dataset.name}")
print(f"Source: {input_data.dataset.source}")
print(f"Context: {input_data.tags}")Model Serving with AI Gateway
Once the model is registered, serving it is just configuration:
import requests
# Create endpoint via API
endpoint_config = {
"name": "churn-predictor",
"config": {
"served_entities": [
{
"entity_name": "catalog.ml.churn_predictor",
"entity_version": "3",
"workload_size": "Small",
"scale_to_zero_enabled": True
}
],
"traffic_config": {
"routes": [
{
"served_model_name": "churn_predictor-3",
"traffic_percentage": 100
}
]
}
}
}
# A/B testing: split traffic between champion and challenger
ab_config = {
"routes": [
{
"served_model_name": "churn_predictor-3", # champion
"traffic_percentage": 90
},
{
"served_model_name": "churn_predictor-4", # challenger
"traffic_percentage": 10
}
]
}AI Gateway adds rate limiting, logging and guardrails on top of the endpoint:
# Every call to the endpoint is automatically logged
# into an inference logs table for monitoringFeature Engineering with Feature Store
The Databricks Feature Store is integrated with Unity Catalog:
from databricks.feature_engineering import FeatureEngineeringClient
fe = FeatureEngineeringClient()
# Create feature table (it's a Delta table with a PK)
fe.create_table(
name="catalog.ml.customer_features",
primary_keys=["customer_id"],
timestamp_keys=["event_date"],
df=feature_df,
description="Customer features for churn prediction"
)
# Train with feature lookups (automatic lineage)
from databricks.feature_engineering import FeatureLookup
training_set = fe.create_training_set(
df=labels_df, # only customer_id + label
feature_lookups=[
FeatureLookup(
table_name="catalog.ml.customer_features",
feature_names=["total_purchases", "days_since_last"],
lookup_key="customer_id"
)
],
label="churned"
)
training_df = training_set.load_df()
# The model knows where the features come from
# At serving time, it looks them up automaticallySummary table: old vs new
| Concept | Workspace Registry | Unity Catalog |
|---|---|---|
| Namespace | model_name |
catalog.schema.model |
| Promotion | Stages (Staging→Production) | Aliases (champion, challenger) |
| Permissions | Workspace ACLs | UC GRANTS |
| Lineage | Manual | Automatic |
| Feature Store | Separate | Integrated with UC |
| Governance | Limited | Full |
Next week: Feature Engineering — how to design features that scale and don’t break in production.