高效的pyspark加入

时间:2018-11-28 16:31:20

标签: apache-spark pyspark

我已经阅读了很多有关如何在pyspark中进行有效联接的内容。我发现实现高效联接的方法基本上是:

  • 如果可以,请使用广播加入。 (我通常不能,因为数据帧太大)
  • 考虑使用非常大的群集。 (我不想因为 $$$ )。
  • 使用相同的分区程序

最后一个是我想尝试的,但是我找不到在pyspark中做到这一点的方法。我尝试过:

df.repartition(numberOfPartitions,['parition_col1','partition_col2'])

但是它无济于事,直到我停止它仍然需要花费很长时间,因为在最后的几个工作中卡住了火花。

那么,我如何才能在pyspark中使用相同的分区程序并加快连接速度,甚至摆脱永久使用的混乱?我需要使用哪个代码?

PD :即使在stackoverflow上,我也查看了其他文章,但仍然看不到代码。

2 个答案:

答案 0 :(得分:4)

如果适合您的需要,您还可以使用两次通过方法。首先,对数据进行重新分区,并使用分区表(dataframe.write.partitionBy())进行持久化。然后,将子分区依次循环连接,“追加”到同一最终结果表。 Sim很好地解释了这一点。参见下面的链接

two pass approach to join big dataframes in pyspark

基于上述情况,我能够在一个循环中串行连接子分区,然后将连接的数据持久保存到配置单元表中。

这是代码。

from pyspark.sql.functions import *
emp_df_1.withColumn("par_id",col('emp_id')%5).repartition(5, 'par_id').write.format('orc').partitionBy("par_id").saveAsTable("UDB.temptable_1")
emp_df_2.withColumn("par_id",col('emp_id')%5).repartition(5, 'par_id').write.format('orc').partitionBy("par_id").saveAsTable("UDB.temptable_2")

因此,如果要加入整数emp_id,则可以按ID取模数进行分区,这样您就可以在spark分区之间重新分配负载,并且类似的记录将在两个数据帧上共享相同的分区id。 然后,您可以读取并遍历每个子分区数据,并合并两个数据框并将它们持久保存在一起。

counter =0;
paritioncount = 5;
while counter<=paritioncount:
    query1 ="SELECT * FROM UDB.temptable_1 where par_id={}".format(counter)
    query2 ="SELECT * FROM UDB.temptable_2 where par_id={}".format(counter)
    EMP_DF1 =spark.sql(query1)
    EMP_DF2 =spark.sql(query2)
    df1 = EMP_DF1.alias('df1')
    df2 = EMP_DF2.alias('df2')
    innerjoin_EMP = df1.join(df2, df1.emp_id == df2.emp_id,'inner').select('df1.*')
    innerjoin_EMP.show()
    innerjoin_EMP.write.format('orc').insertInto("UDB.temptable")
    counter = counter +1

我已经尝试过了,并且工作正常。这只是演示两次通过方法的示例。您的加入条件可能会有所不同,分区数也取决于您的数据大小。

答案 1 :(得分:2)

谢谢@vikrantrana的回答,如果需要的话,我会尝试的。之所以说这些是因为我发现问题不在于“大”联接,问题在于联接之前的计算量。想象一下这种情况:

我读取了一个表,并将其存储在一个名为df1的数据框中。我读取了另一个表,并将其存储在df2中。然后,我进行了大量的计算并加入了两者,最后我得到了df1df2之间的结合。这里的问题不是大小,问题是spark的执行计划很大,并且无法在内存中维护所有中间表,因此它开始写入磁盘,并且花费了很多时间。

对我有用的解决方案是在连接之前将df1df2保留在磁盘上(我还保留了其他大量计算和复杂结果的中间数据帧)。