Spark AQE 调优指南:分区合并、Join 切换与倾斜处理

用 AQE 的分区合并、Join 策略切换与倾斜处理能力优化 Spark 3 作业,给出参数组合与落地顺序

袋鼠云
袋鼠云
数据治理

Spark 3 引入的自适应查询执行(AQE,Adaptive Query Execution)把"根据运行时统计重新规划执行计划"变成了内置能力。对大多数作业,开启 AQE 本身就是收益最大的调优动作;参数微调的价值在于按作业画像做定向优化。本文围绕 AQE 三大能力——分区合并、Join 策略切换、数据倾斜处理——给出参数组合与落地顺序。

先开启 AQE:一行配置拿到大部分收益

Spark 3.2 起 spark.sql.adaptive.enabled 默认就是 true,但很多存量集群仍沿用 Spark 2.x 的配置或显式关闭了它。第一步是确认开启,并让作业先跑出基线:

# spark-defaults.conf
spark.sql.adaptive.enabled=true

AQE 依赖 shuffle 阶段的运行时统计:只有经过 shuffle 的作业才能触发动态优化,纯 map 作业(如只做过滤的读取)不受 AQE 影响。开启后不要急着调参数,先记录基线指标——shuffle 读写量、task 数量、运行时长、GC 时间,作为后续微调的对照。

能力一:分区合并,解决"分区过多、小文件泛滥"

AQE 会在 shuffle 结束后统计每个分区的实际大小,把过小的分区合并,使输出分区接近目标大小。核心参数:

spark.sql.adaptive.coalescePartitions.enabled=true
spark.sql.adaptive.advisoryPartitionSizeInBytes=64m
spark.sql.adaptive.coalescePartitions.minPartitionNum=16
  • advisoryPartitionSizeInBytes 默认 64 MiB,是合并的目标分区大小。输出大量小文件时调大它(如 128m/256m),让分区更少更大;反之分区太少导致并行度不足时调小;
  • coalescePartitions.minPartitionNum 限制合并后分区数的下限,避免大作业被合并成过少的分区;
  • Spark 3.4+ 还支持 coalescePartitions.parallelismFirst 与 coalescePartitions.minPartitionSize,在"优先保证并行度"和"优先合并"之间做权衡,需要明确行为时显式设置。

注意:分区合并只发生在 shuffle 输出阶段,写文件阶段的小文件要靠写入端合并(如调整分区数配合分区写入、或动态分区裁剪)共同治理,AQE 不能单独解决所有小文件问题。

能力二:Join 策略切换,让优化器"看到"真实大小

静态优化器在生成计划时只能依赖统计信息,统计缺失时会把小表误判为大表,导致该走 Broadcast Hash Join(BHJ)的 Join 走了 Sort Merge Join(SMJ),多出一次完整 shuffle。AQE 利用运行时统计,在满足条件时把 SMJ 动态切换为 BHJ:

spark.sql.adaptive.localShuffleReader.enabled=true
spark.sql.adaptive.autoBroadcastJoinThreshold=10m
spark.sql.adaptive.nonEmptyPartitionRatioForBroadcastJoin=0.2
  • autoBroadcastJoinThreshold 默认 10 MiB,是 AQE 判断"小表"的阈值。当小表实际大小明显小于阈值却没走 BHJ,或你确认大表侧存在大量空分区时,可结合 nonEmptyPartitionRatioForBroadcastJoin(默认 0.2)调整判定;
  • localShuffleReader.enabled(默认 true)让 AQE 在 Join 前只读取需要的分区,是减少 shuffle 读取量的基础开关,一般保持开启;
  • maxShuffledHashJoinLocalMapThreshold 控制 SMJ 能否降级为本地 Shuffled Hash Join,默认 0(关闭),数据分布适合时可按需开启。

这个能力的价值在于无需手写 hint:调优者不用在每条 SQL 里写 /*+ BROADCAST(t) */,AQE 根据实际大小自动决策,规则统一、不易遗漏。

能力三:倾斜处理,把"一长一短"拉平

数据倾斜的典型表现是 Join 时某个 key 的数据量远大于其他 key,导致少数 task 长时间运行、拖垮整体。AQE 的倾斜 Join 会把倾斜的大分区拆成多个小分区,并对小表侧做广播复制,让拆分后的分区并行处理:

spark.sql.adaptive.skewJoin.enabled=true
spark.sql.adaptive.skewJoin.skewedPartitionFactor=5
spark.sql.adaptive.skewJoin.skewedPartitionThresholdInBytes=256m
  • 判定规则:某个分区的大小超过 skewedPartitionThresholdInBytes(默认 256 MiB),且超过中位分区大小的 skewedPartitionFactor(默认 5)倍,才被认定为倾斜分区;
  • 拆分的分区数由阈值与总大小推算,可通过观察日志或 Spark UI 确认拆分是否生效;
  • Spark 3.2+ 的 optimizeSkewsInRebalancePartitions.enabled(默认 true)对 rebalance 场景的倾斜也做类似优化。

