Posted at

Top N 件をとる効率的なHive / Prestoクエリ

More than 1 year has passed since last update.

遅いクエリを眺めてたら、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

ちゃんちゃん。