保存到分区拼花文件时实现并发

时间:2018-06-26 19:37:27

标签: scala apache-spark parquet

使用dataframeparquet写入partitionBy时:

df.write.partitionBy("col1","col2","col3").parquet(path)

我希望每个要写入的分区都是由单独的任务独立完成的,并且与分配给当前Spark作业的工人数量的范围平行。

但是,在写入实木复合地板时,实际上一次只运行一个工作人员/任务。那个工人正在循环遍历每个分区并依次写出.parquet文件。为什么会这样-并且在此spark.write.parquet操作中是否有一种方法可以强制并发?

以下是不是我想看到的(应该是700%+ ..)

enter image description here

在另一篇文章中,我也尝试在前面添加repartition

Spark parquet partitioning : Large number of files

df.repartition("col1","col2","col3").write.partitionBy("col1","col2","col3").parquet(path)

不幸的是,这没有任何效果:仍然只有一个工人。

注意:我正在local的{​​{1}}模式下运行,并且看到 other spark操作运行多达8个并发工作程序,并且使用了多达750%的cpus。

1 个答案:

答案 0 :(得分:3)

简而言之,从一个任务写入多个输出文件不是并行的,但是假设您有多个任务(多个输入拆分),那么每个任务都会在工作线程上拥有自己的核心。

写出分区数据的目的不是并行化写操作。 Spark已经通过同时写出多个任务来做到这一点。目标只是优化将来的读取操作,在该操作中您只需要一个分区的已保存数据。

在Spark中写入分区的逻辑旨在将前一阶段的所有记录写到目的地时仅读取一次。我相信设计选择的一部分还在于防止出现以下情况: 分区键有很多值。

编辑:Spark 2.x方法

在Spark 2.x中,它按分区键对每个任务中的记录进行排序,然后遍历它们,一次写入一个文件句柄。我假设他们这样做是为了确保如果分区键中有很多不同的值,它们永远不会打开大量的文件句柄。

作为参考,这里是排序:

https://github.com/apache/spark/blob/master/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/FileFormatWriter.scala#L121

向下滚动一点,您会看到它调用write(iter.next())遍历每一行。

这是实际的内容(一次一个文件/分区键):

https://github.com/apache/spark/blob/master/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/FileFormatWriter.scala#L121

您可以看到它一次只打开一个文件句柄。

编辑:Spark 1.x方法

spark 1.x对给定任务的作用是遍历所有记录,只要遇到新的输出分区(此任务之前从未见过)就打开一个新的文件句柄。然后,它将立即将记录写入该文件句柄,然后转到下一个。这意味着在任何给定时间,在处理单个任务时,仅针对一个任务即可打开多达N个文件句柄,其中N是最大输出分区数。为了使它更清楚,这是一些python psuedo代码,以显示一般思想:

# To write out records in a single InputSplit/Task
handles = {}
for row in input_split:
    partition_path = determine_output_path(row, partition_keys)
    if partition_path not in handles:
        handles[partition_path] = open(partition_path, 'w')

    handles[partition_path].write(row)

上述写入记录的策略有一个警告。在spark 1.x中,设置spark.sql.sources.maxConcurrentWrites对可以按任务打开的掩码文件句柄设置了上限。达到此目标后,Spark会改为按分区键对数据进行排序,因此它可以遍历记录,一次写出一个文件。