如何调整大数据集上的映射/过滤(从两个数据集交叉连接)?

时间:2017-07-19 05:50:03

标签: scala apache-spark apache-spark-sql spark-jdbc

  • Spark 2.2.0

我从SQL脚本转换了以下代码。它已经运行了两个小时,它仍在运行。甚至比SQL Server慢。有什么不正确的吗?

以下是计划,

  1. table2推送到所有执行者
  2. 分区table1并将分区分发给执行者。
  3. table2/t2中的每一行都加入(交叉连接)table1的每个分区。
  4. 因此,可以分布式/并行地运行交叉连接结果的计算。 (我想,例如假设我有16个执行器,在所有16个执行器上保留t2的副本。然后将表1分成16个分区,每个执行器一个。然后每个执行器在表1的一个分区上进行计算和t2。)

    case class Cols (Id: Int, F2: String, F3: BigDecimal, F4: Date, F5: String,
                     F6: String, F7: BigDecimal, F8: String, F9: String, F10: String )
    case class Result (Id1: Int, ID2: Int, Point: Int)
    
    def getDataFromDB(source: String) = {
        import sqlContext.sparkSession.implicits._
    
        sqlContext.read.format("jdbc").options(Map(
          "driver" -> "com.microsoft.sqlserver.jdbc.SQLServerDriver",
          "url" -> jdbcSqlConn,
          "dbtable" -> s"$source"
        )).load()
          .select("Id", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10")
          .as[Cols]
      }
    
    val sc = new SparkContext(conf)
    val table1:DataSet[Cols] = getDataFromDB("table1").repartition(32).cache()
    println(table1.count()) // about 300K rows
    
    val table2:DataSet[Cols] = getDataFromDB("table2") // ~20K rows
    table2.take(1)
    println(table2.count())
    val t2 = sc.broadcast(table2)
    
    import org.apache.spark.sql.{functions => func}
    val j = table1.joinWith(t2.value, func.lit(true))
    
    j.map(x => {
      val (l, r) = x
      Result(l.Id, r.Id, 
      (if (l.F1!= null && r.F1!= null && l.F1== r.F1) 3 else 0)
      +(if (l.F2!= null && r.F2!= null && l.F2== r.F2) 2 else 0)
      + ..... // All kind of the similiar expression
      +(if (l.F8!= null && r.F8!= null && l.F8== r.F8) 1 else 0)
      )
    }).filter(x => x.Value >= 10)
    println("Total count %d", j.count()) // This takes forever, the count will be about 100
    

    如何用Spark惯用法重写它?

    参考:https://forums.databricks.com/questions/6747/how-do-i-get-a-cartesian-product-of-a-huge-dataset.html

1 个答案:

答案 0 :(得分:1)

(不知何故,我觉得我已经看过代码了)

代码很慢,因为您只使用一个任务就可以使用JDBC从数据库加载整个数据集,尽管cache它没有从中受益。

首先查看Web UI中的物理计划和Executors选项卡,以了解单个执行程序和执行此工作的单个任务。

您应该使用以下其中一项来微调要加载的任务数量:

  1. 对JDBC数据源使用partitionColumnlowerBoundupperBound选项
  2. 使用predicates选项
  3. 请参阅Spark官方文档中的JDBC To Other Databases

    在您完成加载后,您应该努力改进上一个count操作,并在以下行之后添加另一个count操作:

    val table1: DataSet[Cols] = getDataFromDB("table1").repartition(32).cache()
    // trigger caching as it's lazy in Dataset API
    table1.count
    

    整个查询速度慢的原因是,只有在执行操作时才会将table1标记为高速缓存(!)。换句话说,cache没有任何用处更重要的是使查询性能更差。

    table2.cache.count之后,效果也会提高。

    如果您想进行交叉加入,请使用crossJoin运算符。

      

    crossJoin(右:数据集[_]):DataFrame 与其他DataFrame明确的笛卡尔连接。

    请注意来自crossJoin标记的注释(无双关语)。

      

    笛卡尔连接非常昂贵,没有可以推下的额外滤波器。

    鉴于可用的所有优化,Spark已经处理了以下要求。

      

    因此交叉连接结果的计算可以分布式/并行运行。

    这是Spark的工作(再次,没有双关语意)。

    以下要求需要广播。

      

    我想,例如假设我有16个执行器,在所有16个执行器上保留t2的副本。然后将表1分成16个分区,每个执行器一个分区。然后每个执行程序在表1和t2的一个分区上进行计算。)

    使用broadcast函数提示Spark SQL的引擎在广播模式下使用table2。

      

    broadcast [T](df:Dataset [T]):Dataset [T] 将DataFrame标记为足够小,以便在广播联接中使用。