在Spark中联接多个表的有效方法

时间:2019-03-14 13:51:50

标签: apache-spark pyspark hadoop-partitioning

有人问过类似的问题here,但它不能正确解决我的问题。我有近100个DataFrame,每个都有至少200,000行,我需要通过基于列full进行ID联接来连接它们,从而创建一个具有列的DataFrame- ID, Col1, Col2,Col3,Col4, Col5..., Col102

仅供说明,我的DataFrames的结构-

df1 =                          df2 =            df3 =          .....  df100 = 
+----+------+------+------+    +----+------+    +----+------+         +----+------+ 
|  ID|  Col1|  Col2|  Col3|    |  ID|  Col4|    |  ID|  Col5|         |  ID|Col102|
+----+------+-------------+    +----+------+    +----+------+         +----+------+
| 501|  25.1|  34.9| 436.9|    | 501| 22.33|    | 503| 22.33|         | 501|  78,1|
| 502|  12.2|3225.9|  46.2|    | 502| 645.1|    | 505| 645.1|         | 502|  54.9|
| 504| 754.5| 131.0| 667.3|    | 504| 547.2|    | 504| 547.2|         | 507|     0|
| 505|324.12| 48.93|  -1.3|    | 506|     2|    | 506|     2|         | 509| 71.57|
| 506| 27.51| 88.99|  67.7|    | 507| 463.7|    | 507| 463.7|         | 510|  82.1|
.
.
+----+------+------|------|    |----|------|    |----|------|         |----|------|

我通过依次对所有它们进行full连接来开始连接这些DataFrame。自然,这是一个计算量很大的过程,因此必须努力减少不同工作节点之间shuffles的数量。因此,我首先使用repartition()将基于df1的DataFrame ID进行了分区,将基于hash-partitions的{​​{1}}的DataFrame划分为30个分区-

ID

现在,我在df1 = df1.repartition(30,'ID') full之间进行了df1连接。

df2

由于df = df1.join(df2,['ID'],how='full') df.persist() 已经是df1,所以我期望上面的hash-partitioned会跳过改组并保持join的{​​{1}},但是我请注意,确实发生了partitioner,并将df1上的分区数增加到shuffle。现在,如果继续通过如下所示的函数调用后续的DataFrame来加入它们,则会收到错误df-

200

更新:错误消息-

java.io.IOException: No space left on device

问题:1.为什么我们执行第一个def rev(df,num): df_temp = spark.read.load(filename+str(num)+'.csv') df_temp.persist() df = df.join(df_temp,['ID'],how='full') df_temp.unpersist() return df df = rev(df,3) df = rev(df,4) . . df = rev(df,100) # I get the ERROR here below, when I call the first action count() - print("Total number of rows: "+str(df.count())) df.unpersist() # Never reached this stage. 时没有维护Py4JJavaError: An error occurred while calling o3487.count. : org.apache.spark.SparkException: Job aborted due to stage failure: Task 42 in stage 255.0 failed 1 times, most recent failure: Lost task 42.0 in stage 255.0 (TID 8755, localhost, executor driver): java.io.IOException: No space left on device at sun.nio.ch.FileDispatcherImpl.write0(Native Method) at sun.nio.ch.FileDispatcherImpl.write(FileDispatcherImpl.java:60) 的分区程序?

2。如何才能有效地连接这些多个表,同时又避免出现此df1问题?用户@silvio here建议使用.bucketBy(),但他也暗示了将维护分区程序的事实,但这种情况并未发生。因此,我不确定加入这些多个DataFrame的有效方法是什么。

任何建议/提示将不胜感激。

2 个答案:

答案 0 :(得分:2)

第一次尝试使用for循环(您可能已经拥有)在每N次迭代中保留大df

第二次尝试通过设置sqlContext.sql("set spark.sql.shuffle.partitions=100")而不是默认值200来控制默认分区号。

您的代码应如下所示:

num_partitions = 10
big_df = spark.createDataFrame(...) #empty df
for i in range(num_partitions):
   big_df = big_df.join(df, ....)

   if i % num_partitions == 0:
     big_df = big_df.persist()

在这里,我将持久性称为每10次迭代,您当然可以根据您的工作行为调整该数字。

编辑: 在您的情况下,您将本地df_temp保留在rev函数中,但不保留包含所有先前联接(在您的情况下为df)的整个数据框。因为这是本地持久性,所以这对最终执行计划没有影响。关于我的建议,我们假设您总共需要100个连接,然后使用上面的代码,您应该循环遍历循环[1..100],并每10次迭代保留累积的结果。持久存储大数据帧之后,DAG将包含较少的内存计算,因为将存储中间步骤,并且Spark知道如何从存储中还原它们,而不是从头开始重新计算所有内容。

答案 1 :(得分:1)

我过去也遇到过类似的问题,只是没有那么多RDD。我能找到的最有效的解决方案是使用低级RDD API。首先存储所有RDD,以便它们通过连接列在哈希中进行分区和排序:https://spark.apache.org/docs/2.4.0/api/java/org/apache/spark/rdd/OrderedRDDFunctions.html#repartitionAndSortWithinPartitions-org.apache.spark.Partitioner-

此后,可以使用zip分区来实现连接,而不会拖曳或占用大量内存:https://spark.apache.org/docs/2.4.0/api/java/org/apache/spark/rdd/RDD.html#zipPartitions-org.apache.spark.rdd.RDD-boolean-scala.Function2-scala.reflect.ClassTag-scala.reflect.ClassTag-