遅いクエリを眺めてたら、Prestoでrow_numberを使ってナンバリングをした後に、rank<=10といったことをしているクエリが多々あった。
例えばPrestoだと、row_numberは全レコードを保持して処理するので、件数が多ければ多いほど遅いし、メモリ消費量もあれなことになる。例えば数億件でrow_numberをすると2~300GBピーク時に使ってそうだ。
https://github.com/prestodb/presto/issues/5298
なので、効率的なPrestoとHive0.13のクエリを書いておく。
Presto
ダメな例(row_number)
データ件数数億件で、30分以上かかっても終わらなさそう。
SELECT checksum(rnk)
FROM (
SELECT row_number() OVER (PARTITION BY l_orderkey, l_partkey ORDER BY l_shipdate DESC) AS rnk
FROM lineitem
) t
WHERE rnk = 1
良い例(rank)
数億件で1~2分程度で終わる。メモリ消費も数GB程度。
SELECT checksum(rnk)
FROM (
SELECT rank() OVER (PARTITION BY l_orderkey, l_partkey ORDER BY l_shipdate DESC) AS rnk
FROM lineitem
) t
WHERE rnk = 1
Hive
悪いとまでは言わないけど良くない例(row_number, rank)
Hiveのrankやrow_numberはあんまりパフォーマンス的には変わらなそう。(コードまでは見てない。)
数億件で4~6分くらい。
SELECT count(rnk)
FROM (
SELECT rank() OVER (PARTITION BY l_orderkey, l_partkey ORDER BY l_shipdate DESC) AS rnk
FROM lineitem
) t
WHERE rnk = 1
SELECT count(rnk)
FROM (
SELECT row_number() OVER (PARTITION BY l_orderkey, l_partkey ORDER BY l_shipdate DESC) AS rnk
FROM lineitem
) t
WHERE rnk = 1
良い例 (each_top_k)
HiveというよりはHivemallの関数にeach_top_kがある。これはまさにTop N件を取得するための関数。
これを使うと3分くらいで処理が終わるようになる。下記の例では、COUNT用にMRの段数を1つ増やして3分なので、それを消せばもう少し早く終わるのではないかなと。
SELECT COUNT(rnk) FROM (
SELECT each_top_k(
1, concat(l_orderkey, '+', l_partkey), l_shipdate,
l_orderkey, l_partkey
) as (rnk, l_shipdate, l_orderkey, l_partkey)
FROM (
SELECT l_orderkey, l_partkey, l_shipdate
FROM lineitem
CLUSTER BY l_orderkey, l_partkey
) t0
) t1
ちゃんちゃん。