Apache spark 在Apache Spark';s`bucketBy`,如何为每个bucket生成1个文件,而不是每个分区为每个bucket生成1个文件?

Apache spark 在Apache Spark';s`bucketBy`,如何为每个bucket生成1个文件,而不是每个分区为每个bucket生成1个文件?,apache-spark,amazon-s3,hive,parquet,bucket,Apache Spark,Amazon S3,Hive,Parquet,Bucket,我试图在一个相当大的数据集上使用Spark的bucketBy功能 dataframe.write() .格式(“拼花地板”) .bucketBy(500,bucketColumn1,bucketColumn2) .mode(SaveMode.Overwrite) .option(“路径”,“s3://我的桶”) .saveAsTable(“我的桌子”); 问题是我的Spark集群大约有500个分区/任务/执行器(不确定术语),因此我最终得到的文件如下所示: part-00001-{UUID}_

我试图在一个相当大的数据集上使用Spark的bucketBy功能

dataframe.write()
.格式(“拼花地板”)
.bucketBy(500,bucketColumn1,bucketColumn2)
.mode(SaveMode.Overwrite)
.option(“路径”,“s3://我的桶”)
.saveAsTable(“我的桌子”);
问题是我的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中那样,为每个bucket生成一个文件?还是有更好的方法来解决这个问题?到目前为止,我似乎必须在降低集群的并行性(减少写入程序的数量)或降低拼花文件的并行性(减少桶的数量)之间做出选择

谢谢

这应该能解决问题

dataframe.write()
.format("parquet")
.bucketBy(1, bucketColumn1, bucketColumn2)
.mode(SaveMode.Overwrite)
.option("path", "s3://my-bucket")
.saveAsTable("my_table");
将BucketBy函数的输入参数修改为1。 您可以从spark的git存储库中查看bucketBy的代码-

第一个拆分部分00001和00002基于保存带扣表格时运行的并行任务数。在您的案例中,有500个并行任务正在运行。每个零件文件中的文件数取决于为bucketBy函数提供的输入


要了解有关Spark任务、分区、执行器的更多信息,请查看我的中级文章-

,以便为每个最终存储桶获取一个文件,请执行以下操作。在将dataframe写入表之前,请使用与bucketBy中使用的列完全相同的列重新分区,并将新分区的数量设置为等于bucketBy中使用的Bucket数量(或更小的数字,即Bucket数量的除数,尽管我不认为有理由在此处使用更小的数字)

在您的情况下,可能是这样的:

dataframe.repartition(500, bucketColumn1, bucketColumn2)
    .write()
    .format("parquet")
    .bucketBy(500, bucketColumn1, bucketColumn2)
    .mode(SaveMode.Overwrite)
    .option("path", "s3://my-bucket")
    .saveAsTable("my_table");
在保存到现有表时,需要确保列的类型完全匹配(例如,如果您的列X在dataframe中为INT,但您要插入到表中的表中的BIGINT,则按X重新划分到500个存储桶中与按X重新划分的存储桶不匹配,并被视为BIGINT,最终500个执行器中的每一个都会再次写入500个文件)

要100%清楚-此重新分区将为您的执行添加另一个步骤,即在1个执行器上收集每个存储桶的数据(因此,如果数据以前没有以相同的方式进行分区,则进行一次完整的数据重新排列)。我假设这正是您想要的

在对另一个答案的评论中也提到,如果bucketing键倾斜,您需要为可能出现的问题做好准备。这是真的,但如果加载表后的第一件事是聚合/连接您所绑定的相同列,则默认的Spark行为对您没有多大帮助(对于那些选择按这些列进行bucket的人来说,这似乎是一种非常可能的情况)。相反,您将遇到延迟问题,并且只有在写入后尝试加载数据时才会看到偏差


在我看来,如果Spark提供一个设置,在编写带扣的表之前(特别是插入到现有表中时)总是对数据进行重新分区,那就太好了.

这种类型会使bucketing变得毫无意义。我建议您参考我的同事编写的关于bucketing的详细解释-。它将在每个分区生成更少的文件(每个分区最多一个bucket文件)。如果每个bucket需要一个文件,则需要按bucket键进行无序排列,但这可能会导致严重的倾斜,因为give me坚持,但这个公认的答案似乎有误导性。bucketing的整个思想是为无无序排列的查询准备数据,假设数据是根据一些连接键或聚合键进行无序排列的使用单个bucket与不使用任何bucket写入没有什么不同。这不仅可能会在以后使用数据时产生问题,而且也不能回答问题。问题是,当使用E执行器并使用B bucket将数据保存在带bucket的表中时,会产生E*B文件(即,每个执行器将其数据拆分为B个存储桶,并使用文件系统写入B个文件,以此作为重新划分数据的一种方式)。当使用此处建议的一个存储桶时,Spark将使每个执行者编写一个文件,因此文件总数将是每1个存储桶中的E-still E files,而不是每一个存储桶中的1个文件。这应该被接受为正确答案。不幸的是,Spark文档使这一点难以理解,因为它似乎是一个常见的问题n要重新存储表的用例。