我需要使用Spark数据帧在一组3个键上连接50个奇数文件。 我有每天有100000条记录的驱动程序表。我使用数据框将这张表与其他53个文件连接起来,如下所示。
val df1 = spark.read.parquet(<driver file>)
val df2 = spark.read.parquet(<right side file1>)
.
.
val df52 = spark.read.parquet(<right side file 52>)
//join
val refinedDF1 = df1.join(df2,Seq("key1","key2","key3"),"leftouter")).select(<some from left table>, <some from right table>)
val refinedDF2 = refinedDF1.join(df3,Seq("key1","key2","key3"),"leftouter")).select(<some from left table>, <some from right table>)
.
.
so on for all 50 odd files
refinedFinalDF.write.parquet(<s3 location>)
执行失败并显示错误
容器退出,退出代码为非零52
基本上是内存不足的异常。 我有一个相当大的集群,可容纳100,000条记录的数据集。我有一个包含12个执行器的EMR,每个执行器16G,驱动程序内存为20G。
我曾尝试使用df.repartition(200)以循环方式将数据帧手动分区为200个分区,但这完全没有帮助。在联接键中,对于所有记录,只有key1是不同的,而对于所有记录,key2和key3是相同的值。 是否可以进行优化以使其正常工作? 我要保存的最终数据帧中有140多个列。 如果驱动程序表有n条记录,那么在每个左外部之后,我仅得到n条记录。
更新: 我曾尝试用limit(100)从驱动程序表中创建一个较小的数据帧,但仍然出现内存不足异常。
答案 0 :(得分:0)
您的表是1-1还是1?如果它们是一对多的,那么您的联接将导致比您可能想要的更多的行。如果是这种情况,一种选择是首先对要加入的每个表进行groupBy。请考虑以下示例:
val df1 = Seq(1, 2).toDF("id")
val df2 = Seq(
(1, "a", true),
(1, "b", false),
(2, "c", true)
).toDF("id", "C2", "B2")
val df3 = Seq(
(1, "x", false),
(1, "y", true),
(2, "z", false)
).toDF("id", "C3", "B3")
// Left outer join without accounting for 1-Many relationship. Results in cartesian
// joining on each ID value!
df1.
join(df2, Seq("id"), "left_outer").
join(df3, Seq("id"), "left_outer").show()
+---+---+-----+---+-----+
| id| C2| B2| C3| B3|
+---+---+-----+---+-----+
| 1| b|false| y| true|
| 1| b|false| x|false|
| 1| a| true| y| true|
| 1| a| true| x|false|
| 2| c| true| z|false|
+---+---+-----+---+-----+
或者,如果将联接之前的行进行分组,以便您的关系始终为1-1,则不会添加记录
val df2Grouped = df2.groupBy("id").agg(collect_list(struct($"C2", $"B2")) as "df2")
val df3Grouped = df3.groupBy("id").agg(collect_list(struct($"C3", $"B3")) as "df3")
val result = df1.
join(df2Grouped, Seq("id"), "left_outer").
join(df3Grouped, Seq("id"), "left_outer")
result.printSchema
result.show(10, false)
scala> result.printSchema
root
|-- id: integer (nullable = false)
|-- df2: array (nullable = true)
| |-- element: struct (containsNull = true)
| | |-- C2: string (nullable = true)
| | |-- B2: boolean (nullable = false)
|-- df3: array (nullable = true)
| |-- element: struct (containsNull = true)
| | |-- C3: string (nullable = true)
| | |-- B3: boolean (nullable = false)
scala> result.show(10, false)
+---+-----------------------+-----------------------+
|id |df2 |df3 |
+---+-----------------------+-----------------------+
|1 |[[a, true], [b, false]]|[[x, false], [y, true]]|
|2 |[[c, true]] |[[z, false]] |
+---+-----------------------+-----------------------+
答案 1 :(得分:0)
发生这种情况,我用来创建数据框的s3存储桶中的基础数据中有多个文件夹,并且我正在过滤特定文件夹作为过滤器的一部分。例如:spark.read.parquet(s3 bucket).filter('folder_name =“ val”)。看起来spark正在将s3存储桶中的所有数据加载到执行程序内存中,然后运行筛选器。这就是为什么要轰炸的原因,与在配置单元外部分区表上指向s3位置的配置单元外部表上运行的配置单元查询运行相同的逻辑时,运行正常。我必须删除过滤器并阅读特定的文件夹才能解决问题。spark.read.parquet(s3 bucket / folder = value)。
答案 2 :(得分:0)
我有一个类似的情况,我有多个联接,最后我不得不将最终数据帧写入HDFS / Hive Table(Parquet格式)。
Spark在惰性执行机制上工作,这意味着,当对第53个数据帧进行操作(将其保存/写入为Parquet)时,Spark然后返回所有联接并执行它们,这将导致大量数据乱码,最终导致您的工作容器失败并抛出内存不足错误。
建议:您可以先将每个加入的数据帧写入HDFS,我的意思是说,一旦加入2个(可以超过2个,但保持有限)数据帧,则将加入的数据帧写入HDFS / Hive,然后使用select * 'hive parquet table
val refinedDF1 = df1.join(df2 ,condition,'join_type')
refinedDF1.write.parquet("location") or refinedDF1.write.mode("overwrite").saveAsTable("dbname.refine1")
val refinedDF1 = hc.sql("select * from dbname.refine1")
val refinedDF2 = refinedDF1.join(df3)
refinedDF2.write.parquet("location") or refinedDF1.write.mode("overwrite").saveAsTable("dbname.refine2")
val refinedDF2 = hc.sql("select * from dbname.refine2")
现在,您经常将连接写入HDFS,这意味着在调用最终连接时spark不必执行它们,只需要使用以表格形式保存的52'nd连接输出即可。
使用这种方法,我的脚本从22小时(包括容器内存错误)减少到15到30分钟(无内存异常/错误)。
一些提示:
1)排除您的联接key
为空的记录,spark在具有null = null
条件的联接上不能提供良好的性能,因此在联接数据框之前将其删除
2)当左边的数据框有很多行而右边的数据框是查找或几行时,请使用广播联接。
3)执行脚本后,您必须清理要保存在Hive / Hdfs中的中间数据帧。