今回は Apache Hive の Vectorization 機能を使ってパフォーマンスが向上するか試してみる。 Apache Hive では、通常 HDFS に保存されたデータを一行ずつ処理する。 それに対し Vectorization 機能を使うと、状況は限られるものの複数行をまとめて処理できるようになる。 結論から先に書くと、機能を有効にすることで多少のパフォーマンス向上はあることが分かった。
使った環境は次の通り。
$ cat /etc/redhat-release CentOS Linux release 7.4.1708 (Core) $ uname -r 3.10.0-693.17.1.el7.x86_64 $ hive --version Hive 2.3.2 Git git://stakiar-MBP.local/Users/stakiar/Desktop/scratch-space/apache-hive -r 857a9fd8ad725a53bd95c1b2d6612f9b1155f44d Compiled by stakiar on Thu Nov 9 09:11:39 PST 2017 From source with checksum dc38920061a4eb32c4d15ebd5429ac8a $ hadoop version Hadoop 2.8.3 Subversion https://git-wip-us.apache.org/repos/asf/hadoop.git -r b3fe56402d908019d99af1f1f4fc65cb1d1436a2 Compiled by jdu on 2017-12-05T03:43Z Compiled with protoc 2.5.0 From source with checksum 9ff4856d824e983fa510d3f843e3f19d This command was run using /home/vagrant/hadoop-2.8.3/share/hadoop/common/hadoop-common-2.8.3.jar
ダミーデータを用意する
パフォーマンスを測るからには、それなりのデータ量が必要となるはずなのでダミーデータを生成する。
今回は Golang とフェイクデータを生成するパッケージの github.com/icrowley/fake
を使った。
まずは Golang をインストールしてパッケージをダウンロードしておく。
$ sudo yum -y install golang git $ go get github.com/icrowley/fake
以下のようなダミーデータ生成用のプログラムを用意した。
$ cat << 'EOF' > dummy_users.go package main import ( "fmt" "strconv" "os" "math/rand" "time" "github.com/icrowley/fake" ) func main() { rand.Seed(time.Now().UnixNano()) if len(os.Args) < 2 { fmt.Println("Too few arguments") fmt.Printf("%s N\n", os.Args[0]) os.Exit(1) } N, err := strconv.Atoi(os.Args[1]) if err != nil { fmt.Printf("Please enter the integer: %s\n", N) os.Exit(1) } for i := 0; i < N; i++ { name := fake.FirstName() age := rand.Intn(100) fmt.Printf("%d,%s,%d\n", i, name, age) } } EOF
上記のプログラムをビルドする。
$ go build dummy_users.go
実行すると ID と名前と年齢のダミーデータを CSV で出力する。
$ ./dummy_users 10 0,Gregory,74 1,Sharon,35 2,Beverly,22 3,Mark,25 4,Ralph,12 5,Douglas,63 6,Catherine,44 7,Joyce,84 8,Bruce,87 9,Kevin,3
とりあえず 1000 万件 (行) のデータを生成しておく。
$ ./dummy_users 10000000 > dummy_users.csv
次のように 168MB の CSV ができた。
$ wc -l dummy_users.csv 10000000 dummy_users.csv $ du -m dummy_users.csv 168 dummy_users.csv
ダミーデータを Hive のテーブルに読み込む
続いてはダミーデータを Apache Hive のテーブルに読み込む。
まずは Hive の CLI を起動する。
$ hive
先ほど生成したダミーデータと一致するようなテーブルを作っておく。
hive> CREATE TABLE users ( > id BIGINT, > name STRING, > age INT > ) > ROW FORMAT DELIMITED > FIELDS TERMINATED BY ',' > STORED AS TEXTFILE; OK Time taken: 1.243 seconds
あとは先ほど作った CSV をテーブルに読み込むだけ。
hive> LOAD DATA LOCAL INPATH 'dummy_users.csv' OVERWRITE INTO TABLE users; Loading data to table default.users OK Time taken: 12.993 seconds
ORC フォーマットのテーブルにコピーする
Apache Hive の Vectorization 機能は、当初は ORC ファイルフォーマットだけに対応していた。 ただし Apache Hive のバージョン 2.1 以降では、それ以外のフォーマットもサポートされているらしい。 とはいえ、軽く触ったところ ORC の方がパフォーマンス向上が望めそうなので、そちらを使うことにする。
[HIVE-12878] Support Vectorization for TEXTFILE and other formats - ASF JIRA
まずは、先ほどとスキーマは同じでファイルフォーマットだけ ORC に変更したテーブルを用意する。
hive> CREATE TABLE users_orc ( > id BIGINT, > name STRING, > age INT > ) > ROW FORMAT DELIMITED > FIELDS TERMINATED BY ',' > STORED AS ORC; OK Time taken: 0.089 seconds
あとは、元のテーブルからデータをコピーしてくるだけ。
hive> INSERT INTO TABLE users_orc SELECT * FROM users; ... OK Time taken: 47.313 seconds
Vectorization 機能を有効にしてみる
Vectorization 機能は hive.vectorized.execution.enabled
と hive.vectorized.execution.reduce.enabled
で有効・無効を切り替える。
次のように、デフォルトでは無効化されている。
hive> set hive.vectorized.execution.enabled; hive.vectorized.execution.enabled=true hive> set hive.vectorized.execution.reduce.enabled; hive.vectorized.execution.reduce.enabled=true
この項目を true
にすれば機能が有効になる。
hive> set hive.vectorized.execution.enabled=true; hive> set hive.vectorized.execution.reduce.enabled=true;
Vectorization 機能が使えない状況では単に設定が無視されるだけなので、とりあえず有効にしておいても良さそう。
機能の有無でパフォーマンスを比較する
Vectorization 機能は、比較的単純なクエリを実行するときに使えるらしい。 そこで、次のような年齢ごとのユーザ数を集計するクエリで比較してみよう。
hive> SELECT > age, > COUNT(1) > FROM users_orc > GROUP BY age; ... 0 100244 1 100033 2 99509 3 100067 4 99326 5 100026 ... 95 99622 96 99675 97 100287 98 99751 99 100240
機能の切り替えを簡単にしたいので、設定を ~/.hiverc という設定ファイルに書き込むことにする。 ここに書いておくと Hive の CLI を起動するとき、自動でそこに記述されている内容を読み込んでくれる。
$ cat << 'EOF' > ~/.hiverc set hive.vectorized.execution.enabled=false; set hive.vectorized.execution.reduce.enabled=false; EOF
まずは機能を無効にした状態で、次のように 10 回ほどクエリを実行したときの時間を測ってみた。
$ SQL='SELECT age, COUNT(1) FROM users_orc GROUP BY age' $ for i in {1..10}; do hive -e "$SQL" 2>&1 | tail -n 1; done; Time taken: 48.642 seconds, Fetched: 100 row(s) Time taken: 46.033 seconds, Fetched: 100 row(s) Time taken: 48.642 seconds, Fetched: 100 row(s) Time taken: 47.711 seconds, Fetched: 100 row(s) Time taken: 47.276 seconds, Fetched: 100 row(s) Time taken: 48.677 seconds, Fetched: 100 row(s) Time taken: 47.498 seconds, Fetched: 100 row(s) Time taken: 46.391 seconds, Fetched: 100 row(s) Time taken: 46.013 seconds, Fetched: 100 row(s) Time taken: 48.593 seconds, Fetched: 100 row(s)
次は設定ファイルで機能を有効化する。
$ cat << 'EOF' > ~/.hiverc set hive.vectorized.execution.enabled=true; set hive.vectorized.execution.reduce.enabled=true; EOF
先ほどと同じように実行時間を測ってみた結果が次の通り。 何だか、ほんのり速くなっているような?
$ for i in {1..10}; do hive -e "$SQL" 2>&1 | tail -n 1; done; Time taken: 45.088 seconds, Fetched: 100 row(s) Time taken: 43.822 seconds, Fetched: 100 row(s) Time taken: 45.188 seconds, Fetched: 100 row(s) Time taken: 45.157 seconds, Fetched: 100 row(s) Time taken: 43.627 seconds, Fetched: 100 row(s) Time taken: 45.167 seconds, Fetched: 100 row(s) Time taken: 45.897 seconds, Fetched: 100 row(s) Time taken: 45.647 seconds, Fetched: 100 row(s) Time taken: 43.263 seconds, Fetched: 100 row(s) Time taken: 44.603 seconds, Fetched: 100 row(s)
実行にかかった時間を比較する
なんだか、ほんのり速くなっているような感じはするけど確証がないので詳しく見ていくことにしよう。 これ以降の部分は、別に Apache Hive とは関係がないので、どういった環境を使っても構わない。
調査するために、まずは Python のサードパーティ製パッケージの scipy
と numpy
をインストールしておく。
$ pip install scipy numpy
Python の REPL を起動する。
$ python
機能を有効にする前と後の実行時間を numpy
の配列として用意する。
>>> import numpy as np >>> before = np.array([ ... 48.642, ... 46.033, ... 48.642, ... 47.711, ... 47.276, ... 48.677, ... 47.498, ... 46.391, ... 46.013, ... 48.593, ... ]) >>> after = np.array([ ... 45.088, ... 43.822, ... 45.188, ... 45.157, ... 43.627, ... 45.167, ... 45.897, ... 45.647, ... 43.263, ... 44.603, ... ])
平均実行時間を比べると、機能を有効にしたときの方が 3 秒前後短いようだ。
>>> before.mean() 47.5476 >>> after.mean() 44.7459
ということで Vectorization 機能はパフォーマンスの向上に有効でした。 ...というのは、あまりにも短絡的なので、一応検定しておくことにする。
ウェルチの t 検定で、機能を有効にした方の平均が「有意に小さい」を対立仮説に「有意に小さくない」を帰無仮説として検定する。
>>> _, p = stats.ttest_ind(before, after, equal_var=False) >>> p / 2 3.957745312666858e-06
p-value は非常に小さな値なので、仮に有意水準 1% でも余裕で帰無仮説は棄却され対立仮説を採用することになる。
最初、F 検定からの単純 t 検定を使っていたんだけど、どうやら最近は等分散とか関係なくウェルチの t 検定でええやんってことみたい。 ちなみに、最初の方法でも等分散かつ有意だった。
laboratoryofbiology.blogspot.jp
めでたしめでたし。
- 作者: Edward Capriolo,Dean Wampler,Jason Rutherglen,佐藤直生,嶋内翔,Sky株式会社玉川竜司
- 出版社/メーカー: オライリージャパン
- 発売日: 2013/06/15
- メディア: 大型本
- この商品を含むブログ (3件) を見る
- 作者: 東京大学教養学部統計学教室
- 出版社/メーカー: 東京大学出版会
- 発売日: 1991/07/09
- メディア: 単行本
- 購入: 158人 クリック: 3,604回
- この商品を含むブログ (79件) を見る
スマートPythonプログラミング: Pythonのより良い書き方を学ぶ
- 作者: もみじあめ
- 発売日: 2016/03/12
- メディア: Kindle版
- この商品を含むブログ (1件) を見る