使用Dataset.groupByKey时如何解决2GB的缓冲区限制?

时间:2018-10-08 00:38:09

标签: apache-spark apache-spark-dataset

在Spark中使用Dataset.groupByKey(_.key).mapGroupsDataset.groupByKey(_.key).cogroup时,当其中一个分组导致2GB以上的数据时,我遇到了一个问题。

在开始减少数据量之前,我需要按组对数据进行规范化,并且我想将组划分为较小的子组,以便它们更好地分配。例如,这是我尝试拆分组的一种方法:

val groupedInputs = inputData.groupByKey(_.key).mapGroups {
    case(key, inputSeries) => inputSeries.grouped(maxGroupSize).map(group => (key, group))
}

但是不幸的是,尽管我尝试解决该问题,但我的工作总是因以下错误而死:java.lang.UnsupportedOperationException: Cannot grow BufferHolder by size 23816 because the size after growing exceeds size limitation 2147483632。使用Kryo序列化时,出现另一个Kryo serialization failed: Buffer overflow错误,建议我增加spark.kryoserializer.buffer.max,但是我已经将其增加到2GB的限制。

我想到的一种解决方案是在对键进行分组之前为它们添加一个随机值。这不是理想的选择,因为它将分散每个组(而不仅仅是大型组),但是我愿意为了“工作”而牺牲“理想”。该代码如下所示:

val splitInputs = inputData.map( record => (record, ThreadLocalRandom.current.nextInt(splitFactor)))
val groupedInputs = splitInputs.groupByKey{ case(record, split) => (record.key, split)).mapGroups {
    case((key, _), inputSeries) => inputSeries.grouped(maxGroupSize).map(group => (key, group.map(_._1)))
}

2 个答案:

答案 0 :(得分:1)

添加一个盐键,然后对您的键和盐键进行分组操作

import scala.util.Random
    val start = 1
      val end   = 5
      val randUdf = udf({() => start + Random.nextInt((end - start) + 1)})

      val saltGroupBy=skewDF.withColumn("salt_key", randUdf())
        .groupBy(col("name"), col("salt_key"))

因此,您所有的偏斜数据都不会进入一个执行程序中,从而导致2GB的限制。

但是您必须开发一种逻辑来汇总以上结果,最后最后删除salt键。

当您使用groupBy时,具有相同键的所有记录将到达一个执行程序,并且出现瓶颈。 以上是缓解这种情况的方法之一。

答案 1 :(得分:1)

在这种情况下,数据集有很多偏差,因此将记录分为常规大小的组非常重要,因此我决定分两遍处理数据集。首先,我使用窗口函数根据键对行进行编号,然后根据可配置的“ maxGroupSize”将其转换为“组索引”:

// The "orderBy" doesn't seem necessary here, 
// but the row_number function requires it.
val partitionByKey = Window.partitionBy(key).orderBy(key)

val indexedData = inputData.withColumn("groupIndex", 
  (row_number.over(partitionByKey) / maxGroupSize).cast(IntegerType))
  .as[(Record, Int)]

然后,我可以按键和索引进行分组,并产生大小一致的组-具有大量记录的键会更多地拆分,而具有很少记录的键可能根本不会拆分。

indexedData.groupByKey{ case (record, groupIndex) => (record.key, groupIndex) }
  .mapGroups{ case((key, _), recordGroup) =>
      // Remove the index values before returning the groups
      (key, recordGroup.map(_._1))
  }