EMR上でHive on Tezを利用する

Amazon EMR

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

本日Amazon EMR release 4.7.0がリリースされました。
Apache Tez 0.8.3 and Apache Phoenix 4.7.0 now available on Amazon EMR

そして、このリリースにApache Tezが含まれていました!Tezは以前EMR上に手動でインストールして動作検証を行ったことがあり、既存のHiveクエリを大幅に高速化出来ることが期待できるためEMR公式リリースを楽しみにしていました!
「Tez on EMRを試してみた」というタイトルで話しました #cmdevio2015G | Developers.IO

ということで、今回はEMR release 4.7.0上でHive on Tezを利用する方法について紹介したいと思います。なお、公式ドキュメントに書かれている[Using Tez]とほぼおなじ内容を試しています *1
Apache Tez - Amazon Elastic MapReduce

前提

以下の環境を前提としています。

  • emr-4.7.0 でアプリケーションは Hadoop, Hive, Tez
  • ハードウェア構成は m1.medium を 1 台(検証用なのでマスターノードのみ)
  • EC2キーペアを設定(SSHで接続して操作するため)
  • 東京リージョン

AWS CLIだと以下のようなコマンドになります。KeyName, SubnetId, log-uriをご自身の環境に合わせればそのまま利用できるはずです。

aws emr create-cluster --applications Name=Hadoop Name=Hive Name=Tez \
  --ec2-attributes '{"KeyName":"XXXX","InstanceProfile":"EMR_EC2_DefaultRole","SubnetId":"subnet-dXXXX"}' \
  --service-role EMR_DefaultRole --enable-debugging --release-label emr-4.7.0 \
  --log-uri 's3n://aws-logs-XXXX-ap-northeast-1/elasticmapreduce/' --name 'Hive on Tez with EMR' \
  --instance-groups '[{"InstanceCount":1,"InstanceGroupType":"MASTER","InstanceType":"m1.medium","Name":"Master Instance Group"}]' \
  --region ap-northeast-1

なお、マネジメントコンソール経由でTezを利用する際は[詳細オプション]を利用する必要があるようでしたのでご注意下さい。

クイックオプション
hive-on-tez-with-amazon-emr_1

詳細オプション
hive-on-tez-with-amazon-emr_2

実行するクエリ

今回はTezを有効化する方法を紹介することが目的であるため、EMRの公式ドキュメントのチュートリアルに書かれているCloudFrontのログをHiveで集計するHive_CloudFront.qを従来のMapReduceエンジンとTezエンジンのそれぞれで実行してみたいと思います。なお、途中でエンジンを切り替えるため、チュートリアルの手順通りStepを追加するのではなくて同じ手順をSSHでログインしてHive CLI経由でHiveクエリを実行することとします。
ステップ 3: サンプルデータとスクリプトの準備 - Amazon Elastic MapReduce

cloudfront_logsテーブルを作成するHiveQLは以下になります。東京リージョン前提であるためHive_CloudFront.qから変更して、LOCATIONはハードコードしています。

CREATE EXTERNAL TABLE IF NOT EXISTS cloudfront_logs (
  Date Date,
  Time STRING,
  Location STRING,
  Bytes INT,
  RequestIP STRING,
  Method STRING,
  Host STRING,
  Uri STRING,
  Status INT,
  Referrer STRING,
  OS String,
  Browser String,
  BrowserVersion String
)
ROW FORMAT SERDE 'org.apache.hadoop.hive.serde2.RegexSerDe'
WITH SERDEPROPERTIES (
  "input.regex" = "^(?!#)([^ ]+)\\s+([^ ]+)\\s+([^ ]+)\\s+([^ ]+)\\s+([^ ]+)\\s+([^ ]+)\\s+([^ ]+)\\s+([^ ]+)\\s+([^ ]+)\\s+([^ ]+)\\s+[^\(]+[\(]([^\;]+).*\%20([^\/]+)[\/](.*)$"
) LOCATION 's3://ap-northeast-1.elasticmapreduce.samples/cloudfront/data';

次にクエリです。元々はINSERTする内容になっていましたが、今回はHive CLI経由で直接実行するためSELECT句以下のみ利用します。

SELECT os, COUNT(*) count FROM cloudfront_logs WHERE date BETWEEN '2014-07-05' AND '2014-08-05' GROUP BY os;

Hive on MapReduce

最初は従来のMapReduceエンジン上でHiveクエリを実行したいと思います。マスターノードにSSHでログインしてhiveコマンドでHive CLIを起動します。

$ ssh -i /path/to/XXXX.pem -l hadoop ec2-XXX-XXX-XXX-XXX.ap-northeast-1.compute.amazonaws.com
[hadoop@ip-172-31-16-183 ~]$ hive

Logging initialized using configuration in file:/etc/hive/conf.dist/hive-log4j.properties
hive> 

まずはcloudfront_logsテーブルを作成します。

