我想重新分区/合并我的数据,以便将其保存到每个分区的一个Parquet文件中。我还想使用Spark SQL partitionBy API。所以我可以这样做:
df.coalesce(1).write.partitionBy("entity", "year", "month", "day", "status")
.mode(SaveMode.Append).parquet(s"$location")
我测试了这个并且似乎表现不佳。这是因为在数据集中只有一个分区可以处理,文件的所有分区,压缩和保存都必须由一个CPU核完成。
在调用coalesce之前,我可以重写这个来手动执行分区(使用带有不同分区值的过滤器)。
但使用标准的Spark SQL API有更好的方法吗?
答案 0 :(得分:67)
我遇到了完全相同的问题,我找到了使用DataFrame.repartition()
执行此操作的方法。使用coalesce(1)
的问题在于您的并行性降至1,并且它最多可能很慢并且最坏时出错。增加这个数字也无济于事 - 如果你coalesce(10)
获得更多的并行性,但最终每个分区有10个文件。
要在不使用coalesce()
的情况下为每个分区获取一个文件,请使用repartition()
与您希望对输出进行分区的相同列。所以在你的情况下,这样做:
import spark.implicits._
df.repartition($"entity", $"year", $"month", $"day", $"status").write.partitionBy("entity", "year", "month", "day", "status").mode(SaveMode.Append).parquet(s"$location")
一旦我这样做,我会为每个输出分区获取一个镶木地板文件,而不是多个文件。
我在Python中对此进行了测试,但我认为在Scala中它应该是相同的。
答案 1 :(得分:8)
根据定义:
coalesce(numPartitions:Int):DataFrame 返回一个具有正确numPartitions分区的新DataFrame。
您可以使用它来使用numPartitions参数减少RDD / DataFrame中的分区数。在过滤掉大型数据集后,它可以更有效地运行操作。
关于您的代码,它并没有很好的表现,因为您实际在做的是:
将所有内容放入1个分区,这会使驱动程序超载,因为它会将所有数据拉入驱动程序的1个分区(这也不是一个好习惯)
coalesce
实际上会对网络上的所有数据进行洗牌,这也可能会导致性能下降。
随机播放是Spark的重新分发数据的机制,因此它可以跨分区进行不同的分组。这通常涉及跨执行程序和机器复制数据,使洗牌成为一项复杂而昂贵的操作。
shuffle 概念对于管理和理解非常重要。由于它涉及磁盘I / O,数据序列化和网络I / O,因此它是一项昂贵的操作,因此总是优先考虑将最小可能性进行洗牌。为了组织shuffle的数据,Spark生成了一系列任务 - 映射任务以组织数据,以及一组reduce任务来聚合它。这个命名法来自MapReduce,并不直接与Spark的地图和减少操作有关。
在内部,各个地图任务的结果会保留在内存中,直到它们无法适应。然后,这些基于目标分区进行排序并写入单个文件。在reduce方面,任务读取相关的排序块。
关于分区镶木地板,我建议你阅读有关Parquet Partitioning的Spark DataFrames的答案here,以及性能调整的Spark编程指南中的section。< / p>
我希望这有帮助!