3. 工作区与集群配置优化

在 Databricks 上,作业跑得慢、成本高,很多时候并不是代码写得差,而是集群配置与工作区层面的选择不合适。同一段 Spark 代码,放在“全用途集群 + 固定大规格 + 按需实例 + 旧运行时 + 每次启动装库”的组合里,和放在“作业集群 + 自动伸缩 + Spot 混合 + 较新运行时 + 预装库”的组合里,执行时间和 DBU 成本可能相差数倍。本节把影响性能的六个关键旋钮拆开讲清楚:集群模式、自动伸缩、Spot 实例、运行时版本、库管理、初始化脚本,并给出可落地的判断依据。

一、集群模式:先决定“谁来跑、跑完是否保留”

Databricks 的集群按用途分为全用途集群(All-Purpose)和作业集群(Job Cluster)。全用途集群适合交互式探索、Notebook 调试、多人共享;作业集群由作业调度创建,作业结束即释放,适合生产批处理。核心判断是:交互式开发用全用途,生产调度用作业集群。作业集群的生命周期与作业绑定,不会因为忘记关集群而持续计费,且可以针对每个作业单独设定规格与运行时,避免“一个集群服务所有负载”导致的资源错配。

另一个常被忽略的是访问模式(Access Mode)。单用户模式支持 Scala、RDD 和较完整的库能力;共享模式(原标准模式)适合多用户 SQL/BI 场景,但对部分底层 API 有限制。如果作业依赖 RDD 或自定义 JAR,选共享模式可能直接报错或被迫改写代码,反而拖慢交付。

二、自动伸缩:让 Worker 数量跟随负载,而不是一直顶格

自动伸缩通过 min/max worker 区间,让集群在负载上升时扩容、空闲时缩容。它解决的是“峰值按最大配、平时浪费”的问题。但要注意两点:扩容不是瞬时的,新节点需要云厂商分配、启动、加入集群,通常有数十秒到数分钟延迟;缩容会触发 shuffle 数据重算或迁移,如果频繁抖动,反而增加开销。

实践建议:对稳定批处理,min 设为预估常驻并发所需,max 留出 1.5~2 倍余量;对波动明显的负载,可配合 AQE(自适应查询执行)让 Spark 在运行中合并分区。判断自动伸缩是否有效,看 Spark UI 的 Executor 数量曲线与任务排队时间:如果长期贴着 max 跑,说明上限不够;如果频繁在 min/max 之间来回,说明触发阈值或负载本身不稳定,应考虑固定规模或拆分作业。

三、Spot 实例:用折扣换中断风险

Spot(抢占式)实例价格通常为按需实例的 30%~70%,适合可重试、可容忍中断的批处理。Databricks 支持“Driver 按需 + Worker Spot”的混合策略,这是最常用的降本组合。风险在于云厂商可能随时回收 Spot 节点,导致 Executor 丢失、任务重跑。缓解手段包括:设置合理的 Spot 回退(Spot 不足时自动用按需补齐)、开启本地磁盘缓存减少重算、把长链路作业拆成可重试的短阶段。对 SLA 严格、不能中断的流处理或在线服务,Driver 和关键 Worker 应使用按需实例。

集群配置六要素对性能与成本的作用链 配置输入 集群模式 全用途 / 作业集群 自动伸缩 min / max worker Spot 实例 按需 + 抢占混合 运行时版本 DBR 版本 / Photon 库管理 预装 / 按需安装 初始化脚本 启动阶段执行 作用机制 生命周期与资源归属 作业结束即释放 按负载增减 Executor 扩容有延迟 折扣与中断风险 节点可能被回收 引擎与优化器能力 AQE / Photon 依赖解析与冲突 版本不一致 启动阶段串行开销 每次启动都执行 性能与成本结果 执行时间下降 更少 shuffle、更快启动、更优引擎 DBU 成本下降 作业集群 + Spot + 自动缩容 稳定性风险 Spot 中断、伸缩抖动、库冲突 可维护性 运行时统一、初始化脚本可复用 判断依据:Spark UI + 账单 + 作业日志

