Study guidePro

Databricks Machine Learning Professional

Every domain of the Databricks Machine Learning Professional exam explained, with decision tables, runnable Python, diagrams and the traps the questions set, following the September 2025 exam guide.

Aligned to the September 2025 exam guide · checked against the Databricks docs on 31 October 2026 · certlake.com/exam/machine-learning-professional/study-guide

Exam at a glance

Questions59 scored, multiple choice (plus up to 10 unscored)
Time120 minutes
Pass markNot published by Databricks; practise to 70% or better
LanguagePython
Exam guideSeptember 2025
DomainWeight≈ Questions
Model Development44%26
MLOps44%26
Model Deployment12%7

1. Model Development44% of the exam

Spark ML or single node?

SituationUse
Data too big for one machine's memory; classic algorithms (linear, trees, GBT)Spark ML on the cluster
Data fits on one nodescikit-learn, XGBoost, LightGBM on the driver
Many small models, one per groupsingle-node library inside applyInPandas
Deep learningPyTorch / TensorFlow with TorchDistributor or Ray

For inference: batch and streaming scoring of huge tables suit a Spark ML PipelineModel (for a stream, apply the logged model as an MLflow UDF, mlflow.pyfunc.spark_udf). Real-time, low-latency requests suit a single-node model behind Model Serving, since a Spark model needs a Spark session to score.

A Spark ML pipeline

Transformers have transform() (StringIndexer model, VectorAssembler, OneHotEncoder model); estimators have fit() and return a transformer (StringIndexer, LogisticRegression). A Pipeline is an estimator; fitting it gives a PipelineModel.

from pyspark.ml import Pipeline
from pyspark.ml.feature import (
    StringIndexer, OneHotEncoder, VectorAssembler)
from pyspark.ml.classification import (
    LogisticRegression)
from pyspark.ml.evaluation import (
    BinaryClassificationEvaluator)
from pyspark.ml.tuning import (
    CrossValidator, ParamGridBuilder)

idx = StringIndexer(inputCol="plan",
                    outputCol="plan_idx",
                    handleInvalid="keep")
ohe = OneHotEncoder(inputCols=["plan_idx"],
                    outputCols=["plan_vec"])
vec = VectorAssembler(
    inputCols=["plan_vec", "tenure", "spend"],
    outputCol="features")
lr = LogisticRegression(labelCol="churned")
pipe = Pipeline(stages=[idx, ohe, vec, lr])

grid = (ParamGridBuilder()
        .addGrid(lr.regParam, [0.01, 0.1])
        .addGrid(lr.elasticNetParam, [0.0, 0.5])
        .build())
ev = BinaryClassificationEvaluator(
    labelCol="churned", metricName="areaUnderROC")
cv = CrossValidator(estimator=pipe,
                    estimatorParamMaps=grid,
                    evaluator=ev, numFolds=3,
                    parallelism=4)
model = cv.fit(train_df).bestModel
print(ev.evaluate(model.transform(test_df)))
model.write().overwrite().save(path)
  • CrossValidator fits each grid point numFolds times, then refits the best on all the data; TrainValidationSplit fits each once, on one split (cheaper, noisier). parallelism fits models concurrently.
  • Evaluators: BinaryClassificationEvaluator (areaUnderROC, areaUnderPR), MulticlassClassificationEvaluator (f1, accuracy), RegressionEvaluator (rmse, r2, mae).
  • Save and reload with PipelineModel.load(path), or log it with mlflow.spark.log_model.

Scaling training out

ChoicePick it when
Vertical (bigger node, more GPU memory)The library is single node, or communication would dominate; simplest
Horizontal (more nodes)The data or the search is too big for one node and the work parallelises
Data parallelismThe model fits on one GPU: each worker trains a copy on a shard and gradients are averaged
Model parallelismThe model doesn't fit on one GPU: layers or tensors are split across devices
SparkRay
Best atData-parallel ETL, Spark ML, scoring big tablesTask-parallel Python: tuning, RL, deep learning, many small tasks
On DatabricksThe clustersetup_ray_cluster() from ray.util.spark, on the same cluster (Runtime 12.2 LTS ML+)

TorchDistributor(num_processes=…, local_mode=False, use_gpu=True).run(train_fn) runs PyTorch distributed training on Spark workers; local_mode=True runs all processes on the driver.

Group-wise models with the pandas function API

import pandas as pd
from sklearn.linear_model import LinearRegression

def fit_store(pdf: pd.DataFrame) -> pd.DataFrame:
    m = LinearRegression().fit(
        pdf[["price", "promo"]], pdf["units"])
    return pd.DataFrame({
        "store_id": [pdf["store_id"].iloc[0]],
        "r2": [m.score(pdf[["price", "promo"]],
                       pdf["units"])]})

out = (df.groupBy("store_id")
         .applyInPandas(fit_store,
             schema="store_id long, r2 double"))

