DataFrame repartition()
和DataFrameWriter partitionBy()
方法有什么区别?
我希望两者都习惯于“基于数据帧列分区数据”?或者有什么不同吗?
答案 0 :(得分:106)
小心:我相信接受的答案不太正确!我很高兴你提出这个问题,因为这些类似命名的函数的行为在重要和意想不到的方面有所不同,这些方法在官方spark文档中没有详细记录。
接受答案的第一部分是正确的:调用df.repartition(COL, numPartitions=k)
将使用基于散列的分区程序创建具有k
分区的数据帧。 COL
此处定义分区键 - 它可以是单个列或列列表。基于散列的分区器获取每个输入行的分区键,通过k
之类的内容将其散列到partition = hash(partitionKey) % k
分区的空间中。这可以保证具有相同分区键的所有行最终位于同一分区中。但是,来自多个分区键的行也可以在同一个分区中结束(当分区键之间发生哈希冲突时)和某些分区可能为空。
总之,df.repartition(COL, numPartitions=k)
的不直观方面是那个
k
分区可能为空,而其他分区可能包含多个分区键中的行 df.write.partitionBy
的行为完全不同,以许多用户不会期望的方式。假设您希望输出文件是日期分区的,并且您的数据跨越7天。我们还假设df
有10个分区开头。当您运行df.write.partitionBy('day')
时,您应该期望多少输出文件?答案是“这取决于”。如果df
中起始分区的每个分区都包含每天的数据,则答案为70.如果df
中的每个起始分区都包含恰好一天的数据,则答案为10。
我们如何解释这种行为?运行df.write
时,df
中的每个原始分区都是独立编写的。也就是说,您的原始10个分区中的每个分区在“日期”列上单独进行了子分区,并为每个子分区写入了单独的文件。
我觉得这种行为很烦人,并希望在编写数据帧时有办法进行全局重新分区。
答案 1 :(得分:18)
如果您运行repartition(COL)
,则在计算期间更改分区 - 您将获得spark.sql.shuffle.partitions
(默认值:200)分区。如果您再调用.write
,您将获得一个包含许多文件的目录。
如果您运行.write.partitionBy(COL)
,那么结果将获得与COL中唯一值一样多的目录。这样可以加快进一步的数据读取速度(如果按分区列过滤)并节省一些存储空间(从数据文件中删除分区列)。
更新:请参阅@ conradlee的回答。他详细解释了应用不同方法后目录结构的样子,以及两种情况下结果的数量。
答案 2 :(得分:2)
repartition()
用于对内存中的数据进行分区,而partitionBy
用于对磁盘中的数据进行分区。它们通常结合使用,如this blog post中所述。
repartition()
和partitionBy
均可用于“基于数据帧列对数据进行分区”,但是repartition()
对内存中的数据进行分区,而partitionBy
对磁盘上的数据进行分区
repartition()
让我们尝试一些代码以更好地了解分区。假设您具有以下CSV数据。
first_name,last_name,country
Ernesto,Guevara,Argentina
Vladimir,Putin,Russia
Maria,Sharapova,Russia
Bruce,Lee,China
Jack,Ma,China
df.repartition(col("country"))
将按内存中的国家/地区对数据进行重新分区。
让我们写出数据,以便我们可以检查每个内存分区的内容。
val outputPath = new java.io.File("./tmp/partitioned_by_country/").getCanonicalPath
df.repartition(col("country"))
.write
.csv(outputPath)
以下是将数据写到磁盘上的方法:
partitioned_by_country/
part-00002-95acd280-42dc-457e-ad4f-c6c73be6226f-c000.csv
part-00044-95acd280-42dc-457e-ad4f-c6c73be6226f-c000.csv
part-00059-95acd280-42dc-457e-ad4f-c6c73be6226f-c000.csv
每个文件都包含一个国家/地区的数据-part-00059-95acd280-42dc-457e-ad4f-c6c73be6226f-c000.csv
文件包含此中国数据,例如:
Bruce,Lee,China
Jack,Ma,China
partitionBy()
让我们用partitionBy
将数据写到磁盘上,看看文件系统输出如何不同。
这是将数据写到磁盘分区的代码。
val outputPath = new java.io.File("./tmp/partitionedBy_disk/").getCanonicalPath
df
.write
.partitionBy("country")
.csv(outputPath)
这是磁盘上数据的样子:
partitionedBy_disk/
country=Argentina/
part-00000-906f845c-ecdc-4b37-a13d-099c211527b4.c000.csv
country=China/
part-00000-906f845c-ecdc-4b37-a13d-099c211527b4.c000
country=Russia/
part-00000-906f845c-ecdc-4b37-a13d-099c211527b4.c000
为什么要在磁盘上分区数据?
对磁盘上的数据进行分区可以使某些查询运行得更快,如this blog post中所述。