Меня интересуют характеристики производительности выполнения агрегатных функций над окном по сравнению с группировкой / объединением. В этом случае меня не интересуют оконные функции с настраиваемыми границами или порядком фреймов, а только как способ запуска агрегатных функций.
Обратите внимание, что меня интересует пакетная (не потоковая) производительность для приличного только объемы данных, поэтому я отключил широковещательные объединения для следующего.
Например, допустим, мы начинаем со следующего DataFrame:
val df = Seq(("bob", 10), ("sally", 32), ("mike", 9), ("bob", 18)).toDF("name", "age")
df.show(false)
+-----+---+
|name |age|
+-----+---+
|bob |10 |
|sally|32 |
|mike |9 |
|bob |18 |
+-----+---+
Допустим, мы хотим подсчитать количество появлений каждого имени, а затем укажите это количество в строках с совпадающим именем.
Группировать по / присоединиться
val joinResult = df.join(
df.groupBy($"name").count,
Seq("name"),
"inner"
)
joinResult.show(false)
+-----+---+-----+
|name |age|count|
+-----+---+-----+
|sally|32 |1 |
|mike |9 |1 |
|bob |18 |2 |
|bob |10 |2 |
+-----+---+-----+
joinResult.explain
== Physical Plan ==
*(4) Project [name#5, age#6, count#12L]
+- *(4) SortMergeJoin [name#5], [name#15], Inner
:- *(1) Sort [name#5 ASC NULLS FIRST], false, 0
: +- Exchange hashpartitioning(name#5, 200)
: +- LocalTableScan [name#5, age#6]
+- *(3) Sort [name#15 ASC NULLS FIRST], false, 0
+- *(3) HashAggregate(keys=[name#15], functions=[count(1)])
+- Exchange hashpartitioning(name#15, 200)
+- *(2) HashAggregate(keys=[name#15], functions=[partial_count(1)])
+- LocalTableScan [name#15]
Окно
import org.apache.spark.sql.expressions.Window
import org.apache.spark.sql.{functions => f}
val windowResult = df.withColumn("count", f.count($"*").over(Window.partitionBy($"name")))
windowResult.show(false)
+-----+---+-----+
|name |age|count|
+-----+---+-----+
|sally|32 |1 |
|mike |9 |1 |
|bob |10 |2 |
|bob |18 |2 |
+-----+---+-----+
windowResult.explain
== Physical Plan ==
Window [count(1) windowspecdefinition(name#5, specifiedwindowframe(RowFrame, unboundedpreceding$(), unboundedfollowing$())) AS count#34L], [name#5]
+- *(1) Sort [name#5 ASC NULLS FIRST], false, 0
+- Exchange hashpartitioning(name#5, 200)
+- LocalTableScan [name#5, age#6]
На основе По планам выполнения похоже, что Windowing более эффективен (меньше этапов). Итак, мой вопрос: всегда ли это так - всегда ли мне использовать оконные функции для такого рода агрегации? Будут ли эти два метода масштабироваться одинаково по мере роста данных? А как насчет крайнего перекоса (т.е. некоторые имена встречаются намного чаще, чем другие)?