边界要认清:AQE 倾斜 Join 只处理 shuffle 之后、Join 键上的倾斜;源端倾斜(如读文件时某个分区文件特别大)、group by 聚合倾斜、以及非 Join 场景的倾斜,AQE 帮不上忙,仍需要手动加盐、拆 key 或调整分区。日志中若看到 Detected skewed data 相关记录,说明 AQE 已介入,可据此判断参数是否命中。

当倾斜来自 AQE 覆盖不到的环节时,常用手段包括:对倾斜键加随机后缀打散后再聚合(加盐)、把高频 key 单独拆出来单独处理、用两阶段聚合(先局部聚合再全局聚合)降低单 task 压力。这些手段与 AQE 并不冲突——AQE 处理的是 shuffle 之后可自动识别的 Join 倾斜,手动手段处理的是它识别不到的倾斜来源,两者配合才能覆盖完整的问题面。

观察 AQE 是否真的生效

调参前先学会读证据,否则参数有没有起作用全靠猜:

  • 物理计划:Spark UI 的 SQL 页面展示最终物理计划,若看到 Exchange(shuffle)节点后的分区数被合并、或 Join 节点显示为 BroadcastHashJoin,说明 AQE 已改写计划;对比 EXPLAIN 输出的静态计划与执行计划,差异就是 AQE 的贡献;
  • 日志提示:日志中出现 Detected skewed data 类记录说明倾斜拆分被触发,出现分区合并相关提示说明 coalesce 生效;
  • 作业级覆盖:AQE 参数多为作业级可覆盖(spark.sql.*),注意任务配置里若显式设置了旧值,会覆盖平台默认值,导致"开了等于没开";排查时先确认最终生效值。

与动态分区裁剪配合,而不是二选一

AQE 常与动态分区裁剪(DPP,spark.sql.optimizer.dynamicPartitionPruning.enabled,默认 true)一起工作:DPP 在维度表过滤条件下裁剪事实表要读的分区,减少的是读取量;AQE 优化的是 shuffle 与 Join 阶段。两者互补,不要因为开了 AQE 就关掉 DPP。

同时要承认 AQE 的边界:源端倾斜、聚合倾斜、写文件阶段的小文件,仍需要作业级手段(加盐、拆 key、合并写)处理。另外注意版本差异——3.2 引入多数默认开启的能力,3.4 新增 parallelismFirst 与 minPartitionSize,升级 Spark 大版本后参数默认值可能变化,套用旧集群配置前先查对应版本文档。

参数实验的纪律

  • 用同一份数据、同一资源规格做对比,避免把资源变化误判为参数收益;
  • 每次只改一个参数并记录组合,防止交互效应无法归因;
  • 把有效组合固化在作业或平台配置模板里,而不是全局修改 spark-defaults.conf 影响所有作业。

落地顺序:先开 AQE,再按作业画像微调

不要一上来就把所有参数调一遍,推荐顺序:

  1. 开启 AQE,跑基线:默认参数下记录作业耗时与资源指标;
  2. 按画像定向调整:小文件多、输出分区碎 → 调 advisoryPartitionSizeInBytes;大表 Join 慢且小表确实很小 → 检查是否走了 BHJ,调 autoBroadcastJoinThreshold;日志或监控显示长尾 task → 确认倾斜分区后调 skewedPartitionThresholdInBytes 与 skewedPartitionFactor;
  3. 验证与回滚:每次只改一个参数,用同一份数据对比基线;无效即回退,避免参数叠加后行为不可解释;
  4. 固化为作业配置:把有效的参数组合固化到作业或平台配置模板,纳入 CI 冒烟。

一个常见失败模式是"过度合并":advisoryPartitionSizeInBytes 调得过大,导致并行度不足、单 task 内存压力上升,反而更慢。参数调优始终是"作业画像 + 实验"的结果,不是固定值。

下一步:从你耗时最长的 Top 10 作业里选 3 个,先开 AQE 跑基线,再分别对"小文件型、大 Join 型、倾斜型"三类问题做参数实验并记录对比。若你在统一的数据开发平台上提交作业,像 BatchWorks 这类一站式离线数据开发平台会把 Spark 作业的参数配置、调度与运维统一收口,把验证过的参数组合固化为平台侧的默认配置模板,能让团队整体受益,避免每个作业各自摸索。

开启Data+AI 之旅

填写表单,获取专属解决方案

400-002-1024

工作日 9:00-18:00

关注公众号

获取最新产品动态与技术干货

关注公众号

企业微信

扫码添加企业微信