hive> CREATE EXTERNAL TABLE IF NOT EXISTS cloudfront_logs (
    >   Date Date,
    >   Time STRING,
    >   Location STRING,
    >   Bytes INT,
    >   RequestIP STRING,
    >   Method STRING,
    >   Host STRING,
    >   Uri STRING,
    >   Status INT,
    >   Referrer STRING,
    >   OS String,
    >   Browser String,
    >   BrowserVersion String
    > )
    > ROW FORMAT SERDE 'org.apache.hadoop.hive.serde2.RegexSerDe'
    > WITH SERDEPROPERTIES (
    >   "input.regex" = "^(?!#)([^ ]+)\\s+([^ ]+)\\s+([^ ]+)\\s+([^ ]+)\\s+([^ ]+)\\s+([^ ]+)\\s+([^ ]+)\\s+([^ ]+)\\s+([^ ]+)\\s+([^ ]+)\\s+[^\(]+[\(]([^\;]+).*\%20([^\/]+)[\/](.*)$"
    > ) LOCATION 's3://ap-northeast-1.elasticmapreduce.samples/cloudfront/data';
OK
Time taken: 18.698 seconds

ではクエリを実行します。

hive> SELECT os, COUNT(*) count FROM cloudfront_logs WHERE date BETWEEN '2014-07-05' AND '2014-08-05' GROUP BY os;
Query ID = hadoop_20160603125757_5806ff1d-2d8b-4f60-870a-82614a17bf00
Total jobs = 1
Launching Job 1 out of 1
Number of reduce tasks not specified. Estimated from input data size: 1
In order to change the average load for a reducer (in bytes):
  set hive.exec.reducers.bytes.per.reducer=<number>
In order to limit the maximum number of reducers:
  set hive.exec.reducers.max=<number>
In order to set a constant number of reducers:
  set mapreduce.job.reduces=<number>
Starting Job = job_1464953340211_0001, Tracking URL = http://ip-172-31-16-183.ap-northeast-1.compute.internal:20888/proxy/application_1464953340211_0001/
Kill Command = /usr/lib/hadoop/bin/hadoop job  -kill job_1464953340211_0001
Hadoop job information for Stage-1: number of mappers: 1; number of reducers: 1
2016-06-03 12:57:39,857 Stage-1 map = 0%,  reduce = 0%
2016-06-03 12:58:17,272 Stage-1 map = 13%,  reduce = 0%, Cumulative CPU 15.85 sec
2016-06-03 12:58:32,230 Stage-1 map = 27%,  reduce = 0%, Cumulative CPU 25.27 sec
2016-06-03 12:58:35,703 Stage-1 map = 33%,  reduce = 0%, Cumulative CPU 27.84 sec
2016-06-03 12:58:39,012 Stage-1 map = 40%,  reduce = 0%, Cumulative CPU 30.27 sec
2016-06-03 12:58:47,908 Stage-1 map = 53%,  reduce = 0%, Cumulative CPU 38.07 sec
2016-06-03 12:58:54,552 Stage-1 map = 100%,  reduce = 0%, Cumulative CPU 43.39 sec
2016-06-03 12:59:09,939 Stage-1 map = 100%,  reduce = 100%, Cumulative CPU 45.88 sec
MapReduce Total cumulative CPU time: 45 seconds 880 msec
Ended Job = job_1464953340211_0001
MapReduce Jobs Launched: 
Stage-Stage-1: Map: 1  Reduce: 1   Cumulative CPU: 45.88 sec   HDFS Read: 629 HDFS Write: 60 SUCCESS
Total MapReduce CPU Time Spent: 45 seconds 880 msec
OK
Android	855
Linux	813
MacOS	852
OSX	799
Windows	883
iOS	794
Time taken: 126.913 seconds, Fetched: 6 row(s)

無事終わりました。コンソールにはお馴染みのMapReduceの進捗状況が表示されています。

Hive on Tez

次に同じクエリをTezエンジンで実行したいと思います。Tezエンジンへの切り替えはHiveのhive.execution.engineという設定値を変更するだけです。

まずはhive.execution.engineの初期状態を確認します。

hive> SET hive.execution.engine;
hive.execution.engine=mr

mrつまりMapReduceエンジンになっています。こちらをTezに変更します。

hive> SET hive.execution.engine=tez;

これだけです。この操作でMapReduceエンジンからTezエンジンに切り替わります。クエリを実行する前に設定値が変更されていることを確認します。

hive> SET hive.execution.engine;    
hive.execution.engine=tez

変更されていますね!では、実際にクエリを実行します。

hive> SELECT os, COUNT(*) count FROM cloudfront_logs WHERE date BETWEEN '2014-07-05' AND '2014-08-05' GROUP BY os;
Query ID = hadoop_20160603130808_0a04cd9e-bbe3-4c95-861a-d69c2be073c9
Total jobs = 1
Launching Job 1 out of 1


Status: Running (Executing on YARN cluster with App id application_1464953340211_0002)

--------------------------------------------------------------------------------
        VERTICES      STATUS  TOTAL  COMPLETED  RUNNING  PENDING  FAILED  KILLED