applyInPandas gets one pandas DataFrame per group (a whole group must fit in an executor's memory); mapInPandas gets an iterator of batches with no grouping, which suits inference. A pandas UDF (@pandas_udf) is column in, column out, its type hints pick the UDF variant, and the return type goes in @pandas_udf(...).

Distributed tuning: Optuna and Ray Tune

import mlflow
from mlflow.optuna.storage import MlflowStorage
from mlflow.pyspark.optuna.study import (
    MlflowSparkStudy)

storage = MlflowStorage(experiment_id=exp_id)
study = MlflowSparkStudy(
    study_name="gbt-tuning", storage=storage)
study.optimize(objective, n_trials=32, n_jobs=4)
print(study.best_params)
  • MlflowStorage keeps the study in the MLflow experiment; MlflowSparkStudy runs the trials on Spark executors (MLflow 3, preinstalled in Runtime 17.0 ML+).
  • Optuna's pruners (default MedianPruner) stop poor trials early; the default sampler is TPE.
  • Ray Tune: Tuner(...).fit() on a Ray-on-Spark cluster; log to MLflow with Ray's MLflow integration.

Advanced MLflow

with mlflow.start_run(run_name="search"):
    for p in grid:
        with mlflow.start_run(nested=True):
            mlflow.log_params(p)
            mlflow.log_metric("cv_auc", cv_auc(p))
    mlflow.log_dict(summary, "summary.json")
    mlflow.log_figure(fig, "confusion.png")
  • One parent run for the search, a child run per parameter set (logging the cross-validated metric), then the best set retrained and logged with its model.
  • log_param(s), log_metric(s) (with step=), log_artifact(s), log_dict, log_figure, log_table, set_tag(s).

Custom models with real-time feature engineering: subclass mlflow.pyfunc.PythonModel and compute features from the request inside predict (see domain 3), so training and serving share one code path.

Feature Store: point in time

Point-in-time lookup: a label on March 7 joins the feature value from March 5, the latest at or before its timestamp, and never the March 9 value from its futureFeature table main.ml.txn_feats, card_id 42Mar 1 · avg 40feature rowMar 5 · avg 55latest at or beforeMar 9 · avg 70future: not usedtimeLabel row · Mar 7gets avg = 55timestamp_lookup_key= "event_ts"
timestamp_lookup_key turns the join into an AS OF join: each label row gets the latest feature value at or before its own timestamp, so no future data leaks into training.
from datetime import timedelta
from databricks.feature_engineering import (
    FeatureEngineeringClient, FeatureLookup,
    FeatureFunction)

fe = FeatureEngineeringClient()
fe.create_table(
    name="main.ml.txn_feats",
    primary_keys=["card_id", "ts"],
    timeseries_columns="ts", df=feats_df)

ts = fe.create_training_set(
    df=labels_df,
    feature_lookups=[
        FeatureLookup(
            table_name="main.ml.txn_feats",
            lookup_key="card_id",
            timestamp_lookup_key="event_ts",
            lookback_window=timedelta(days=7)),
        FeatureFunction(
            udf_name="main.ml.amount_ratio",
            input_bindings={"amt": "amount",
                            "avg": "avg_30d"},
            output_name="amount_ratio")],
    label="is_fraud")
  • A time series feature table has one timestamp key, in its primary keys. A lookup with timestamp_lookup_key joins the latest feature value at or before each label's timestamp (an AS OF join), so no future data leaks in; no earlier value gives null.
  • lookback_window drops values older than the window, in training and batch inference; online lookups always use the latest value.

Feature pipelines, online serving, on-demand features

  • Automated pipelines: a scheduled job or Lakeflow pipeline computes features and calls fe.write_table(name=…, df=…, mode="merge") (merge is the only mode). Pass a streaming DataFrame and write_table returns a StreamingQuery, keeping features fresh as events arrive.
  • Online Feature Store: fe.create_online_store(name=…, capacity="CU_1"), then fe.publish_table(online_store=…, source_table_name=…, online_table_name=…). publish_mode is TRIGGERED (default, incremental) or CONTINUOUS (streaming), both needing Change Data Feed on the source, or SNAPSHOT (one full copy).
  • The legacy Databricks SDK online_tables API is gone with Online Tables; the SDK's feature_store service still deletes online tables (delete_online_table).
  • A model logged with fe.log_model and served looks up published features by key; Feature Serving endpoints serve a feature spec to non-model clients.
  • On-demand features are Python UDFs in Unity Catalog (CREATE FUNCTION … LANGUAGE PYTHON) wrapped in FeatureFunction. They're computed from request data at inference time and by create_training_set at training time: one definition, no skew. Runtime 13.3 LTS ML+.

The rest of this guide is part of Pro

2 more domains, explained with snippets, decision tables and diagrams:

  • 2. MLOps · 44%
  • 3. Model Deployment · 12%

Know it, or just recognise it?

The free diagnostic shows which domains you'd lose marks on, in about 10 minutes and without signing up.

Take the free diagnostic

Cheat sheet

The two-page cheat sheet: rules, defaults and traps for the last week before the exam.

Open the cheat sheet

Spotted something out of date? Email support@certlake.com. CertLake is not affiliated with Databricks, Inc.