LoginSignup
24
13

More than 1 year has passed since last update.

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

Last updated at Posted at 2017-03-16

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

ちゃんちゃん。

24
13
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
24
13