Spark:'请求的数组大小超过VM限制'在编写数据帧时

时间:2018-03-30 01:53:08

标签: scala apache-spark spark-dataframe geospatial geojson

我遇到了" OutOfMemoryError:请求的数组大小超过VM限制"运行我的Scala Spark作业时出错。

我在具有以下构成的AWS EMR集群上运行此作业:

  

Master:1 m4.4xlarge 32 vCore,64 GiB memory

     

核心:1 r3.4xlarge 32 vCore,122 GiB内存

Spark I使用的版本在EMR发行标签5.11.0上为2.2.1。

我使用以下配置在火花壳中运行我的工作:

spark-shell --conf spark.driver.memory=40G 
--conf spark.driver.maxResultSize=25G 
--conf spark.serializer=org.apache.spark.serializer.KryoSerializer 
--conf spark.kryoserializer.buffer.max=2000 
--conf spark.rpc.message.maxSize=2000 
--conf spark.dynamicAllocation.enabled=true

我尝试这项工作的目的是将对象的一列数据框转换为包含这些对象列表的一行数据框。

对象如下:

case class Properties (id: String)
case class Geometry (`type`: String, coordinates: Seq[Seq[Seq[String]]])
case class Features (`type`: String, properties: Properties, geometry: Geometry)

我的数据帧架构如下:

root
 |-- geometry: struct (nullable = true)
 |    |-- type: string (nullable = true)
 |    |-- coordinates: array (nullable = true)
 |    |    |-- element: array (containsNull = true)
 |    |    |    |-- element: array (containsNull = true)
 |    |    |    |    |-- element: string (containsNull = true)
 |-- type: string (nullable = false)
 |-- properties: struct (nullable = false)
 |    |-- id: string (nullable = true)

我将其转换为列表并将其添加到一行数据框中,如下所示:

val x = Seq(df.collect.toList)
final_df.withColumn("features", typedLit(x))

创建此列表时我不会遇到任何问题,而且速度非常快。但是,当我尝试通过执行以下任一操作将其写出来时,此列表的大小似乎有限:

final_df.first
final_df.write.json(s"s3a://<PATH>/")

我还试图通过执行以下操作将列表转换为数据框,但它似乎永远不会结束。

val x = Seq(df.collect.toList)
val y = x.toDF

我能够使用此数据帧的最大列表包含813318要素对象,每个对象包含一个包含33个元素列表的Geometry对象,总共29491869个元素。

尝试编写几乎任何大于这个列表的列表,在运行我的工作时会给出以下堆栈跟踪。

