在Apache Spark的“ bucketBy”中,如何为每个存储区生成1个文件,而不是为每个分区每个存储区生成1个文件?

时间:2019-07-04 07:14:23

标签: apache-spark amazon-s3 hive parquet bucket

我正在尝试在非常大的数据集上使用Spark的 bucketBy 功能。

dataframe.write()
    .format("parquet")
    .bucketBy(500, bucketColumn1, bucketColumn2)
    .mode(SaveMode.Overwrite)
    .option("path", "s3://my-bucket")
    .saveAsTable("my_table");

问题是我的Spark集群有大约500个分区/任务/执行程序(不确定术语),所以我最终得到的文件如下:

part-00001-{UUID}_00001.c000.snappy.parquet
part-00001-{UUID}_00002.c000.snappy.parquet
...
part-00001-{UUID}_00500.c000.snappy.parquet

part-00002-{UUID}_00001.c000.snappy.parquet
part-00002-{UUID}_00002.c000.snappy.parquet
...
part-00002-{UUID}_00500.c000.snappy.parquet

part-00500-{UUID}_00001.c000.snappy.parquet
part-00500-{UUID}_00002.c000.snappy.parquet
...
part-00500-{UUID}_00500.c000.snappy.parquet

这是500x500 = 250000个拼花实木复合地板文件! FileOutputCommitter永远需要将其提交到S3。

是否可以像Hive中那样在每个存储桶中生成一个文件?还是有更好的方法来解决这个问题?到目前为止,似乎我不得不在降低群集的并行性(减少作者的数量)或降低木地板文件的并行性(减少存储桶的数量)之间进行选择。

谢谢

2 个答案:

答案 0 :(得分:1)

为了每个最终存储桶获取1个文件,请执行以下操作。就在将数据帧作为表分区写入之前,它使用与用于存储分区的列完全相同的列,并将新分区的数量设置为等于您将在bucketBy中使用的存储分区的数量(或较小的数字,它是number的除数)桶,尽管我看不出在这里使用较小桶的原因。

在您的情况下,可能看起来像这样:

dataframe.repartition(500, bucketColumn1, bucketColumn2)
    .write()
    .format("parquet")
    .bucketBy(500, bucketColumn1, bucketColumn2)
    .mode(SaveMode.Overwrite)
    .option("path", "s3://my-bucket")
    .saveAsTable("my_table");

在保存到现有表的情况下,需要确保列的类型完全匹配(例如,如果列X在数据帧中为INT,但在表中的BIGINT则插入到重新分区中) X划分为500个存储桶将与X划分为BIGINT的X进行重新分区不匹配,最终您将有500个执行者再次写入500个文件。

只需100%清除-这种重新分区将在您的执行中增加另一步,即在1个执行程序上收集每个存储桶的数据(因此,如果以前未按相同的方式对数据进行分区,则将重新进行一次完整的数据重排)。我以为那正是您想要的。

在另一个答案的注释中也提到过,如果存储桶密钥倾斜,则需要为可能的问题做好准备。的确是这样,但是如果加载表之后的第一件事是在存储分区的同一列上进行聚合/联接,那么默认的Spark行为并不能为您提供很多帮助(对于选择按这些列进行存储)。取而代之的是,您将得到一个延迟的问题,并且仅在写入后尝试加载数据时才看到偏斜。

我认为,如果Spark提供一个设置来始终在写存储桶的表之前(尤其是在插入现有表时)对数据进行重新分区,那将是非常好的。

答案 1 :(得分:0)

这应该解决它。

dataframe.write()
.format("parquet")
.bucketBy(1, bucketColumn1, bucketColumn2)
.mode(SaveMode.Overwrite)
.option("path", "s3://my-bucket")
.saveAsTable("my_table");

将BucketBy函数的输入参数修改为1。 您可以从spark的git存储库-https://github.com/apache/spark/blob/f8d59572b014e5254b0c574b26e101c2e4157bdd/sql/core/src/main/scala/org/apache/spark/sql/DataFrameWriter.scala

中查看bucketBy的代码

第一个拆分的部分00001,即部分00002基于保存存储桶表时运行的并行任务数。在您的情况下,您有500个并行任务正在运行。每个零件文件中的文件数取决于您为bucketBy函数提供的输入。

要了解有关Spark任务,分区,执行器的更多信息,请查看我的中型文章-https://medium.com/@tharun026