如何在Spark中分割和编写DataFrame而不删除没有新数据的分区?

时间:2017-02-18 16:32:13

标签: apache-spark spark-dataframe partitioning parquet

我正在尝试使用DataFrameDataFrameWriter保存为Parquet格式的HDFS,并按三个列值进行分区,如下所示:

dataFrame.write.mode(SaveMode.Overwrite).partitionBy("eventdate", "hour", "processtime").parquet(path)

this question中所述,partitionBy将删除path处的完整现有分区层次结构,并将其替换为dataFrame中的分区。由于特定日期的新增量数据将定期出现,我想要的是仅替换dataFrame具有数据的层次结构中的那些分区,而不更改其他分区。

为此,我需要使用完整路径单独保存每个分区,如下所示:

singlePartition.write.mode(SaveMode.Overwrite).parquet(path + "/eventdate=2017-01-01/hour=0/processtime=1234567890")

但是我无法理解将数据组织到单分区DataFrame中的最佳方法,以便我可以使用它们的完整路径将它们写出来。一个想法是这样的:

dataFrame.repartition("eventdate", "hour", "processtime").foreachPartition ...

但是foreachPartitionIterator[Row]进行操作,这对于写出Parquet格式并不理想。

我还考虑使用select...distinct eventdate, hour, processtime获取分区列表,然后按每个分区过滤原始数据帧并将结果保存到完整的分区路径。但是,每个分区的独特查询加过滤器似乎效率不高,因为它会进行大量的过滤/写入操作。

我希望有一种更简洁的方法来保留dataFrame没有数据的现有分区?

感谢阅读。

Spark版本:2.1

3 个答案:

答案 0 :(得分:10)

模式选项Append有一个问题!

df.write.partitionBy("y","m","d")
.mode(SaveMode.Append)
.parquet("/data/hive/warehouse/mydbname.db/" + tableName)

我已经测试并发现这将保留现有的分区文件。但是,这次出现的问题如下:如果您运行相同的代码两次(使用相同的数据),那么它将创建新的镶木地板文件,而不是替换相同数据的现有代码(Spark 1.6)。因此,我们仍然可以使用Append解决此问题,而不是使用Overwrite。我们应该在分区级别覆盖,而不是在表级覆盖。

df.write.mode(SaveMode.Overwrite)
.parquet("/data/hive/warehouse/mydbname.db/" + tableName + "/y=" + year + "/m=" + month + "/d=" + day)

有关详细信息,请参阅以下链接:

Overwrite specific partitions in spark dataframe write method

(我在suriyanto的评论之后更新了我的回复.Tnnx。)

答案 1 :(得分:4)

我知道这很老了。由于我看不到任何解决方案,我会继续发布一个。此方法假定您在要写入的目录上有一个配置单元表。 解决此问题的一种方法是从dataFrame创建一个临时视图,该视图应添加到表中,然后使用普通的类似蜂巢的insert overwrite table ...命令:

dataFrame.createOrReplaceTempView("temp_view")
spark.sql("insert overwrite table table_name partition ('eventdate', 'hour', 'processtime')select * from temp_view")

它保留旧分区,同时(仅)写入新分区。

答案 2 :(得分:2)

这是一个古老的话题,但是我遇到了同样的问题,找到了另一个解决方案,只需使用以下方法将分区覆盖模式设置为动态:

spark.conf.set('spark.sql.sources.partitionOverwriteMode', 'dynamic')

因此,我的spark会话配置如下:

spark = SparkSession.builder.appName('AppName').getOrCreate()
spark.conf.set('spark.sql.sources.partitionOverwriteMode', 'dynamic')