Spark SQL - df.repartition和DataFrameWriter partitionBy之间的区别?

时间:2016-11-04 06:10:15

标签: apache-spark-sql data-partitioning

DataFrame repartition()和DataFrameWriter partitionBy()方法有什么区别?

我希望两者都习惯于“基于数据帧列分区数据”?或者有什么不同吗?

3 个答案:

答案 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中所述。