使用同一表的SubQuery的Spark替代方案

时间:2019-02-09 00:11:18

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

这篇文章是关于火花如何在使用同一表的子查询联接表时效率低下。使用Spark 2.2和Scala。

我有一个具有以下架构的表(fieldcnts):

|key |name |value |cnt |keycnt |minkeycnt|type |
|A   |f1   |123   |3   |100    |30       |-1   |
|A   |f1   |234   |5   |100    |30       |-1   |
|A   |f2   |345   |50  |225    |75       |-1   |
|B   |f1   |456   |40  |100    |30       |-1   |
...

此表派生自具有以下查询(Q1)的缓存表:

SELECT key, 'f1' AS name, f1 AS value, COUNT(*) AS cnt,
       first(keycnt) AS keycnt, first(keymincnt) AS keymincnt
FROM table1
GROUP BY key, f1
UNION ALL
SELECT key, 'f2' AS name, f2 AS value, COUNT(*) AS cnt,
       first(keycnt) AS keycnt, first(keymincnt) AS keymincnt
FROM table1
GROUP BY key, f2
UNION ALL
...
(simiarly for f3, f4 and f5)

表1的架构为:

|key|f1|f2|f3|f4|f5|keycnt|keymincnt|

因此,此阶段的fieldcnts表具有每个(键,f *)元组的计数。我目前在每行中都冗余存储了keycnt和keymincnt(keycnt的百分比),以避免额外的联接和计算,但是如果我获得更好的性能(具有联接和较小的数据集),以后可能会更改。

类型字段默认为-1。随后,我需要根据某些条件将此值设置为4个值之一。我使用以下查询(Q2)进行此操作:

SELECT R.key, R.name, R.value, R.cnt
    CASE 
        WHEN ${cond1} THEN -2
        WHEN ${cond2} THEN -3
        WHEN ${cond3} THEN -4
        ELSE -1
    END AS type
FROM fieldcnts R
JOIN (SELECT key, name, COUNT(*) AS numrecs, SUM(CASE WHEN cnt >= keymincnt THEN cnt ELSE 0 END) AS fieldsum
      FROM fieldcnts
      GROUP BY key, name) AS S
ON R.key = S.key AND R.name = S.name

每个$ {condn}是一个涉及cnt,keymincnt,keycnt,fieldum和numrecs的条件(例如(A B)(可能很专有,以防万一!)< / p>

所以这可行,但是在调优时我注意到,由于Q2中的子查询使用与外部查询相同的表,因此它迫使子查询从磁盘上读取(镶木地板扫描)。这很麻烦,因为Q1 UNION ALL查询中的每个查询都在发生这种情况!

在计算table1时,我首先注意到了这一点。读取一组镶木地板文件,它选择字段的子集,然后过滤掉keycnt 的所有行

SELECT R.key, R.f1, R.f2, R.f3, R.f4, R.f5, S.cnt AS keycnt, 0.33*S.cnt AS keymincnt
FROM tbl R
JOIN (SELECT key, COUNT(*) AS keycnt
      FROM tbl T
      GROUP BY key
      HAVING keycnt >= ${minCnt}) AS S
ON R.key = S.key

(实际上tbl在此之前就已经被很好地使用过,所以我现在就收到它的缓存了)。查看DAG,我可以清楚地看到外部表是从缓存中读取的,而内部表是从磁盘中读取的!我实际上使用Windowing解决了这个问题:

val dfSQL = spark.sql("SELECT key, f1, f2, f3, f4, f5 FROM tbl")
    .withColumn("keycnt", count("*").over(Window.partitionBy($"key")))
    .withColumn("keymincnt", $"key"*0.33)
    .filter($"keycnt" >= minCnt)

好多了。在这种情况下,不会读取SortMergeJoin或磁盘。

现在回到问题2:这与table1查询类似,但更为复杂。我还无法提出使用Windowing的方案(仍然是火花的新手)。这里有使用Windowing的方法吗?另外,有没有一种方法可以防止火花从磁盘读取使用同一表的子查询?

谢谢!

更新 回到这个问题。我的同事遇到了一个不同的解决方案:使用mapPartitions,它更适合我的问题。

我保留了此查询,该查询正是通过键分配给分区的:

SELECT R.key, R.f1, R.f2, R.f3, R.f4, R.f5, S.cnt AS keycnt, 0.33*S.cnt AS keymincnt
FROM tbl R
JOIN (SELECT key, COUNT(*) AS keycnt
    FROM tbl T
    GROUP BY key
    HAVING keycnt >= ${minCnt}) AS S
ON R.key = S.key

然后,我在mapPartitions scala函数中编写了其余算法。更清洁,更快。

0 个答案:

没有答案