如何在不产生.rdd成本的情况下检查Spark DataFrame的分区数

时间:2019-01-19 15:56:53

标签: scala apache-spark partition

关于如何获取n个RDD和一个DataFrame的分区数量存在很多问题:答案总是:

 rdd.getNumPartitions

 df.rdd.getNumPartitions

不幸的是,这是对DataFrame昂贵操作,因为

 df.rdd

需要从DataFramerdd的转换。这是运行时间的顺序

 df.count

我正在根据当前的数字编写 {em> repartitioncoalesceDataFrame的逻辑分区数在可接受值范围内,或者低于或高于它们。

  def repartition(inDf: DataFrame, minPartitions: Option[Int],
       maxPartitions: Option[Int]): DataFrame = {
    val inputPartitions= inDf.rdd.getNumPartitions  // EXPENSIVE!
    val outDf = minPartitions.flatMap{ minp =>
      if (inputPartitions < minp) {
        info(s"Repartition the input from $inputPartitions to $minp partitions..")
        Option(inDf.repartition(minp))
      } else {
        None
      }
    }.getOrElse( maxPartitions.map{ maxp =>
      if (inputPartitions > maxp) {
        info(s"Coalesce the input from $inputPartitions to $maxp partitions..")
        inDf.coalesce(maxp)
      } else inDf
    }.getOrElse(inDf))
    outDf
  }

但是我们不能以这种方式为每个人 rdd.getNumPartitions支付DataFrame的费用。

没有任何方法可以获取此信息-例如从在线/临时catalog的{​​{1}}表中查询?

更新:Spark GUI显示DataFrame.rdd操作所花费的时间与作业中最长的sql一样长。我将重新运行作业,并在此处添加屏幕截图。

以下只是一个 testcase :它使用的是生产数据量的一小部分。最长的registered只有五分钟-而这一分钟也将花费时间 (请注意,sql不是 在这里提供了帮助:它还必须随后执行,从而有效地使累积执行时间加倍。

enter image description here

我们可以看到,sql第30行的.rdd操作(如上面的代码段所示)需要5.1分钟-而DataFrameUtils操作仍然花了5.2分钟后-ie就后续save的执行时间而言,我们通过执行.rdd并没有节省任何时间。

2 个答案:

答案 0 :(得分:8)

rdd中没有rdd.getNumPartitions组件的固有成本,因为从不评估返回的RDD

虽然您可以凭经验轻松地确定这一点,但可以使用调试器(我将其留给读者练习),或者确定在基本情况下没有触发任何作业

Spark session available as 'spark'.
Welcome to
      ____              __
     / __/__  ___ _____/ /__
    _\ \/ _ \/ _ `/ __/  '_/
   /___/ .__/\_,_/_/ /_/\_\   version 2.4.0
      /_/

Using Scala version 2.11.12 (OpenJDK 64-Bit Server VM, Java 1.8.0_181)
Type in expressions to have them evaluated.
Type :help for more information.
scala> val ds = spark.read.text("README.md")
ds: org.apache.spark.sql.DataFrame = [value: string]

scala> ds.rdd.getNumPartitions
res0: Int = 1

scala> spark.sparkContext.statusTracker.getJobIdsForGroup(null).isEmpty // Check if there are any known jobs
res1: Boolean = true

可能不足以说服您。因此,让我们以更系统的方式进行处理:

  • rdd返回MapPartitionRDD(如上定义的ds):

    scala> ds.rdd.getClass
    res2: Class[_ <: org.apache.spark.rdd.RDD[org.apache.spark.sql.Row]] = class org.apache.spark.rdd.MapPartitionsRDD
    
  • RDD.getNumPartitions invokes RDD.partitions

  • 在非检查点场景RDD.partitions invokes getPartitions中(也可以跟踪检查点路径)。
  • RDD.getPartitions is abstract
  • 因此,本例中使用的实际实现是MapPartitionsRDD.getPartitions,简称delegates the call to the parent
  • MapPartitionsRDD与来源之间只有rdd

    scala> ds.rdd.toDebugString
    res3: String =
    (1) MapPartitionsRDD[3] at rdd at <console>:26 []
     |  MapPartitionsRDD[2] at rdd at <console>:26 []
     |  MapPartitionsRDD[1] at rdd at <console>:26 []
     |  FileScanRDD[0] at rdd at <console>:26 []
    

    类似地,如果Dataset包含一次交换,我们将跟随父母到最近的随机播放:

    scala> ds.orderBy("value").rdd.toDebugString
    res4: String =
    (67) MapPartitionsRDD[13] at rdd at <console>:26 []
     |   MapPartitionsRDD[12] at rdd at <console>:26 []
     |   MapPartitionsRDD[11] at rdd at <console>:26 []
     |   ShuffledRowRDD[10] at rdd at <console>:26 []
     +-(1) MapPartitionsRDD[9] at rdd at <console>:26 []
        |  MapPartitionsRDD[5] at rdd at <console>:26 []
        |  FileScanRDD[4] at rdd at <console>:26 []
    

    请注意,这种情况特别有趣,因为我们实际上触发了工作:

    scala> spark.sparkContext.statusTracker.getJobIdsForGroup(null).isEmpty
    res5: Boolean = false
    
    scala> spark.sparkContext.statusTracker.getJobIdsForGroup(null)
    res6: Array[Int] = Array(0)
    

    这是因为我们遇到了无法静态确定分区的情况(请参见Number of dataframe partitions after sorting?Why does sortBy transformation trigger a Spark job?)。

    在这种情况下,getNumPartitions也将触发工作:

    scala> ds.orderBy("value").rdd.getNumPartitions
    res7: Int = 67
    
    scala> spark.sparkContext.statusTracker.getJobIdsForGroup(null)  // Note new job id
    res8: Array[Int] = Array(1, 0)
    

    但是,这并不意味着观察到的成本与.rdd通话相关。相反,如果没有静态公式(例如,某些Hadoop输入格式需要完全扫描数据),这是找到partitions的固有成本。

请注意,此处提出的观点不应外推到Dataset.rdd的其他应用中。例如ds.rdd.count确实是昂贵且浪费的。

答案 1 :(得分:1)

根据我的经验sample_api.py很快,我从来没有遇到过超过一秒钟左右的时间。

或者,您也可以尝试

python -m unittest discover

这将避免使用df.rdd.getNumPartitions