# java.lang.OutOfMemoryError: Requested array size exceeds VM limit
# -XX:OnOutOfMemoryError="kill -9 %p"
#   Executing /bin/sh -c "kill -9 33028"...
os::fork_and_exec failed: Cannot allocate memory (12)
18/03/29 21:41:35 ERROR FileFormatWriter: Aborting job null.
java.lang.OutOfMemoryError: Requested array size exceeds VM limit
    at org.apache.spark.sql.catalyst.expressions.codegen.BufferHolder.grow(BufferHolder.java:73)
    at org.apache.spark.sql.catalyst.expressions.codegen.UnsafeArrayWriter.write(UnsafeArrayWriter.java:217)
    at org.apache.spark.sql.catalyst.expressions.GeneratedClass$SpecificUnsafeProjection.apply_1$(Unknown Source)
    at org.apache.spark.sql.catalyst.expressions.GeneratedClass$SpecificUnsafeProjection.apply1_1$(Unknown Source)
    at org.apache.spark.sql.catalyst.expressions.GeneratedClass$SpecificUnsafeProjection.apply(Unknown Source)
    at org.apache.spark.sql.execution.LocalTableScanExec$$anonfun$unsafeRows$1.apply(LocalTableScanExec.scala:41)
    at org.apache.spark.sql.execution.LocalTableScanExec$$anonfun$unsafeRows$1.apply(LocalTableScanExec.scala:41)
    at scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:234)
    at scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:234)
    at scala.collection.immutable.List.foreach(List.scala:381)
    at scala.collection.TraversableLike$class.map(TraversableLike.scala:234)
    at scala.collection.immutable.List.map(List.scala:285)
    at org.apache.spark.sql.execution.LocalTableScanExec.unsafeRows$lzycompute(LocalTableScanExec.scala:41)
    at org.apache.spark.sql.execution.LocalTableScanExec.unsafeRows(LocalTableScanExec.scala:36)
    at org.apache.spark.sql.execution.LocalTableScanExec.rdd$lzycompute(LocalTableScanExec.scala:48)
    at org.apache.spark.sql.execution.LocalTableScanExec.rdd(LocalTableScanExec.scala:48)
    at org.apache.spark.sql.execution.LocalTableScanExec.doExecute(LocalTableScanExec.scala:52)
    at org.apache.spark.sql.execution.SparkPlan$$anonfun$execute$1.apply(SparkPlan.scala:117)
    at org.apache.spark.sql.execution.SparkPlan$$anonfun$execute$1.apply(SparkPlan.scala:117)
    at org.apache.spark.sql.execution.SparkPlan$$anonfun$executeQuery$1.apply(SparkPlan.scala:138)
    at org.apache.spark.rdd.RDDOperationScope$.withScope(RDDOperationScope.scala:151)
    at org.apache.spark.sql.execution.SparkPlan.executeQuery(SparkPlan.scala:135)
    at org.apache.spark.sql.execution.SparkPlan.execute(SparkPlan.scala:116)
    at org.apache.spark.sql.execution.QueryExecution.toRdd$lzycompute(QueryExecution.scala:92)
    at org.apache.spark.sql.execution.QueryExecution.toRdd(QueryExecution.scala:92)
    at org.apache.spark.sql.execution.datasources.FileFormatWriter$$anonfun$write$1.apply$mcV$sp(FileFormatWriter.scala:173)
    at org.apache.spark.sql.execution.datasources.FileFormatWriter$$anonfun$write$1.apply(FileFormatWriter.scala:166)
    at org.apache.spark.sql.execution.datasources.FileFormatWriter$$anonfun$write$1.apply(FileFormatWriter.scala:166)
    at org.apache.spark.sql.execution.SQLExecution$.withNewExecutionId(SQLExecution.scala:65)
    at org.apache.spark.sql.execution.datasources.FileFormatWriter$.write(FileFormatWriter.scala:166)
    at org.apache.spark.sql.execution.datasources.InsertIntoHadoopFsRelationCommand.run(InsertIntoHadoopFsRelationCommand.scala:145)
    at org.apache.spark.sql.execution.command.ExecutedCommandExec.sideEffectResult$lzycompute(commands.scala:58)

我已尝试进行一百万次配置更改,包括在这项工作中投入更多驱动程序和执行程序内存,但无济于事。有没有办法解决?有什么想法吗?

2 个答案:

答案 0 :(得分:0)

问题出在这里

val x = Seq(df.collect.toList) 

当您对数据帧进行收集时,它会将数据帧的所有数据发送给驱动程序。因此,如果您的数据帧很大,这将导致驱动程序内存不足。

需要注意的是,在您分配给执行程序的所有内存中,驱动程序所使用的堆内存通常为30%(如果未更改)。因此,由于收集操作,驱动程序正在发生数据量阻塞。

现在你可能认为数据帧在磁盘上的尺寸较小,但这是因为数据被序列化并保存在那里。当您收集它时,实现数据框并使用JVM来存储数据。这将导致巨大的内存爆炸(通常为5-7X)。

我建议您删除收集部分并直接使用df数据框。因为我重新审视

val x = Seq(df.collect.toList) and df are essentially same

答案 1 :(得分:0)

嗯,有一个数据帧聚合功能可以执行您想要的操作,而无需在驱动程序上进行收集。例如,如果您想通过键:df.groupBy($"key").agg(collect_list("feature"))来收集所有“功能”列,或者您真的想对整个数据框执行此操作而不进行分组:df.agg(collect_list("feature"))

但是我想知道为什么要使用一个每个对象只有一行的数据框而不是包含整个结果的一行的数据框,为什么要这么做。即使使用collect_list聚合函数,如果内存仍然用完,我也不会感到惊讶。