在循环中将火花数据集(增量地)与更大的数据集联合

时间:2021-07-23 13:13:52

标签: scala dataframe apache-spark union

我有一个大约 1000 万个键的列表作为字符串列表 - list("xxx","yyx"......)。我想查询一个数据库以在循环中获取这些 10M 键的记录,因为它一次最多可以处理 1 M。数据库返回一个记录数据集。所以我想单独调用 1M 个键,最后将所有的输出结合起来。

val list_len =  list.length
var start = 0
val batch_size = 1000000  // 1-Milliom
val result = spark.createDataFrame(spark.sparkContext.emptyRDD[(Array[Byte])])

while (start < list_len) {

      var end = start + batch_size
      if (end > list_len) {
        end = list_len
      }

      val df = spark.context.read("some-database")
              .load.where('key' isin list.slice(start, end))

      result = result.union(df)
      // tried performing action and caching to prevent recursive 
      // result.count
      // result.cache  

      start = start + batch_size   
}

但这不起作用。 “ result = result.union(df) ”被递归地引起。所以在第一次迭代中,它扫描了 1M 条记录,在第二次 2M 期间,依此类推。

还有,

我想立即“执行”每次迭代而不是使用惰性求值,因为这样所有调用都同时对 db 进行,db 响应变慢。

1 个答案:

答案 0 :(得分:0)

您所做的是在驱动程序中调用数据库,创建 n 数据帧。所以你可以用简单的scala来做,然后使用union减少子结果列表:

val list = (1 to  100) // what ever
val batchSize = 3 // 1M
val grouped = list.grouped(batchSize).toList // split list into sublist

val empty = spark.createDataFrame(spark.sparkContext.emptyRDD[(List[Byte])])

// for each group, call DB
val result = grouped.map{ subList =>

     // random code, call the DB instead
     subList.map(x => (x-1 to x+1).map(_.toByte)).toDF("data")

  }.foldLeft(empty)(_ union _) // merge all sub-result DF into one

读取执行

迭代执行读取是另一个问题,但这是我所知道的:默认情况下,第一个 spark 总是惰性的,直到调用一个操作(计数,写入,...)。这是因为一旦spark开始处理数据,预计会太大而无法保存在内存中,它需要将中间数据存储在磁盘上。 Read 可能是一个例外,因为它需要知道数据集模式。但是,所做的事情取决于用于访问数据的 Spark 连接器(例如,对于 csv 和 parquet,我相信如果您提供架构并且仅读取文件标题,则它很懒惰)

  1. 因此您可以查看用于访问数据库的 spark 连接器,也许您可​​以在调用 read 时立即触发调用。但这不太可能,因为我认为这违反了火花原则。

  2. 否则,您需要使用操作触发火花调用,并使用缓存保留中间结果。类似的东西:

val result = grouped.map{ subList =>
  val subResult = spark.read.load.where(..., subList).cache
  subResult.count // minimal action to trigger read and cache
  subResult

}.foldLeft(empty)(_ union _) // merge all sub-result DF into one

注意:w.r.t 问题中的代码,请确保 .cache 位于 subResult 变量中,否则将不适用:

val subResult = spark.[...]
subResult.cache // does nothing, cache is not an action
subResult.count // subResult doesn't contain the cache in its execution dag
  1. 根据您的情况可能会更好的替代方法是将每个子结果读取和存储/写入 (h)dfs 数据集:
grouped.foreach{ subList =>
  // read and write in parquet
  spark.read.load.where(..., subList)
    .write.mode("append").parquet("hdfs://some/path")
}

// you can later read the whole (h)dfs dataset:
val everything = spark.read.parquet("hdfs://some/path")

在上述所有情况下,调用的分布将取决于 groupedmapforeach 运算符的类型。对于 List 它将是迭代的,一个接一个调用。如果你想要更多的控制,你可以研究 scala 并行映射执行,也许使用 Future 和 ExecutionContext。