--------------------------------------------------------------------------------
Map 1 ..........   SUCCEEDED      1          1        0        0       0       0
Reducer 2 ......   SUCCEEDED      1          1        0        0       0       0
--------------------------------------------------------------------------------
VERTICES: 02/02  [==========================>>] 100%  ELAPSED TIME: 67.18 s    
--------------------------------------------------------------------------------
OK
Android	855
Linux	813
MacOS	852
OSX	799
Windows	883
iOS	794
Time taken: 92.722 seconds, Fetched: 6 row(s)

全く同じクエリですがターミナルに出力される内容が変わっていることが分かるかと思います。処理時間に関しては 126.9 秒 -> 92.7 秒 なので 3 / 4 程度の時間で実行できていますね!なお、Tezを利用することでどの程度早くなるかはクエリやインスタンスタイプ、インスタンス数によって変わりますので、実際に利用する際はご自身の環境でお試し下さい。

Web UI

YARN ResourceManager Web UIではどのように表示されるのでしょうか。実際に見てみたいと思います。EMR上でSparkのWeb UIにアクセスする方法 | Developers.IOと同じようにSOCKSプロキシを利用してアクセスします。

YARN ResourceManager Web UIにアクセスすると、[Application Type]がそれぞれMAPREDUCETEZになっており、Hiveクエリ実行時のエンジンが識別可能なことが分かります。
hive-on-tez-with-amazon-emr_3

更に、TEZを実行していたApplicaitonの詳細画面を表示し、[Tracking URL]をクリックします。
hive-on-tez-with-amazon-emr_4

すると、TEZのWEB UIが表示されます。EMR上でSparkのWeb UIにアクセスする方法 | Developers.IOでもそうでしたが、各YARN ApplicaitonのWEB UIを表示する方法は一貫しているようですね。
hive-on-tez-with-amazon-emr_5

なおTezに関してはSparkと異なり、マスターノードのpublic DNSからでも直接Web UIにアクセスできるようになっていました。アクセスする際はhttp://masterDNS:8080/tez-uiをブラウザのアドレスバーに入力して下さい。
hive-on-tez-with-amazon-emr_6

最初からTezエンジンを利用する

公式ドキュメントに書かれている通り、EMRクラスタ起動時に--configurationsオプションを利用してhive-site.xmlhive.execution.engineの値を変更します。
Apache Tez - Amazon Elastic MapReduce

今回の起動コマンドの場合であれば6行目を追加する形になります。

aws emr create-cluster --applications Name=Hadoop Name=Hive Name=Tez \
  --ec2-attributes '{"KeyName":"XXXX","InstanceProfile":"EMR_EC2_DefaultRole","SubnetId":"subnet-dXXXX"}' \
  --service-role EMR_DefaultRole --enable-debugging --release-label emr-4.7.0 \
  --log-uri 's3n://aws-logs-XXXX-ap-northeast-1/elasticmapreduce/' --name 'Hive on Tez with EMR' \
  --instance-groups '[{"InstanceCount":1,"InstanceGroupType":"MASTER","InstanceType":"m1.medium","Name":"Master Instance Group"}]' \
  --configurations '[{"Classification":"hive-site","Properties":{"hive.execution.engine":"tez"},"Configurations":[]}]' \
  --region ap-northeast-1

Tezのインストール状況の確認

ちょっと寄り道して、TezがどのようにEMR上にインストールされているか確認したいと思います。

最初はTezのバイナリがHDFS上に配備されているか確認します。やはり配備されているようですね。

[hadoop@ip-172-31-16-183 ~]$ hadoop fs -ls /apps/tez
Found 1 items
-rw-r--r--   1 root hadoop   16391028 2016-06-03 11:32 /apps/tez/tez.tar.gz

次にtez-site.xmlを探してみたいと思います。予想通り/etc/tez/conf/tez-site.xmlにありました。

[hadoop@ip-172-31-16-183 ~]$ ll /etc/tez/conf/tez-site.xml 
-rw-r--r-- 1 root root 1869  6月  3 11:31 /etc/tez/conf/tez-site.xml

最後にHADOOP_CLASSPATHへの追加です。こちらは/etc/hadoop/conf/hadoop-env.sh内で行われていました。

[hadoop@ip-172-31-16-183 ~]$ grep -i tez /etc/hadoop/conf/hadoop-env.sh 
# tez environment, needed to enable tez
export TEZ_CONF_DIR=/etc/tez/conf
export TEZ_JARS=/usr/lib/tez
# Add tez into HADOOP_CLASSPATH
export HADOOP_CLASSPATH=$HADOOP_CLASSPATH:${TEZ_CONF_DIR}:${TEZ_JARS}/*:${TEZ_JARS}/lib/*

ということで、公式のインストール手順通り素直にインストールしてるみたいでした。
Apache Tez – Install and Deployment Instructions

最後に

ということで、EMR上でのHive on Tezの利用方法の紹介でした。EMR release 4.7.0を利用すれば1箇所設定を変えるだけで利用できますし、それだけで処理時間を短縮したり、同じ処理時間で良いなら例えばインスタンス数を減らすことでコスト削減できる可能性がありますのでぜひ試してみてはいかがでしょうか。

脚注

  1. ほぼ書き終えたタイミングで気付きました。。