关于 databricks-serverless-migration
databricks-serverless-migration 是用于分析 Databricks 工作负载并从经典计算迁移到无服务器计算的 Agent Skill,面向迁移笔记本、作业和管道的开发者。它按收集、分析、测试和验证的流程检查兼容性,并给出代码与配置修正建议。
主要功能
- 01分析经典计算工作负载
- 02检测无服务器兼容性问题
- 03提供代码和配置修正建议
- 04按笔记本逐项处理迁移
- 05对比测试并验证迁移
适用场景
- 01迁移笔记本、作业或管道
- 02检查现有代码的无服务器兼容性
- 03为无服务器计算编写新代码
- 04排查迁移后的专属错误
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ databricks/ databricks-agent-skills --skill databricks-serverless-migration安装后在 Claude Code 中输入/databricks-serverless-migration确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
agents/openai.yamlassets/databricks.svgreferences/code-patterns.mdcompatibility-checks.mdconfiguration-guide.mdfailure-reporting.mdinstall-in-databricks-genie-code.mdjar-migration.mdmlflow-uc-patterns.mdmulti-source-enumeration.mdnetworking-and-security.mdstreaming-migration.md详细介绍
无服务器计算迁移
首先:使用父级 databricks-core 技能了解 CLI 基础知识、身份验证和配置文件选择。
分析现有 Databricks 代码与无服务器计算的兼容性,并指导从经典集群迁移。此技能遵循 4 步迁移生命周期:采集工作负载 → 分析兼容性 → 通过 A/B 对比进行测试 → 验证并迭代。
何时使用此技能
- 将笔记本、作业或管道从经典计算迁移到无服务器计算
- 检查现有代码是否兼容无服务器计算
- 编写面向无服务器计算的新代码
- 排查迁移后特有的无服务器错误
- 在性能优化模式和标准模式之间进行选择
在何处运行此技能
此技能以 Agent Skill(agentskills.io)的形式发布,可在任何兼容客户端中运行:
- Claude Code、Cursor 或笔记本电脑上的任何 agentskills.io 客户端——默认方式。通过
databricks aitools install安装,或参阅各客户端的文档。 - 在 Databricks 工作区中通过 Genie Code Agent 模式运行——将技能放入
/Workspace/Users/<you>/.assistant/skills/databricks-serverless-migration/(按用户配置)或/Workspace/.assistant/skills/databricks-serverless-migration/(整个工作区范围,仅限管理员)。有关三种安装方法以及重要的无服务器计算注意事项(无服务器环境中未预装 Databricks CLI,因此需要调整某些部署步骤),请参阅在 Databricks Genie Code 中安装。
如果你为一位目前通过笔记本电脑客户端使用此技能的用户完成了迁移分析,请在最后提及一次 Genie Code 选项——许多用户更愿意在工作负载所在的工作区内迭代迁移。
了解迁移阻碍因素
迁移阻碍因素分为三类。请将精力重点放在第 2 类上——此技能对此最有帮助。
| 类别 | 描述 | 操作 |
|---|---|---|
| 1. 功能逐步扩展 | Databricks 正在积极扩展支持范围(例如 SparkML、自定义 JDBC) | 先使用变通方案,之后再重新评估 |
| 2. 需要更改代码/配置 | 你的代码使用了需要针对无服务器计算更新的模式(例如 RDD、DBFS、流式触发器) | 此技能可提供帮助——它会检测这些模式并提供修复方案 |
| 3. 仅限经典计算 | 工作负载需要无服务器环境不具备的能力(例如 root 操作系统访问权限、R 语言) | 继续使用经典计算 |
决策树:我的工作负载已准备就绪吗?
Workload → Check language
├── R code → Category 3: keep on classic
├── Scala notebook cells → Category 2: port to PySpark/SQL, or compile as a JAR job task
├── Compiled Scala JAR (spark_jar_task, build.sbt/pom.xml/build.gradle) → see references/jar-migration.md
│ ├── Scala 2.12 build? → recompile against 2.13.16
│ ├── Bundles spark-core/spark-sql? → use databricks-connect % Provided
│ └── Bundles libs on the kernel classpath? → mark % Provided (see the classpath table in references/jar-migration.md)
├── Python / SQL → Continue
├── Uses RDD APIs? → Category 2: rewrite to DataFrame API (see fixes below)
├── Uses DBFS paths? → Category 2: migrate to UC Volumes
├── Uses Hive Metastore? → Category 2: migrate to Unity Catalog (or use HMS Federation)
├── Uses df.cache/persist? → Category 1: remove and materialize to Delta (native support coming soon)
├── Uses streaming?
│ ├── ProcessingTime trigger → Category 2: use AvailableNow or migrate to SDP
│ ├── Continuous trigger → Category 2: use SDP continuous mode
│ ├── No trigger specified → Category 2: add explicit .trigger(availableNow=True)
│ └── AvailableNow / Once → Ready ✓
├── Uses init scripts? → Category 2: use Environments
├── Uses VPC peering? → Category 2: use NCCs / Private Link
├── Uses unsupported Spark configs? → Category 2: remove (serverless auto-tunes)
├── Uses custom JDBC drivers? → Category 2: use Lakehouse Federation or built-in JDBC
├── Uses Docker containers? → Category 3: use Environments for libs, or keep on classic
└── All clear → Ready for serverless ✓
迁移工作流
步骤 1:采集——收集工作负载背景信息
确认迁移目标是无服务器计算。 此技能专为经典计算 → 无服务器计算迁移而设计。检查、修复和工作流都面向无服务器计算架构(Spark Connect、Environments、NCC)。如果用户想在经典 DBR 版本之间升级,则此技能不适用——经典 DBR 升级的兼容性范围不同,应遵循标准 DBR 升级指南。
全面收集需要迁移到无服务器计算的工作负载信息:
- 阅读用户的笔记本/脚本文件
- 确认经典集群配置(实例类型、DBR 版本、Spark 配置、初始化脚本、库)
- 记录网络设置(VPC 对等连接、实例配置文件、挂载)
- 了解工作负载类型:批处理作业、流式处理、交互式笔记本、管道
- 确定目标:输出始终是无服务器计算配置,而不是采用较新 DBR 的经典集群
Step 1.5:处理多笔记本工作负载
如果工作负载涉及多个用户笔记本(统计时不包括 _resources/ 设置笔记本),请逐个处理,不要一次性处理所有笔记本。Agent 的上下文窗口容量有限,在执行分析、修复和迁移时,试图在活动上下文中同时保留一个包含 8 个笔记本的任务的全部源内容,会导致自动压缩反复触发(Claude Code),或在其他客户端中引发同等的上下文溢出故障。
流程:
- 先枚举。 列出每个用户笔记本及其路径。此时不要阅读全文,只需阅读几行以了解概况。
- 检查是否存在
bundle_config.py(H4)。 如果源代码树中包含bundle_config.py,请解析该文件,并遵循其中声明的上游源(git URL、S3 路径、init_scripts、git_source或自定义上游仓库声明)。将每个引用的仓库克隆或获取到同级目录,并将其中的笔记本纳入枚举。在迁移计划中,将“外部源”列为一类独立的产物。若省略此步骤,多源演示(例如,dbt-on-databricks引用上游dbt-databricks-c360仓库)就会被错误枚举,导致遗漏真正的任务笔记本。
- 检查是否存在
- 依次处理每个笔记本:
a. 阅读其完整源内容。
b. 仅针对当前笔记本运行 Step 2 Analyze 检查清单。
c. 在回复中或临时文件中记录结构化摘要,然后从活动工作记忆中移除原始源内容。摘要必须包括:
notebook_path(相对路径)detected_patterns[](此技能目录中的模式 ID)blockers[](此笔记本命中的第 3 类模式,如有)migration_steps[](按顺序列出的具体修复措施)unmigratable(布尔值——如果任何阻碍因素都没有变通方案,则为 true) d. 如果你有可写的临时目录(~/.databricks-migration-skill/scratch/<run-id>/findings/),请将摘要保存为<notebook-basename>.json。这样可以安全地将源内容从上下文中释放出来。否则,请在对话记录中保留精简摘要。
- 综合。 所有笔记本都有摘要后,仅根据这些摘要制定统一的迁移计划。逐个笔记本应用修复,除非需要消除摘要中的歧义,否则不要重新读取原始源内容。
- 失败报告。 每个工作负载严格触发一次 Failure Reporting Protocol,而不是每个笔记本触发一次。报告中的
detected_patterns数组汇总所有笔记本的数据;notebook_characteristics则使用行数总和,以及语言、流处理和 ML 标志的并集。
阈值指南:当用户笔记本数量 ≥ 3,或单个笔记本的源内容超过约 5KB 时,应用此流程。对于 1–2 个较小的笔记本,可采用 Steps 2–4 中的单次处理流程。
反模式:不要尝试“将所有笔记本合并成一个大文件”,也不要采用“先读完所有笔记本,再在一个超长轮次中处理”的策略。这两种做法都会重新引入原本要解决的上下文压力问题。
第 2 步:分析 — 扫描无服务器就绪情况
运行 Notebook 前先阅读,不要依赖失败的作业运行来发现问题。 预运行扫描比反复查看错误跟踪更快地发现不兼容问题;许多无服务器运行失败的原因(硬编码的目录引用、初始化脚本、缺少依赖项)很容易通过静态检查发现,但在运行失败后调试的成本很高。
创建或运行任何测试作业之前:
- 阅读作业引用的每个 Notebook 和源文件
- 扫描所有硬编码的目录/架构引用(例如,
spark.table("main.schema.table")、spark.sql("... FROM main...")、catalog = "main") - 检查依赖项模式:初始化脚本、本地 wheel 文件、自定义安装函数、
%pip install行 - 找到任何
requirements.txt或等效文件,并解析完整的依赖项集合 - 标记需要转换或升级处理的操作系统级安装命令(
apt install、yum install)
扫描代码中与无服务器计算架构不兼容的模式。这些检查专用于无服务器环境——无论 DBR 版本如何,其中大多数模式在经典计算上都能正常工作。对于发现的每个问题,请报告:
- 类别:属于 3 种阻塞类别中的哪一种
- 严重性:阻塞项(无服务器环境必须修复)/警告(应该修复)/信息(供了解)
- 模式:检测到的内容及其位置
- 修复方法:针对无服务器计算的具体修复方案
改写后 lint:Cell magic 边界检查(A1)
影响重大。 在宣布迁移后的 Notebook 已就绪之前,请对每个输出单元格运行此 lint 检查。在 dbdemos E2E 扫描中,导致 7 个演示中有 3 个失败(hls-readmission、fsi-fraud、retail-c360)。
检测:查找包含 # MAGIC %<word> 行的单元格(例如,# MAGIC %run、# MAGIC %sql、# MAGIC %md、# MAGIC %pip、# MAGIC %fs)。在该单元格中,每个非空行都必须以 # MAGIC 开头。其他行必须为空行。如果同一单元格中有纯 Python 注释(或任何其他 Python 代码)出现在 # MAGIC %... 指令之前,则该单元格已损坏:Databricks 会将其解析为 Python,导致 %run 回退到 IPython 行 magic,并产生类似 File './00-global-setup-v2' not found 的错误。
修复:切勿在同一单元格中将纯 Python 注释放在 # MAGIC %... 行之前。以下两种方式均有效:
- 首选:在上方单独的
# MAGIC %md单元格中添加迁移说明(使用其自己的# COMMAND ----------块)。 - 可接受:完全删除迁移说明,依赖 git/文件历史记录。
修改前示例(已损坏;%run 会报错 File not found):
# COMMAND ----------
# Migration: relative '%run ../../../_resources/00-global-setup-v2' may not
# resolve under serverless job tasks. Replaced with sibling reference.
# MAGIC %run ./00-global-setup-v2
修改后示例(正常;%run 作为 cell magic 执行):
# COMMAND ----------
# MAGIC %md
# MAGIC Migration: relative %run path replaced with sibling reference.
# COMMAND ----------
# MAGIC %run ./00-global-setup-v2
类别 A:不支持的 API
| 模式 | 严重级别 | 修复方法 |
|---|---|---|
sc.parallelize(data) | 阻断 | spark.createDataFrame([(x,) for x in data], ["value"]) |
rdd.map(fn) | 阻断 | df.select(F.col("value") * 2) 或 df.withColumn(...) |
rdd.filter(fn) | 阻断 | df.filter(F.col("value") > 3) |
rdd.reduce(fn) | 阻断 | df.agg(F.sum("col")).collect()[0][0] |
rdd.flatMap(fn) | 阻断 | df.select(F.explode(F.split(col, " "))) |
rdd.groupByKey() | 阻断 | df.groupBy("key").agg(F.collect_list("value")) |
rdd.mapPartitions(fn) | 阻断 | df.groupBy(F.spark_partition_id()).applyInPandas(fn, schema) |
sc.textFile(path) | 阻断 | spark.read.text(path) |
sc.wholeTextFiles(path) | 阻断 | spark.read.format("binaryFile").load(path) |
sc.broadcast(data) | 阻断 | from pyspark.sql.functions import broadcast; df.join(broadcast(lookup_df), key) |
sc.accumulator(init) | 阻断 | df.agg(F.sum("col")) 或 df.count() |
spark.sparkContext | 阻断 | 直接使用 spark(SparkSession) |
SparkContext.getOrCreate() | 阻断 | 不支持——会引发 RuntimeError: Only remote Spark sessions using Databricks Connect are supported。替换为 spark.createDataFrame() 或 spark.range() 来设置数据。 |
sqlContext.sql(query) | 阻断 | spark.sql(query) |
sc.hadoopConfiguration.set(...) | 阻断 | 使用 UC 外部位置——无需凭据配置 |
df.cache() / df.persist() | 警告 | 移除缓存调用。对于开销较大的中间结果,将其物化到 Delta 表。原生支持即将推出。 |
df.checkpoint() | 警告 | 改为写入 Delta 表 |
spark.catalog.cacheTable(t) / CACHE TABLE | 警告 | 移除——无服务器环境中不需要 |
notebook 中的 %scala 单元格 | 阻断 | 移植到 PySpark/SQL,或编译为 JAR 以用于作业任务 |
notebook 中的 %r 单元格 | 阻断 | 无对应的无服务器方案——保留在经典环境中或移植到 PySpark |
Hive 变量语法 ${var} | 警告 | 使用 DECLARE VARIABLE / SET VARIABLE(SQL)或 Python f-string |
CREATE GLOBAL TEMPORARY VIEW | 阻断 | 使用 CREATE OR REPLACE TEMPORARY VIEW——无服务器环境中不存在 global_temp 数据库 |
查询中的 global_temp. 前缀 | 警告 | 移除前缀——无需限定符即可访问会话级临时视图 |
使用 from pyspark.sql.functions import * 时的内置函数 max(..., key=) / min(..., key=) / sorted(..., key=)(A2) | 阻断 | pyspark.sql.functions.max 会覆盖内置函数,并拒绝 key=(引发 TypeError: max() got an unexpected keyword argument 'key')。使用排序+索引:xs.sort(key=...); top = xs[0]。参见 MLflow on UC。 |
from databricks import automl / automl.classify() / automl.regress() / automl.forecast()(A3) | 阻断 | AutoML 在无服务器环境中不可用,而且 DBDemos.create_mockup_automl_run 后备方案在 Spark Connect 上会报错 PlanMetrics not JSON serializable。改写为内联 scikit-learn Pipeline,并使用 mlflow.sklearn.log_model + mlflow.register_model + UC 别名。参见 MLflow on UC。 |
mlflow.pyfunc.spark_udf(...) 后接 df.withColumn("prediction", loaded_model(struct(*features)))(A4) | mlflow 2.19.0 上为阻断 | Spark Connect 上的闭包错误:batch_predict_fn 将 loaded_model 捕获为自由变量;工作进程会失败并报错 NameError: cannot access free variable 'loaded_model'。根本原因是 Spark Connect 序列化。 首选修复方法(可移植,适用于任何 mlflow):在驱动程序端进行 pandas 推理:mlflow.pyfunc.load_model(uri).predict(df.toPandas()),然后执行 spark.createDataFrame(...)。后备修复方法:在环境规范中固定 mlflow>=2.20.0。 |
创建模型服务端点时使用 AutoCaptureConfigInput(enabled=...)(A5) | 警告 | 已弃用的参数,会导致首次部署端点失败。从 EndpointCoreConfigInput(...) 中彻底移除 auto_capture_config=AutoCaptureConfigInput(...) 参数。 |
在作用域中调用 mlflow.set_registry_uri("databricks-uc") 时使用 mlflow.<flavor>.log_model(..., registered_model_name=...)(M1) | 阻断 | 在 UC 下,registered_model_name= 会触发内部 get_model_version_by_alias(..., 'Champion') 调用;对于全新模型,该调用会引发 RESOURCE_DOES_NOT_EXIST。从 log_model 中移除该关键字参数;运行结束后,调用 mlflow.register_model(model_uri=f"runs:/{run.info.run_id}/model", name=<full_name>) 和 MlflowClient().set_registered_model_alias(...)。参见 MLflow on UC。 |
访问 UC 注册模型的 .latest_versions(例如,client.get_registered_model(name).latest_versions)(M2) | 阻断 | UC 上的 RegisteredModel.latest_versions 始终为 None;max(None, key=...) 会引发 TypeError: 'NoneType' object is not iterable。使用 client.search_model_versions(f"name='{name}'") + 排序+索引(参见上文 A2)。参见 MLflow on UC。 |
在作用域中调用 mlflow.set_registry_uri("databricks-uc") 时使用未带 signature= 关键字参数的 mlflow.<flavor>.log_model(...)(M3) | 阻断 | UC 要求每个注册模型都必须有模型签名。未提供 signature= 时,log_model 会引发 MlflowException: Model signature is required for registering a model to Unity Catalog。根据样本推断:signature = infer_signature(X_sample, model.predict(X_sample)),然后将其作为 signature=signature 传给 log_model。参见 MLflow on UC。 |
二分类器的预测列以 float64(Double)类型写入,而下游 Delta 表要求 Integer(M4) | 首次写入时为阻断 | sklearn 二分类器(例如 A3 中从 AutoML 改写为 sklearn 的方案)会将 predict() 结果输出为 float64。写入 prediction 列为 IntegerType 的 Delta 表时,会因 DELTA_FAILED_TO_MERGE_FIELDS: prediction (Double) vs prediction (Integer) 而失败。写入前进行类型转换:df.withColumn("prediction", col("prediction").cast("integer"))。参见 MLflow on UC。 |
| 类别 B:数据访问 |
| 模式 | 严重性 | 修复方法 |
|---|---|---|
持久化数据的 dbfs:/ 或 /dbfs/ 路径 | 阻断 | 替换为 /Volumes/<your_catalog>/schema/volume/path |
dbfs:/tmp/、/dbfs/tmp/,以及包含 cache/scratch/temp 的路径 | 警告 | 使用 /tmp/ 或 /local_disk0/tmp/(本地驱动器磁盘)——由于性能原因,临时文件不要使用 Volumes |
file:///dbfs/ FUSE 挂载路径 | 警告 | 将持久化路径替换为 /Volumes/...;将临时路径替换为 /local_disk0/tmp/ |
dbutils.fs.mount(...) | 阻断 | 创建 UC 外部位置和外部卷 |
hive_metastore.db.table | 警告 | 迁移到 UC,或使用 HMS 联邦:CREATE FOREIGN CATALOG ... USING CONNECTION hms_connection |
不带 USE CATALOG 或三级名称的 CREATE DATABASE/CREATE SCHEMA | 阻断 | 在笔记本开头、任何 CREATE 语句之前添加 spark.sql("USE CATALOG <your_catalog>")。从现有表引用中检测目标目录,或询问用户。 |
| IAM 实例配置文件引用 | 警告 | 使用 UC 外部位置和存储凭据 |
| Hive SerDe 表 | 阻断 | 将其迁移为 UC 中的 Delta 表 |
config.py、config/__init__.py、_config*.py 或通过 %run 引用的任何 Python 文件(B1)中的独立 catalog = "<value>" / schema = "<value>" 赋值 | 阻断 | 目录重写必须扫描所有配置文件,而不仅是包含 spark.table(...) 的笔记本正文。将 "main"、"main__build"、"hive_metastore" 等字面值替换为用户的目标目录(通常为 home_<user>)。重写后,对整个迁移树执行 grep,查找残留的字面目录引用。 |
spark.sql("CREATE CATALOG IF NOT EXISTS ...")(B2) | 阻断 | 权限检查会在 IF NOT EXISTS 生效之前触发,因此即使目录已存在,非管理员用户也会遇到 PERMISSION_DENIED: User does not have CREATE CATALOG on Metastore。先使用 SHOW CATALOGS LIKE '...' 进行探测;只有探测结果为空时才执行 CREATE CATALOG。**在整个迁移树中递归应用,包括 _resources/00-global-setup-v2.py 和 config* 文件。**对于用户不拥有的目录,同样适用于 CREATE SCHEMA IF NOT EXISTS 和 CREATE VOLUME IF NOT EXISTS。 |
类别 C:流处理
| 模式 | 严重性 | 修复方法 |
|---|---|---|
.trigger(processingTime=...) | 阻断 | 使用 .trigger(availableNow=True),并设置 maxFilesPerTrigger 或 maxBytesPerTrigger,以避免 OOM |
.trigger(continuous=...) | 阻断 | 迁移到 SDP 连续模式 |
在 writeStream 上未调用 .trigger() | 阻断 | 必须添加 .trigger(availableNow=True)——Spark 默认使用 ProcessingTime("0 seconds"),此模式不受支持 |
| Kafka 源 | 信息 | 可与 AvailableNow 配合使用;使用 maxOffsetsPerTrigger 控制批次大小 |
| Auto Loader | 信息 | 可正常使用;使用 cloudFiles.maxFilesPerTrigger(注意 cloudFiles. 前缀) |
类别 D:配置
| 模式 | 严重程度 | 修复方法 |
|---|---|---|
不支持的 spark.conf.set(...) | 警告 | 移除——仅支持 6 项配置:spark.sql.shuffle.partitions、spark.sql.session.timeZone、spark.sql.ansi.enabled、spark.sql.files.maxPartitionBytes、spark.sql.legacy.timeParserPolicy、spark.databricks.execution.timeout。服务器无服务器模式会自动优化其他所有配置。 |
| 初始化脚本 | 阻断级 | 使用 Environments:通过笔记本的 Environment 面板或 requirements.txt 添加依赖项。固定指定版本。 |
| 集群策略 | 信息 | 使用预算策略进行成本归属 |
| Docker 容器 | 阻断级 | 使用 Environments 管理库。只有在需要 Docker 进行操作系统级自定义时,才继续使用经典模式。 |
%run ./relative/path 或 %run ../path | 警告 | 相对 %run 路径在无服务器模式的作业任务中可能无法正确解析。修复方法:(1) 如果被引用笔记本的代码少于 500 行,将其内联(首选);(2) 使用带绝对路径的 dbutils.notebook.run("<absolute_workspace_path>", timeout)。约 19% 的代码库中发现此问题。 |
os.environ["VAR"](系统/自定义环境变量) | 警告 | 使用带备用值的 os.environ.get()、通过 spark.version 获取 Spark 信息,或通过 dbutils.widgets 获取自定义变量 |
SET hivevar: / ${hivevar:...}(Hive 变量替换) | 阻断级 | 使用 SQL 会话变量:DECLARE OR REPLACE VARIABLE name = value(DBR 14.1+) |
| 环境变量(在初始化脚本中) | 警告 | 使用 dbutils.widgets 或作业参数 |
| 显式配置执行器数量/内存 | 信息 | 移除——无服务器模式会自动扩缩并优化 |
已退役的 Foundation Model 端点引用,例如 databricks-meta-llama-3-1-405b-instruct 及类似端点(D1) | 阻断级 | 扫描所有迁移文件的内容进行检测(而非依据文件名模式)。常见引用位于 ai_query(endpoint => '...')、ChatDatabricks(endpoint=...)、模型服务配置以及 Genie/AI-Functions SQL 中。替换为当前默认端点 databricks-meta-llama-3-3-70b-instruct。最终部署前,确认目标工作区中存在替换后的端点。 |
类别 E:库
| 模式 | 严重性 | 修复方法 |
|---|---|---|
| Notebook 中的 JAR 库 | 阻断 | 编译为 JAR 作业任务(Scala 2.13、JDK 17、环境版本 4+) |
| 已编译 Scala JAR 迁移(版本和依赖冲突) | 阻断 | 使用 Scala 2.13.16 重新编译;将 databricks-connect 设为 % Provided 依赖;将内核捆绑的依赖标记为 % Provided。完整流程和 env-4 类路径请参阅 JAR 迁移 |
| Maven 坐标 | 阻断 | 在 Environments 中替换为 PyPI 包 |
未固定版本的 %pip install | 警告 | 固定版本:%pip install numpy==2.2.2 pandas==2.2.3 |
| 自定义 Spark 数据源(v1/v2 JAR) | 阻断 | 使用 Lakehouse Federation、Lakeflow Connect 或 PySpark 自定义数据源 |
| LZO 格式文件 | 阻断 | 转换为 Parquet 或 Delta |
在 SDP .py 库文件(E1)中通过 mlflow.pyfunc.spark_udf(..., env_manager='local') 加载 AutoML 训练的模型 | 阻断 | SDP 无服务器镜像不包含 databricks-automl-runtime;cloudpickle.load 会引发 ModuleNotFoundError: No module named 'databricks.automl_runtime'。自动在 SDP .py 库文件中添加 %pip install -q databricks-automl-runtime,作为第一条非注释行。SDP .py 库文件支持 %pip install,并会在规划 SQL 流之前、每次更新时运行一次。对于加载 AutoML 训练模型的非 SDP Notebook,同样适用此修复。 |
| AutoML → sklearn 重写(A3)且已存在模型服务端点(E2) | 首次重新部署时阻断 | 重写会更改模型签名(例如,从输入中删除 id)。已存在且固定为旧 AutoML 签名的端点在创建/更新时会因 HTTP 400 错误而失败:Failed to enforce schema of data ... Model is missing inputs ['id']。在下游服务 Notebook 中,针对已迁移的测试端点(而非服务真实流量的生产端点),将 force_update = False 改为 force_update = True,以便端点重新绑定到当前的 prod(或 Champion)别名。更改前,请确认端点名称与迁移后的副本一致。 |
类别 F:网络
| 模式 | 严重性 | 修复方法 |
|---|---|---|
| VPC 对等连接配置 | 阻断 | 创建 NCC,获取稳定 IP,并在资源防火墙中添加允许列表。同一区域的 S3 访问无需更改。 |
| 未使用 UC 的直接 S3/ADLS 访问 | 警告 | 使用 UC 外部位置 |
类别 G:规模调整与调试
| 模式 | 严重性 | 修复方法 |
|---|---|---|
| 大型驱动程序内存配置 | 信息 | 无服务器 REPL 的默认内存为 8GB(可通过 Environments 使用 16GB 及以上的高内存选项) |
| Spark UI 引用 | 信息 | 改用 Query Profile:点击单元格输出下方的“See performance” |
类别 H:作业级配置(dbt_task、SDP、多源、部署前置条件)
这些检查针对作业/管道规范 JSON 和部署前置条件,而非 Notebook 正文。请将其与类别 A–G 中针对每个 Notebook 的检查一并应用。
| 模式 | 严重程度 | 修复方式 |
|---|---|---|
作业规范中的 dbt_task 块(H1) | 对 dbt 工作负载构成阻断 | 执行三项子检查:(1)warehouse_id:将已知繁忙或非无服务器仓库替换为稳定的/专用的 DBSQL 无服务器仓库。(2)project_directory:将路径改写为迁移后的工作区位置(例如,/Workspace/Users/<me>/<demo>-skill-migrated/...)。(3)libraries[]:将仅兼容经典计算的 Python wheel 替换为固定版本的无服务器兼容版本;标记低于 1.7.x 的 dbt-databricks。 |
与原始演示并行部署、且 schema 相同的 SDP 流水线规范(H2) | 阻断 | UC 会拒绝并行部署,并报错 Table is already managed by pipeline <orig-id>。当目标路径包含迁移后缀(例如,-skill-migrated/)时,自动为流水线的目标 schema 添加后缀(例如,dbdemos_retail_c360 → dbdemos_retail_c360_skill_migrated)。对转换文件中硬编码该 schema 的任何 LIVE / STREAMING TABLE 引用执行相同的改写。部署前检查流水线规范:如果任何目标表已存在且归属于其他流水线,则报错并给出清晰提示。 |
工作区 import 的 10 MB 限制(H3) | 提示(可预见) | 部署前置条件:在执行 databricks workspace import 前遍历迁移后的目录树,并标记任何大于 10 MB 的文件。将大型文件(样例 CSV、dbt seed 数据、样例数据集等)改为上传至 UC Volumes(databricks fs cp),而不是通过工作区导入。生成工作区导入命令的同时,输出 UC Volumes 上传命令,并附上文件去向清单。大多数工作负载不会因此受阻,但使用 dbt seed 或大型样例 CSV 的演示会受阻。 |
通过 bundle_config.py 声明上游 git URL 的多源工作负载(H4) | 诊断 | 否则,该工具会错误枚举用户笔记本。解析 bundle_config.py 并跟进所有上游源声明(git URL、S3 路径)。在逐个枚举笔记本前克隆或获取引用的仓库。在迁移计划中将“外部源”列为一类一等产物。请参阅 多源枚举。具体示例:dbt-on-databricks 的 bundle_config.py 引用了上游 dbt-databricks-c360 仓库;实际任务笔记本位于该仓库中,而非本地 dbdemos-notebooks/ 目录树中。 |
必需输出:无服务器环境规范
迁移输出必须包含无服务器环境规范以及迁移后的代码。按以下步骤生成:
- 扫描所有
import语句和%pip install行,以检测所需软件包 - 从作业配置中提取 init script 的
pip install命令 - 生成适用于 Jobs API
environments字段的 JSON 块:
{
"environment_key": "Default",
"spec": {
"client": "2",
"dependencies": ["mlflow==2.12.1", "scikit-learn==1.3.0", "xgboost==2.0.3"]
}
}
重要:机器学习运行时库(mlflow、scikit-learn、hyperopt、xgboost、tensorflow、torch 等)不会预装在无服务器计算环境中。必须在环境规范的 dependencies 中明确列出这些库。无服务器环境不提供机器学习运行时——请始终使用显式声明软件包依赖项的无服务器环境。
步骤 3:测试——双分支策略
使用独立的测试分支和生产分支进行测试,避免仅用于测试的变通方案进入实际发布的代码。测试分支是用于实验的安全沙盒;生产分支只包含生产环境实际需要的更改。
| 方面 | 测试分支 | 生产分支 |
|---|---|---|
| 名称模式 | serverless-test-{job_name}-{timestamp} | serverless-prod-{job_name} |
| 基础分支 | 任意工作分支 | 必须是 master |
| 用途 | 验证无服务器兼容性 | 部署到生产环境 |
| 仅用于测试的变通方案 | 有(目录覆盖、采样数据、日期限制) | 无 |
| 兼容性修复 | 有(在此发现) | 有(应用已验证的修复) |
| 作业配置更改 | 有(针对测试作业) | 有(针对生产作业) |
| 目录 | 测试目录 | 生产目录 |
| 是否需要 PR | 否 | 是 |
| 是否合并到 master | 否 | 是 |
测试分支(serverless-test-{job_name}-{timestamp}):临时分支,无需 PR。
- 从当前工作分支创建分支
- 设置测试数据:使用作业血缘在测试目录中创建上游表的采样副本(见下文的测试数据设置)
- 参数化目录,使笔记本能够同时使用测试数据和生产数据(见下文的目录参数化模式)
- 应用步骤 2 中发现的所有兼容性修复
- 创建无服务器测试作业并运行
- 如果失败,获取错误输出,进行调试和修复,然后重试
- 记录哪些更改是仅用于测试的变通方案,哪些是真正的兼容性修复
生产分支(serverless-prod-{job_name}):必须创建 PR,并从 master 创建。
- 从 master 创建新分支(不要从测试分支创建)
- 仅应用真正的兼容性修复——不包含仅用于测试的变通方案
- 应用作业配置更改(见下文的作业配置转换)
- 提交更改并创建 PR
测试数据设置
如果作业读取生产表,请勿让测试作业直接使用生产数据。而应在专用测试目录中创建上游表的采样副本,并针对这些副本运行测试作业。
推荐模式:
- 根据作业血缘(或通过静态扫描笔记本)确定作业的上游表
- 对每个上游表运行
CREATE TABLE IF NOT EXISTS <test_catalog>.<schema>.<table> AS SELECT * FROM <prod_catalog>.<schema>.<table> LIMIT N(N 通常为 10–1000 行) - 保持架构名称与生产环境相同——只更改目录
- 确保操作具有幂等性:跳过已存在的表,使设置步骤可以安全地重复运行
- 确保 SQL 仓库正在运行,并且拥有测试目录的
CREATE TABLE权限
保留架构名称后,同一份笔记本代码即可在两个环境中运行——只需更改 catalog 微件的值。
决策树:此更改应该进入生产环境吗?
| 更改类型 | 进入生产环境? | 原因 |
|---|---|---|
| 移除不兼容的 Spark 配置 | 是 | Serverless 兼容性修复 |
| 更新库版本 | 是 | Serverless 兼容性修复 |
| 将 DBFS 路径替换为 UC Volumes | 是 | Serverless 兼容性修复 |
| 移除初始化脚本,添加 Environments | 是 | Serverless 兼容性修复 |
| 修复硬编码的集群设置 | 是 | Serverless 兼容性修复 |
| 将目录覆盖为测试目录 | 否 | 仅用于测试的变通方案 |
| 处理测试数据缺失时的空 DataFrame | 否 | 仅用于测试的变通方案 |
| 限制日期范围以加快测试 | 否 | 仅用于测试的变通方案 |
简单判断:如果没有此更改,生产环境在 serverless 上会失败吗?如果会 → 纳入生产分支。如果不会 → 仅保留在测试分支。
A/B 对比
两个分支准备就绪后,对比输出:
# Compare outputs between classic and serverless runs
classic_df = spark.read.table("main.output.classic_results")
serverless_df = spark.read.table("main.output.serverless_results")
assert classic_df.count() == serverless_df.count(), "Row count mismatch"
assert classic_df.schema == serverless_df.schema, "Schema mismatch"
diff = classic_df.exceptAll(serverless_df)
assert diff.count() == 0, f"Found {diff.count()} differing rows"
临时桥接配置:如果 serverless 运行失败,可以临时设置受支持的 Spark 配置(例如 spark.sql.shuffle.partitions)来弥补差异。请将这些配置标记为临时配置——工作负载稳定后将其移除。
第 4 步:验证 — 确认并监控
A/B 对比通过后:
- 合并生产分支 PR
- 将生产作业切换到 serverless 计算
- 通过系统表(
system.billing.usage)和预算策略监控成本 - 移除所有临时桥接配置
- 设置预算警报,以便了解成本情况
迁移交付物
成功完成迁移后,展示以下交付物,方便用户验证工作并检查结果:
| 交付物 | 内容 | 重要性 |
|---|---|---|
| 测试分支名称/URL | serverless-test-{job_name}-{timestamp} 分支,包含所有兼容性修复和测试变通方案 | 用户可以查看实验期间的更改,包括仅用于测试的调整 |
| 生产分支名称/URL | serverless-prod-{job_name} 分支,仅包含经过验证的兼容性修复 | 最终交付内容——用户在此处审查并合并 PR |
| 测试作业 ID 和运行 URL | 用于验证迁移的 serverless 测试作业 | 证明笔记本可在 serverless 上使用抽样数据成功运行 |
| Classic 与 serverless 对比 | A/B 结果摘要(行数、架构检查、行级差异) | 确认 serverless 输出与 classic 输出一致 |
| Serverless 环境规范 | environments JSON 代码块(客户端版本 + 固定的依赖项) | 可直接粘贴到生产作业配置中 |
| 更改摘要 | 进入生产环境与仅用于测试的更改列表(附原因) | 为 PR 审核者提供审计记录 |
如果缺少任何交付物,迁移就尚未完成——请勿将其标记为已完成。
停止条件
请勿尝试变通方案;应向用户说明并停止:
- 无权访问源表、测试目录或工作区
- 第 3 类阻碍因素(R 代码、自定义 Spark 数据源 JAR、需要 classic 计算的功能)
- SQL warehouse 或测试目录不可用
- 在错误跟踪中没有新信息的情况下反复失败(通常为 5 次以上)——请改为生成失败报告(参见故障报告协议)
快速修复参考
将 DBFS 路径替换为 UC Volumes
# BEFORE (classic)
df = spark.read.csv("dbfs:/mnt/datalake/sales/data.csv", header=True)
df.write.parquet("dbfs:/mnt/output/results")
# AFTER (serverless)
df = spark.read.csv("/Volumes/main/sales/raw_data/data.csv", header=True)
df.write.parquet("/Volumes/main/analytics/output/results")
# Replace mounts with external volumes (SQL):
# CREATE EXTERNAL VOLUME main.data.raw_files LOCATION 's3://my-bucket/data/';
# Then use: /Volumes/main/data/raw_files/
# Pandas paths too:
# BEFORE: pd.read_csv("/dbfs/mnt/data/file.csv")
# AFTER: pd.read_csv("/Volumes/main/data/volume/file.csv")
将 RDD 操作替换为 DataFrames
from pyspark.sql import functions as F
# parallelize + map
# BEFORE:
rdd = sc.parallelize([1, 2, 3])
result = rdd.map(lambda x: x * 2).collect()
# AFTER:
df = spark.createDataFrame([(1,), (2,), (3,)], ["value"])
result = df.select((F.col("value") * 2).alias("value")).collect()
# flatMap (word splitting)
# BEFORE:
words = sc.parallelize(["hello world"]).flatMap(lambda l: l.split(" ")).collect()
# AFTER:
df = spark.createDataFrame([("hello world",)], ["line"])
words = df.select(F.explode(F.split("line", " ")).alias("word")).collect()
# groupByKey
# BEFORE:
rdd = sc.parallelize([("a", 1), ("b", 2), ("a", 3)])
grouped = rdd.groupByKey().mapValues(list).collect()
# AFTER:
df = spark.createDataFrame([("a", 1), ("b", 2), ("a", 3)], ["key", "value"])
grouped = df.groupBy("key").agg(F.collect_list("value").alias("values")).collect()
# mapPartitions → applyInPandas
# BEFORE:
def process_partition(iterator):
yield sum(iterator)
result = sc.parallelize(range(100), 4).mapPartitions(process_partition).collect()
# AFTER:
import pandas as pd
def process_group(pdf: pd.DataFrame) -> pd.DataFrame:
return pd.DataFrame({"total": [pdf["id"].sum()]})
result = (spark.range(100).repartition(4)
.groupBy(F.spark_partition_id())
.applyInPandas(process_group, schema="total long")
.collect())
# textFile
# BEFORE: rdd = sc.textFile("/mnt/data/file.txt")
# AFTER: df = spark.read.text("/Volumes/catalog/schema/volume/file.txt")
# wholeTextFiles
# BEFORE: rdd = sc.wholeTextFiles("/mnt/data/dir/")
# AFTER: df = spark.read.format("binaryFile").load("/Volumes/catalog/schema/volume/dir/")
修复流式处理触发器
# CRITICAL: Omitting .trigger() defaults to ProcessingTime(0) — not supported on serverless
# BEFORE (fails on serverless — no trigger = ProcessingTime default):
query = df.writeStream.format("delta").outputMode("append").start(path)
# BEFORE (fails — explicit ProcessingTime):
query = df.writeStream.trigger(processingTime="10 seconds").start(path)
# AFTER (serverless compatible):
query = (df.writeStream
.format("delta")
.outputMode("append")
.trigger(availableNow=True)
.option("checkpointLocation", "/Volumes/main/data/checkpoints/stream1")
.start("/Volumes/main/data/output/stream1"))
query.awaitTermination()
# With OOM prevention (recommended for large sources):
query = (spark.readStream.format("delta")
.option("maxFilesPerTrigger", 100) # Delta/Parquet sources
.option("maxBytesPerTrigger", "10g") # Limit data per micro-batch
.load(input_path)
.writeStream
.trigger(availableNow=True)
.option("checkpointLocation", checkpoint_path)
.start(output_path))
# Kafka: use maxOffsetsPerTrigger
query = (spark.readStream.format("kafka")
.option("kafka.bootstrap.servers", "broker:9092")
.option("subscribe", "topic1")
.option("maxOffsetsPerTrigger", 100000) # Kafka-specific
.load()
.writeStream.trigger(availableNow=True).start(output_path))
# Auto Loader: use cloudFiles.maxFilesPerTrigger (note the prefix)
query = (spark.readStream.format("cloudFiles")
.option("cloudFiles.format", "json")
.option("cloudFiles.maxFilesPerTrigger", 1000) # cloudFiles. prefix
.load(landing_path)
.writeStream.trigger(availableNow=True).start(output_path))
移除缓存
# BEFORE (classic):
df = spark.read.parquet(path)
df.cache()
df.count() # materialize cache
result1 = df.filter("status = 'active'")
result2 = df.groupBy("region").agg(F.sum("revenue"))
# AFTER (serverless — remove .cache(); native support coming soon):
df = spark.read.parquet(path)
result1 = df.filter("status = 'active'")
result2 = df.groupBy("region").agg(F.sum("revenue"))
# For truly expensive intermediate results, materialize to Delta:
expensive_df.write.format("delta").mode("overwrite").saveAsTable("main.scratch.intermediate")
result = spark.table("main.scratch.intermediate")
# SQL equivalent:
# BEFORE: CACHE TABLE my_table
# AFTER: (just remove the CACHE TABLE statement)
其他快速修复
| 模式 | 修复方法 | 完整示例 |
|---|---|---|
sc.broadcast / sc.accumulator / sqlContext.sql | 使用 SparkSession 的等效功能:broadcast() 联接、df.agg()、spark.sql() | 代码模式 |
| 初始化脚本 | 移至 Environment 面板或 requirements.txt。切勿安装 PySpark。固定版本。 | 代码模式 |
| Hive Metastore 表 | 使用 HMS Federation 作为桥接(CREATE FOREIGN CATALOG),或直接迁移(CREATE TABLE ... AS SELECT) | 代码模式 |
| 自定义 JDBC JAR | 使用 Lakehouse Federation(CREATE CONNECTION ... TYPE POSTGRESQL)或内置 JDBC(适用于无服务器计算) | 代码模式 |
| Spark UI 调试 | 使用 Query Profile:点击单元格输出下方的“查看性能”,或运行 df.explain(True) | 代码模式 |
在运行时检测无服务器计算
import os
is_serverless = os.getenv("IS_SERVERLESS", "").lower() == "true"
将作业配置从经典计算转换为无服务器计算
移除 job_clusters / new_cluster,添加包含无服务器规范的 environments,将 job_cluster_key 替换为 environment_key,并移除 init_scripts。完整的转换前后 JSON 示例和环境版本映射请参阅配置指南。
环境版本映射(匹配工作负载所使用的 DBR 版本):
| 经典 DBR | 无服务器 spec.client | Python |
|---|---|---|
| 13.x, 14.x | "1" | 3.10 |
| 15.x | "2" | 3.11 |
| 16.x+ | "3" | 3.12 |
作业定义迁移
迁移作业时,必须同时转换作业配置 JSON 和笔记本代码。Agent 应完成以下所有操作:
将初始化脚本迁移到无服务器环境:检测作业 JSON 中的 init_scripts。提取所有 pip install 命令,并将其转换为 Environment dependencies。对于有 pip 等效项的操作系统级软件包(apt install / yum install),例如将 apt install python3-opencv 转换为 opencv-python。将没有 pip 等效项的操作系统级软件包标记为与无服务器不兼容(类别 3)。
将集群库(Maven/JAR)迁移到 Environment 或卷:将用于 Python 封装 JAR 的 Maven 坐标替换为 Environment 规范中的 PyPI 等效项。将 DBFS 上的自定义 JAR 移至 /Volumes/<your_catalog>/schema/volume/,并从该位置引用。自定义 Spark 数据源 JAR(v1/v2)属于类别 3 阻碍项 — 将其标记为需保留在经典计算环境中。
将 job_clusters 迁移到无服务器计算:完全移除 job_clusters / new_cluster 块。添加包含无服务器规范的 environments 数组。将每个任务中的 job_cluster_key 替换为 environment_key。移除 init_scripts、num_workers、node_type_id、spark_version。完整的转换前后示例请参阅配置指南。
迁移 spark_conf:扫描笔记本中的所有 spark.conf.set(...) 调用,以及作业 JSON 中的 spark_conf 条目。对于每一项:
- 支持的配置(保留):
spark.sql.shuffle.partitions、spark.sql.session.timeZone、spark.sql.ansi.enabled、spark.sql.files.maxPartitionBytes、spark.sql.legacy.timeParserPolicy、spark.databricks.execution.timeout - 自动调优的配置(移除并添加注释):AQE 配置、Delta 自动紧凑、执行器 / 驱动程序规格设置、并行度配置
- 凭据配置(移除):
fs.s3a.*、fs.azure.*— 改用 UC 外部位置 - 在每项移除操作处添加代码注释,说明原因:
# Removed: auto-tuned on serverless或# Removed: use UC external locations instead
参数化目录以供测试
dbutils.widgets.text("catalog", "main") # Default to production
catalog = dbutils.widgets.get("catalog")
df = spark.table(f"{catalog}.sales.orders")
# Pass catalog="test_catalog" as a job parameter during testing
模拟表目录映射和测试作业创建模式请参阅配置指南。
调试失败的无服务器运行
不要猜测,先通过 w.jobs.get_run_output(run_id=...) 获取实际错误。常见错误:
| 错误 | 修复方法 |
|---|---|
INFINITE_STREAMING_TRIGGER_NOT_SUPPORTED | 添加 .trigger(availableNow=True) |
UNRESOLVED_COLUMN | 临时视图名称冲突 — 使用唯一名称 |
TABLE_OR_VIEW_NOT_FOUND | DBFS/HMS 表无法访问 — 迁移到 UC |
Py4JError: ... is not available | 使用了 SparkContext/RDD — 改写为 DataFrame |
| 软件包安装超时 | 固定版本;切勿将 PySpark 安装为依赖项 |
ModuleNotFoundError: No module named 'mlflow' | 将其添加到环境规范的 dependencies 中 — 无服务器环境不提供 ML 运行时 |
SparkContext.getOrCreate() is NOT supported / RuntimeError: Only remote Spark sessions | 替换为 spark.createDataFrame() 或 spark.range() |
UC_FILE_SCHEME_FOR_TABLE_CREATION_NOT_SUPPORTED | 使用托管表或 /Volumes/... 路径 |
PERMISSION_DENIED: CREATE SCHEMA on Catalog 'main' | 在 CREATE 语句前添加 spark.sql("USE CATALOG <your_catalog>") |
DATA_SOURCE_NOT_FOUND: Failed to find data source | 类别 3 阻碍 — 自定义 JAR 数据源需要经典计算 |
在 JAR 运行时出现 NoSuchMethodError: scala.Predef$.wrapRefArray / NoClassDefFoundError: scala/Serializable | Scala 版本不匹配 — JAR 是针对 2.12 编译的;无服务器环境使用 2.13.16。请针对 2.13.16 重新编译。参见 JAR Migration |
在 JAR 运行时出现 NoClassDefFoundError,指向 org/apache/spark/... | Spark 被打包进去了,而非设为 provided。将 databricks-connect % Provided 标记为 Provided(并改写任何 SparkContext/RDD 源码)。参见 JAR Migration |
迁移后出现 SyntaxError | 确保注释位于 MAGIC 块内,而不是跨越单元格分隔符 |
%run 报错 File './<name>' not found(或 %run 被当作 IPython 行魔法执行) | A1:同一单元格中的 # MAGIC %run 前有一条纯 Python 注释。将该注释移到上方独立的 # MAGIC %md 单元格中。 |
TypeError: max() got an unexpected keyword argument 'key' | A2:from pyspark.sql.functions import * 覆盖了内置的 max。使用排序加索引替代 max(..., key=)。 |
TypeError: Object of type PlanMetrics is not JSON serializable | A3:不支持 automl.classify/regress/forecast;DBDemos.create_mockup_automl_run 回退方案在 Spark Connect 上会触发此错误。请改写为内联 sklearn Pipeline。 |
NameError: cannot access free variable 'loaded_model' | A4:mlflow 2.19.0 的 pyfunc.spark_udf 在 Spark Connect 上存在闭包错误。使用驱动端 pyfunc.load_model + toPandas() + spark.createDataFrame,或固定 mlflow>=2.20.0。 |
ModuleNotFoundError: No module named 'databricks.automl_runtime' | E1:SDP 镜像缺少 databricks-automl-runtime。在 SDP .py 库文件顶部输出 %pip install -q databricks-automl-runtime。 |
HTTP 400: Failed to enforce schema of data ... Model is missing inputs ['id'] | E2:AutoML → sklearn 改写更改了模型签名;将下游的 force_update = False 改为 True。 |
在 log_model 期间,get_model_version_by_alias(..., 'Champion') 返回 RESOURCE_DOES_NOT_EXIST | M1:在 UC 下从 log_model 中移除 registered_model_name=;在运行结束后调用 mlflow.register_model(...)。 |
.latest_versions 报错 TypeError: 'NoneType' object is not iterable | M2:在 UC 上,RegisteredModel.latest_versions 始终为 None。使用 client.search_model_versions(...) 并排序加索引。 |
MlflowException: Model signature is required for registering a model to Unity Catalog | M3:UC 要求在 log_model 中提供 signature=。使用 infer_signature(X_sample, model.predict(X_sample)) 推断,并传入 log_model(..., signature=signature)。参见 MLflow on UC。 |
ai_query(endpoint => 'databricks-meta-llama-3-1-405b-instruct') 返回 404 | D1:Foundation Model 端点已弃用。通过对所有迁移文件进行内容扫描,将其替换为 databricks-meta-llama-3-3-70b-instruct。 |
PERMISSION_DENIED: User does not have CREATE CATALOG on Metastore(即使目录已存在) | B2:权限检查在 IF NOT EXISTS 提前退出前触发。使用 SHOW CATALOGS LIKE '...' 探测进行保护。递归应用,包括 _resources/ 和 config* 文件。 |
SDP 并行部署时报错 Table is already managed by pipeline <pipeline-id> | H2:为已迁移管道的目标 schema 添加后缀(例如 <orig>_skill_migrated)。 |
DELTA_FAILED_TO_MERGE_FIELDS: prediction (Double) vs prediction (Integer) | M4:AutoML → sklearn 改写会输出 float64 预测值;写入前,将二元分类器的预测值转换为 IntegerType。参见 MLflow on UC。 |
| 完整的错误参考和 SDK 代码示例请参阅 Configuration Guide。 |
选择性能模式
| 标准 | 性能优化 | 标准 |
|---|---|---|
| 启动时间 | <50 秒 | 4-6 分钟 |
| 成本 | 较高 | 显著较低 |
| 适用于 | 笔记本、作业、SDP | 仅作业和 SDP |
| 最适合 | 交互式工作、开发、时间敏感的任务 | 批处理 ETL、计划管道 |
| 默认模式 | 是(UI 和 API) | 必须明确选择 |
笔记本不支持标准模式。 笔记本始终使用性能优化模式。
需要了解的无服务器默认设置
| 设置 | 值 |
|---|---|
| REPL VM 内存 | 默认 8GB(提供高内存选项) |
| 最大执行器数 | 32(Premium)、64(Enterprise)——可通过支持团队提高 |
| 支持的 Spark 配置 | 仅 6 项(见上文的 Category D) |
| 调试 | Query Profile(无 Spark UI) |
| ANSI SQL | 默认启用(可配置) |
失败报告协议
迁移失败或遇到无法迁移的模式时,请生成结构化失败报告,并为用户提供一步提交 GitHub issue 的途径。这个反馈闭环是该技能不断改进的方式——没有它,问题缺口就不会被发现。
完整的脱敏检查清单、URL 编码方法和预填链接示例,请参阅 Failure Reporting。
决策树:何时建议提交 issue
只要工作负载无法按原样完全迁移到无服务器环境,就应建议提交 GitHub issue。 来自“已知”模式(R、Scala、自定义 JAR 数据源、JVM 访问、第三方连接器)的报告与来自未知模式的报告同样有价值——它们能告诉维护者用户最常遇到哪些问题,从而帮助确定优先级。
具体而言,满足以下任意一项时,务必提出建议:
- 工作负载包含任何 Category 3(仅经典计算)阻碍项——R、Scala 笔记本单元格、自定义 JAR 数据源、JVM/Py4J 访问、没有无服务器等效项的第三方连接器、本机二进制依赖等。某种模式“已记录为 Cat 3”并不是跳过建议的理由。
- 重试耗尽——
retry_count >= max_retries(通常为 5),且最终状态为 FAILED - 未知模式——检测到技能目录中未收录的经典计算构造
- 修复未解决问题——应用了已知修复,但工作负载在无服务器环境中仍然失败
- 用户明确要求——用户调用
/migration-report
仅在以下情况不要建议提交 issue:
- 迁移已完全成功(即使经过重试),或
- 工作负载已兼容无服务器环境,且无需任何更改。
如何生成报告
将 JSON 文件写入 ~/.databricks-migration-skill/reports/failure-<ISO-timestamp>.json。如果目录不存在,请创建该目录。
架构(严格遵循——不得包含自由文本代码或标识符):
{
"report_version": "1.1",
"report_id": "<uuid-v4>",
"skill_version": "<from SKILL.md frontmatter metadata.version>",
"timestamp": "<ISO 8601 UTC>",
"failure_phase": "analyze | migrate | test | validate",
"detected_patterns": [
{"category": "A", "pattern_id": "rdd_parallelize", "count": 3}
],
"attempted_fixes": [
{"pattern_id": "rdd_parallelize", "fix_applied": "<fix_id>", "attempt_number": 1, "outcome": "failed"}
],
"final_error_category": "unknown_api | missing_library | data_access | permission | custom_data_source | jvm_access | unsupported_language | other",
"final_error_signature": "<SHA256 of top 3 stack frames, NOT the frames themselves>",
"retry_count": 5,
"total_duration_seconds": 245,
"notebook_characteristics": {
"lines_of_code": 180,
"language": "python | sql | scala | r",
"uses_streaming": false,
"uses_ml_libraries": true,
"databricks_runtime_source": "<DBR version only, no cluster identifiers>"
}
}
报告绝不能包含的内容
硬性要求——报告必须适合在 GitHub Issues 上公开分享:
- 不得包含代码内容——仅包含模式 ID(例如,
rdd_parallelize),绝不能包含代码片段、函数体,甚至单行示例 - 不得包含文件路径——不得包含笔记本名称、目录路径、工作区 URL 或 DBFS 路径
- 不得包含错误消息文本——仅包含错误类别枚举值和经过哈希处理的签名
- 不得包含标识符——不得包含表名、列名、目录名、架构名、机密范围名称、用户邮箱、工作区 ID 或帐户 ID
- 不得包含 Databricks 内部引用——不得包含 Databricks 员工姓名、内部代号(例如,未出现在公开文档中的产品代号)、
go/链接、Confluence 页面 ID、Google Doc ID、Slack 用户或频道 ID(U…、C…)、PROD-* / SEV-* / SC-* 工单编号 - 不得包含客户信息——不得包含公司名称、客户系统的产品名称,或任何能识别工作区所属组织的信息
- 不得包含凭据——不得包含令牌、API 密钥、连接字符串、JDBC URL 或服务主体 ID
- 不得包含数据描述——不得包含列值示例、与特定表关联的行数,或超出
notebook_characteristics字段范围的架构指纹
匿名化安全检查
编写报告前,请根据此模式清单检查每个字符串字段。如果任何模式匹配,请移除相关字段(不要只部分遮盖——将字段留空比冒险泄露更安全):
| 模式 | 应清除的内容 |
|---|---|
[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,} | 电子邮件地址 |
dbfs:/、/dbfs/、s3://、abfss://、gs://、wasbs:// | 云存储路径 |
https?://[a-z0-9-]+\.cloud\.databricks\.com、https?://adb-\d+\.\d+\.azuredatabricks\.net | 工作区 URL |
U[A-Z0-9]{8,}、C[A-Z0-9]{8,} | Slack 用户 / 频道 ID |
\bgo/[a-z0-9-]+\b | go/ 链接 |
| `\b(PROD | SEV |
[0-9a-f]{20,}(启发式) | 可能的文档 / 文件 / 工作区 ID |
| 被分析笔记本中的目录 / 架构 / 表名称字面值 | 移除并替换为 "<redacted>" |
notebook_characteristics 字段是承载工作负载元数据的唯一安全范围。未扩展此清单前,不要添加新字段。
确定性序列化后清除(必需)
上述脱敏在生成时执行,依赖模型应用所有规则。对于最终会生成公开 GitHub issue 的流程,这还不够。将报告序列化为 JSON 后:
- 在最终 JSON 文件中以字面文本搜索的方式重新运行 MUST-NOT-CONTAIN 正则表达式集合。匹配上表中的相同模式,以及被分析笔记本中引用过的任何目录 / 架构 / 表名称。
- 如果有任何模式匹配,则不得显示预填 URL 或
gh issue create命令。打印本地文件路径,列出匹配的模式,并告知用户在分享前手动脱敏文件。 - 只有序列化后的检查确认无匹配时,才能显示预填 URL。
此确定性检查不可妥协;即使你确信生成时已应用脱敏,也不得跳过。
生成报告后——输出模板
当上方决策树指示“提供创建报告选项”时,默认只在本地保存。只有在用户确认本地文件且序列化后检查通过(见上文)后,才显示预填的 URL 和 gh 命令。
默认响应——始终包含:
- 本地报告文件路径(
~/.databricks-migration-skill/reports/failure-<timestamp>.json,其中<timestamp>已填写)。 - 一行说明:报告仅包含模式 ID 和笔记本特征,不包含代码或标识符。
- 提示:"这是一份草稿。请打开文件,确认脱敏结果正确,然后告诉我可以分享了。你确认后,我会生成预填的 GitHub issue URL。"
只有在用户明确确认(例如 “分享吧”、“创建 issue”、“看起来没问题”)且上述确定性序列化后检查结果为通过后,才生成:
- 选项 A——完整的预填 URL:
https://github.com/databricks/databricks-agent-skills/issues/new?template=migration-feedback.md&title=<…>&body=<…>,两个参数均经过 URL 编码。 - 选项 B——原样的
gh issue create --repo databricks/databricks-agent-skills …命令,正文文件指向本地报告。
写入文件的同一轮中不要生成 URL 或 gh 命令。分两轮进行:先写入并请用户检查,然后在用户明确确认后发布。
使用以下确切的结束模板,并替换 <…> 占位符:
Migration could not complete. A failure report has been generated at:
~/.databricks-migration-skill/reports/failure-<timestamp>.json
The report contains anonymized diagnostic data (detected pattern IDs, error
category, retry count, notebook characteristics) and no code content or PII.
Submission is optional and opt-in.
To help improve this skill, file the report as a GitHub issue:
Option A — One-click in browser (pre-filled):
<PREFILLED_ISSUE_URL>
Option B — From the terminal (if you have the GitHub CLI installed):
gh issue create \
--repo databricks/databricks-agent-skills \
--title "<TITLE>" \
--body-file ~/.databricks-migration-skill/reports/failure-<timestamp>.json \
--label migration-skill
Before submitting, please open the JSON and confirm nothing sensitive
slipped through. We never transmit reports automatically.
按以下方式构建 <PREFILLED_ISSUE_URL>:
- 标题:
[migration-skill] <final_error_category> in <failure_phase> phase示例:[migration-skill] custom_data_source in migrate phase - 正文:issue 模板的 Markdown 框架(Category、Environment、Description、以
```json围栏代码块包裹的 Failure report JSON),并内嵌报告 JSON。 - 对标题和正文进行 URL 编码(空格用
%20,#用%23,换行用%0A等)。 - 最终 URL:
https://github.com/databricks/databricks-agent-skills/issues/new?template=migration-feedback.md&title=<URL-encoded title>&body=<URL-encoded body>
如果运行环境无法实际写入文件(受沙盒限制或没有文件系统写入权限),仍然显示文件应保存的路径,并生成选项 A 和 B。用户可以自行将 JSON 写入磁盘。
完整流程及示例见 Failure Reporting。
绝不要自动传输报告。 用户拥有自己的数据,分享前必须先进行检查。如果用户拒绝,不要催促——记录本地报告路径后继续。
成功报告
迁移成功时,为每个工作负载生成一份报告,结构与失败报告相同(不包括发布前的脱敏步骤),并列出检测到的模式及针对各工作负载应用的修复。写入 ~/.databricks-migration-skill/reports/success-<timestamp>.json,并告知用户文件路径。
多工作负载迁移
涉及多个相互独立的工作负载时,并行迁移(例如每个工作负载使用一个子 Agent)。在单个工作负载内,按 Step 1.5 中的方式顺序执行每个笔记本的步骤。
参考指南
如需了解超出上述快速修复范围的详细变通方案和代码示例,请参阅:
- 兼容性检查 — 包含全部 40 多项检查的完整模式检测表
- 流式迁移 — 触发器迁移、SDP 连续模式、连续作业
- 网络与安全 — 从 VPC 对等互连迁移到 NCC、Private Link、防火墙设置
- 代码模式 — 每种迁移模式的完整迁移前后代码示例
- 配置指南 — 支持的 Spark 配置、Environments 设置、预算策略
- UC 上的 MLflow — 将 AutoML 改写为 sklearn、支持 UC 的注册、
latest_versions替换、spark_udf闭包错误修复、预测列 dtype 对齐 - 多源枚举 — 在逐个分析笔记本之前解析
bundle_config.py,并获取上游 git 源 - 故障报告 — 脱敏检查清单 + 预填 GitHub 问题的 URL 生成方案(适用于无法完成迁移的情况)
- 在 Databricks Genie Code 中安装 — 在 Databricks 工作区中运行此技能
- JAR 迁移 — 迁移已编译的 Scala JAR(spark_jar_task):Scala 2.13.16、Databricks Connect、与无服务器内核类路径的依赖冲突、sbt 修复、构建/测试/部署
文档
- 无服务器计算概述:https://docs.databricks.com/en/compute/serverless/
- 迁移指南:https://docs.databricks.com/en/compute/serverless/migration
- 限制:https://docs.databricks.com/en/compute/serverless/limitations
- 最佳实践:https://docs.databricks.com/en/compute/serverless/best-practices
- 无服务器笔记本:https://docs.databricks.com/en/compute/serverless/notebooks
- 无服务器作业:https://docs.databricks.com/en/jobs/run-serverless-jobs
- 无服务器 SDP:https://docs.databricks.com/en/ldp/serverless
- Spark Connect 与经典模式对比:https://docs.databricks.com/en/spark/connect-vs-classic
- Unity Catalog 升级:https://docs.databricks.com/en/data-governance/unity-catalog/upgrade/
- 支持的 Spark 配置:https://docs.databricks.com/en/spark/conf#serverless