使用少量 I/O 的慢速 Spark 阶段

如果你有一个没有多少 I/O 的慢阶段,原因可能是:

  • 读取大量小文件
  • 写入大量小文件
  • 缓慢的 UDF
  • 笛卡尔联接
  • 分解联接

可以使用 SQL DAG 识别几乎所有这些问题。

打开 SQL DAG

若要打开 SQL DAG,请向上滚动到作业页面顶部,然后单击关联的 SQL 查询

SQL ID

现在,你应该会看到 DAG。 如果没有,请滚动一下,应该就会看到它:

SLQ DAG

在继续操作之前,你需要熟悉 DAG 以及花费时间的位置。 DAG 中的某些节点具有有用的时间信息,而其他节点则没有。 例如,此块花费了 2.1 分钟,甚至提供阶段 ID:

阶段节点缓慢

此节点需要打开它才能看到它花费了 1.4 分钟:

写入节点缓慢

这些时间是累积的,因此它是所有任务花费的总时间,而不是时钟时间。 但它仍然非常有用,因为它们与时钟时间和费用关联。

熟悉 DAG 中花费时间的位置很有帮助。

读取大量小文件

如果看到其中一个扫描运算符花费了很多时间,请打开它并查找读取的文件数:

读取多个文件

如果要读取数万个文件或更多文件,则可能有一个小文件问题。 文件不应小于 8MB。 小文件问题通常是通过对过多列或高基数列进行分区引起的。

如果幸运,可能只需要运行 OPTIMIZE。 不管怎样,都需要重新考虑文件布局

写入大量小文件

如果看到写入需要很长时间,请打开它并查找文件数以及写入的数据量:

写入多个文件

如果要编写数万个文件或更多文件,则可能有一个小文件问题。 文件不应小于 8MB。 小文件问题通常是通过对过多列或高基数列进行分区引起的。 需要重新考虑 文件布局 或打开 优化的写入

UDF 缓慢

如果你知道你有 UDF,或者在 DAG 中看到类似这样的内容,则可能拥有缓慢的 UDF:

UDF 节点

如果你认为你遇到此问题,请尝试注释禁止 UDF 以查看它如何影响管道速度。 如果 UDF 确实是花费时间的位置,则最佳选择是使用本机函数重写 UDF。 如果这不可行,请考虑执行 UDF 的阶段中的任务数。 如果它小于群集上的内核数,则在使用 UDF 之前repartition()数据帧:

  (df
    .repartition(num_cores)
    .withColumn('new_col', udf(...))
  )

UDF 也可能因内存问题而受到影响。 请考虑每个任务可能需要将分区中的所有数据加载到内存中。 如果此数据太大,则情况可能会变得非常缓慢或不稳定。 重新分区还可以通过缩小每个任务来解决此问题。

笛卡尔联接

如果在 DAG 中看到笛卡尔联接或嵌套循环联接,则应知道这些联接非常昂贵。 确保这是你想要的,另外请了解是否有其他办法。

爆炸联接或分解

如果进入节点的行数很少,而流出的行数却很多,则可能是连接或 explode() 出现了问题:

爆炸联接

详细了解 Databricks 优化指南中有关爆炸的信息。