四、运行时版本:新版本通常更快,但要验证兼容性

Databricks Runtime(DBR)版本决定了 Spark 内核、优化器能力和内置库版本。较新的 DBR 通常包含 AQE 增强、动态分区裁剪、Photon 向量化引擎等,对 SQL 和 DataFrame 负载提升明显。Photon 在扫描、聚合、Join 等场景可带来数倍加速,但并非所有算子都支持,遇到不支持的算子会回退到 JVM 路径。升级运行时前,应在小数据集上跑通关键作业,重点检查:自定义 UDF 是否兼容、第三方库版本是否冲突、输出结果是否一致。不要为了“用最新”而跳过验证,也不要长期停留在过旧版本而错过优化红利。

五、库管理:预装优于每次安装

库的安装方式直接影响集群启动时间。每次启动都通过 pip 安装大量依赖,会让集群在“可用”之前多花几分钟,且可能因网络或版本解析失败导致启动失败。更好的做法是:把稳定依赖固化到自定义镜像或集群级库中,作业级只安装少量变动依赖;使用 requirements.txt 或 Poetry 锁定版本,避免“今天能跑、明天报错”。同时注意库的作用域:集群级库对所有 Notebook 生效,Notebook 级库只对当前会话生效,作业级库随作业生命周期存在。作用域混乱是“本地能跑、集群报错”的常见原因。

六、初始化脚本:把启动开销变成一次性成本

初始化脚本在集群启动时执行,适合做环境准备:挂载存储、设置环境变量、安装系统包、拉取配置。它的代价是串行阻塞启动,脚本越重,集群可用越慢。优化原则是:能预置的不在启动时做,能并行的不串行。例如把常用 Python 包打进镜像,而不是每次 pip install;把配置文件放到对象存储,用轻量脚本拉取;避免在初始化脚本里做大数据下载或复杂编译。对于作业集群,初始化脚本每次启动都会执行,因此它的耗时会被放大到每个作业。

判断配置是否合理的三个观察点:Spark UI 中 Executor 数量与任务排队时间是否匹配;集群启动到首个任务开始的时间是否过长;账单中 DBU 与云资源成本的比例是否与负载特征一致。

七、组合策略与示例参数

下面给出一个面向“每日批处理 ETL”的示例配置组合,参数为教学示例,实际需按数据量与 SLA 调整:

集群模式:作业集群(Job Cluster)
访问模式:单用户(需 RDD / 自定义 JAR)
Driver:按需实例,规格与 Worker 同级或略高
Worker:Spot 为主,按需回退,min=4,max=16
运行时:较新 DBR LTS 版本,开启 Photon
库管理:集群级预装核心依赖,作业级仅装变动包
初始化脚本:仅挂载存储与设置环境变量,控制在 30 秒内

这套组合的逻辑是:作业集群避免闲置计费,Spot 降低 Worker 成本,自动伸缩覆盖日间波动,较新运行时提升单任务效率,预装库和轻量初始化脚本压缩启动时间。上线后应对比优化前后的“作业总时长”和“单次运行 DBU 成本”,并观察 Spot 中断是否导致重跑次数上升。如果重跑频繁,可提高按需回退比例或缩小 max 规模;如果启动时间仍长,优先检查库安装和初始化脚本。

不要同时改动所有旋钮。建议每次只调整一到两项,保留基线数据,否则无法判断性能变化来自哪个配置。Spot 比例、自动伸缩区间和运行时升级是最值得优先验证的三项。

总结来说,工作区与集群配置优化的核心不是“把每个参数调到最大”,而是让资源规格、生命周期、成本模型和负载特征对齐。集群模式决定资源归属,自动伸缩决定资源弹性,Spot 决定成本底线,运行时决定单任务效率,库与初始化脚本决定启动开销。把这六项纳入日常作业模板和上线检查,才能让性能优化从“偶发调参”变成“可复制的工程习惯”。