DataFrame partitionBy到单个Parquet文件(每个分区)

时间:2016-01-14 12:26:45

标签: apache-spark apache-spark-sql

我想重新分区/合并我的数据,以便将其保存到每个分区的一个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有更好的方法吗?

2 个答案:

答案 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个分区,这会使驱动程序超载,因为它会将所有数据拉入驱动程序的1个分区(这也不是一个好习惯)

  2. coalesce实际上会对网络上的所有数据进行洗牌,这也可能会导致性能下降。

  3.   

    随机播放是Spark的重新分发数据的机制,因此它可以跨分区进行不同的分组。这通常涉及跨执行程序和机器复制数据,使洗牌成为一项复杂而昂贵的操作。

    shuffle 概念对于管理和理解非常重要。由于它涉及磁盘I / O,数据序列化和网络I / O,因此它是一项昂贵的操作,因此总是优先考虑将最小可能性进行洗牌。为了组织shuffle的数据,Spark生成了一系列任务 - 映射任务以组织数据,以及一组reduce任务来聚合它。这个命名法来自MapReduce,并不直接与Spark的地图和减少操作有关。

    在内部,各个地图任务的结果会保留在内存中,直到它们无法适应。然后,这些基于目标分区进行排序并写入单个文件。在reduce方面,任务读取相关的排序块。

    关于分区镶木地板,我建议你阅读有关Parquet Partitioning的Spark DataFrames的答案here,以及性能调整的Spark编程指南中的section。< / p>

    我希望这有帮助!