RSS2.0

Hive を構築して Hadoop とつなげてみた

引き続き Hadoop でいろいろ試してみるシリーズです。
前回は Hadoop 本体を構築し、HDFS に配置したファイルを使って簡単なデータ分析をしてみました。取り扱ったデータが小さい上に、実際には単体構成な Pseudo-Distributed Mode だったのでアレですが、Hadoop の概要のようなものが少しずつ見えてきました。

Hadoop ではやはり HDFS という独自のファイルシステムを扱うことが特徴的で、実際に HDFS 上にファイルを配置し、処理を行うというのは、自動化を考えるとなかなか大変そうでした。この HDFS を使ったデータ処理をより手軽に行うための周辺技術として、古くから Hive というコンポーネントが使われてきました。Hive を利用することで、HDFS へのファイル配置や参照・集計といった処理を、HiveQL という SQL ライクなクエリ言語として実行することができます。

今回は Hive の環境構築を行い、前回 HDFS に対して直接行ったデータ集計と同じことを、Hive を通して試してみようと思います。

なお、Hive はソフトウェア要件として Java7 以上、Hadoop 2.x を必要としています。
ここでは、前回構築した Hadoop 環境に追加していく形で構築していきます。

Hive のインストール

まずは Hive をインストールします。公式サイトドキュメントがあるのですが、いまいちうまく動いてくれなかったので他にもいろいろやっています。

まずはアーカイブをとってきましょう。今回インストールする Hive のバージョンは 2.1.1 です。
アーカイブが公開されているミラーサイトはいくつかありますが、理研のものを使ってみます。
$ wget http://ftp.riken.jp/net/apache/hive/hive-2.1.1/apache-hive-2.1.1-bin.tar.gz

展開して配置します。
配置先はどこでもいいですが、私は /usr/local 下に置いておくことにします。
$ tar xvfz apache-hive-2.1.1-bin.tar.gz
$ sudo mv apache-hive-2.1.1-bin /usr/local/
$ sudo ln -s /usr/local/apache-hive-2.1.1-bin /usr/local/hive

bin ディレクトリ下に Hadoop のコマンドがあるので、こちらを実行できるようパスを通します。
$ vi ~/.bashrc
----------------------------
...
export HIVE_HOME="/usr/local/hive"
export PATH=$PATH:$HIVE_HOME/bin
...
----------------------------
$ source ~/.bashrc

次に、HDFS 上に Hive が利用するディレクトリを作成します。
Hadoop を起動したら、hdfs コマンドで /user/hive/warehouse と /tmp ディレクトリを作成し、アクセス権限を設定してください。
$ hdfs dfs -mkdir /user/hive
$ hdfs dfs -mkdir /user/hive/warehouse
$ hdfs dfs -mkdir /tmp

$ hdfs dfs -chmod g+w /user/hive/warehouse
$ hdfs dfs -chmod g+w /tmp
/user/hive/warehouse が Hive で取り扱うデータを配置するディレクトリ、/tmp が一時ファイルを配置するディレクトリになります。

Derby のインストール

Hive では、HDFS 上のデータに SQL ライクなインターフェースでアクセスするために、SQL でアクセスするテーブルのメタ情報などを JDBC Metastore として保持しておく必要があります。デフォルトでは JDBC Metastore は Derby という Java 製 RDB で作成されるため、Derby が利用できない場合はインストールしておかなければなりません。
Derby の他にも MySQL 等好きな RDB が利用できるので、きちんとした環境を構築する際には用途にあった RDB を選択すると良さそうです。

Derby の公式サイトからアーカイブをとってきます。ここではバージョン 10.13.1.1 を使うことにします。ミラーサイトは同じく理研です。
$ wget http://ftp.riken.jp/net/apache//db/derby/db-derby-10.13.1.1/db-derby-10.13.1.1-bin.tar.gz

アーカイブをとってきたら、展開して配置します。
配置先はどこでもいいですが、私は /usr/local 下に置いておくことにします。
$ tar xvfz db-derby-10.13.1.1-bin.tar.gz
$ sudo mv db-derby-10.13.1.1-bin /usr/local/
$ sudo ln -s /usr/local/db-derby-10.13.1.1-bin /usr/local/derby

bin ディレクトリ下に Derby の実行ファイルがあるので、こちらにパスを通しておきます。
また、lib ディレクトリ下には Hive が参照する jar ファイルがあるので、CLASSPATH にこれを通します。
$ vi ~/.bashrc
----------------------------
...
export DERBY_HOME=/usr/local/derby
export PATH=$PATH:$DERBY_HOME/bin
export CLASSPATH=$CLASSPATH:$DERBY_HOME/lib/derby.jar:$DERBY_HOME/lib/derbytools.jar
...
----------------------------
$ source ~/.bashrc

Hive の設定

Hive の諸々の設定をしていきます。
まず、$HIVE_HOME/conf ディレクトリ内に設定ファイル hive-site.xml のもととなるテンプレートファイルがあるので、これをコピーしておきます。
$ cp /usr/local/hive/conf/hive-default.xml.template /usr/local/hive/conf/hive-site.xml

この hive-site.xml の javax.jdo.option.ConnectionURL 値として、JDBC Metastore を作成するディレクトリを設定します。
ここでは /home/hive/metastore ディレクトリを指定しておきます。
<configuration>
...
  <property>
    <name>javax.jdo.option.ConnectionURL</name>
    <value>jdbc:derby:;databaseName=/home/hive/metastore/metastore_db;create=true</value>
    <description>
      JDBC connect string for a JDBC metastore.
      To use SSL to encrypt/authenticate the connection, provide database-specific SSL flag in the connection URL.
      For example, jdbc:postgresql://myhost/db?ssl=true for postgres database.
    </description>
  </property>
...
</configuration>

また、system:java.io.tmpdir 値として Hive が利用する一時ディレクトリを、system:user.name 値として Hive の Linux 実行ユーザーを設定します。
この 2つの設定は他の設定値が参照しているので、<configuration> タグ内の先頭に書いておきます。
<configuration>
  <property>
    <name>system:java.io.tmpdir</name>
    <value>/usr/local/hive/tmp</value>
  </property>
  <property>
    <name>system:user.name</name>
    <value>${user.name}</value>
  </property>
...
</configuration>
system:java.io.tmpdir 値として指定したディレクトリは、あらかじめ作成して Hive の実行ユーザーに権限を付与しておきます。
$ sudo mkdir /usr/local/hive/tmp
$ sudo chown hadoop.hadoop /usr/local/hive/tmp

JDBC Metastore を初期化する

Hive 上に作成されたテーブル情報などを格納するための JDBC Metastore を作成し、初期化します。
schematool コマンドを以下のように実行します。
$ schematool -dbType derby -initSchema
...
Metastore connection URL:	 jdbc:derby:;databaseName=/home/hive/metastore/metastore_db;create=true
Metastore Connection Driver :	 org.apache.derby.jdbc.EmbeddedDriver
Metastore connection User:	 APP
Starting metastore schema initialization to 2.1.0
Initialization script hive-schema-2.1.0.derby.sql
Initialization script completed
schemaTool completed
標準出力に Metastore connection URL として、hive-site.xml の javax.jdo.option.ConnectionURL 値として設定されたディレクトリが表示されます。
実際にこのディレクトリを確認してみると、JDBC Metastore が作成されているのがわかります。

既に JDBC Metastore を作成している状態で再度作成しようとしたり、schematool で作成する前に hive コマンドを実行してしまったりした場合には、エラーが発生します。
再作成の場合は普通再度作り直すことはないと思いますが、hive コマンドによって JDBC Metastore が作られてしまった場合には、metastore_db を削除すれば、schematool コマンドから再度 JDBC Metastore を作成することができます。JDBC Metastore にはテーブル内のデータは格納されていないので、JDBC Metastore を削除してもテーブルのレコード(実際には、これは HDFS 上に配置されたテキストファイルになります)は削除されません。

動作確認する

ここまでで Hive の環境構築は完了です。
実際に hive コマンドを実行してみます。
$ hive
...
hive> 
Hive のコンソールが起動すれば OK です。

Hive でデータ分析をする

前回は hadoop コマンドで、HDFS 上のアクセスログファイルに対してデータ分析を行い、IP 毎のアクセス数を集計してみました。今回は hadoop コマンドではなく、Hive を使って同等の集計を行ってみます。

hive コマンドで起動する Hive コンソールでは、Hadoop でのデータ集計をするにあたって、SQL ライクな HiveQL を使うことができます。全体的な使用感覚はだいぶ SQL に近いものとなっているので、RDB に慣れ親しんだユーザーにはかなりとっつきやすいと思います。

HiveQL でデータベースを作成する

Hive コンソールから HiveQL を使ってデータベースを作成します。データベースの中にテーブルを作っていくという流れや、データベースの作成クエリは普通の RDB とまさしく同じです。
hive> CREATE DATABASE testdb;

存在するデータベースの一覧は、SHOW DATABASES 文で確認できます。
hive> SHOW DATABASES;
default
testdb
default というのは最初に作られるデータベースのようです。中には特にテーブルはありません。
とりあえずここでは自分で作ったデータベースを使っていくことにします。

データベースを作ったら、USE 文で利用するデータベースを選択しておきます。
hive> USE testdb;

HiveQL でテーブルを作成する

HiveQL で、データを格納するテーブルを作成してみます。

Hive の最も単純な利用法では、テーブルにはデータとして CSV ファイルや TSV ファイルを読み込ませます。
CSV ファイルの各行がテーブルの 1 レコードに相当し、1 レコード中のカラムは , で区切られています。このような CSV ファイルを Hive のテーブルに読み込ませる場合、以下のように TERMINATED BY 句に区切り文字の , を指定してテーブルを作成します。
hive> CREATE TABLE csv_lines (
  column1 STRING,
  column2 STRING,
  column3 STRING
) ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t';
TSV を読み込ませるテーブルでは , の代わりに \t を指定します。

ただし、今回は Apache のアクセスログをそのまま読み込ませたいので、ROW FORMAT に SERDE を指定して、テキストファイルの特定の文字列だけを正規表現で抜き出してみようと思います。
SERDE は Serializer/Deserializer を意味するもので、どうデータを変換するかは自分で実装することができます。ここでは組み込みの org.apache.hadoop.hive.contrib.serde2.RegexSerDe クラスを利用します。
hive> CREATE TABLE IF NOT EXISTS access_logs (
  ip STRING,
  access_date STRING,
  method STRING,
  path STRING,
  status_code STRING
)
ROW FORMAT SERDE 'org.apache.hadoop.hive.contrib.serde2.RegexSerDe'
WITH SERDEPROPERTIES (
  "input.regex" = "([0-9.]*) - - \\[([^\\]]*)\\] \"([^ ]*) ([^ ]*) [^\"]*\" ([0-9]*) .*",
  "output.format.string" = "%1$s %2$s %3$s %4$s %5$s"
);
SERDE に org.apache.hadoop.hive.contrib.serde2.RegexSerDe を利用する場合、抜き出した文字列は String 型のカラムにしか格納できない点に注意してください。

HiveQL でテーブルにデータを読み込ませる

作成した access_logs に、前回利用した 3 つのサンプルのアクセスログを読み込ませてみます。
ファイルからテーブルにデータを読み込むには、LOAD DATA INPATH 文を利用します。
LOAD DATA LOCAL INPATH "accesslog01.log" OVERWRITE INTO TABLE access_logs;
LOAD DATA LOCAL INPATH "accesslog02.log" INTO TABLE access_logs;
LOAD DATA LOCAL INPATH "accesslog03.log" INTO TABLE access_logs;
1 つめのファイルを読み込む時のみ OVERWRITE 句を付け足しています。これにより、読み込み時にそれまでテーブルにあったレコードは破棄されます。
逆に 2 つめ、3 つめのファイルを読み込む時には OVERWRITE 句を指定せずに、既存のレコードに追記する形としています。

Hive は SQL ライクな命令文で Hadoop を利用するための仕組みなので、読み込んだデータは HDFS 上に配置されます。
$ hdfs dfs -ls /user/hive/warehouse/testdb.db
$ hdfs dfs -ls /user/hive/warehouse/testdb.db/access_logs
Found 3 items
-rwxrwxr-x   1 hadoop hadoop        459 2017-04-04 18:21 /user/hive/warehouse/testdb.db/access_logs/accesslog01.log
-rwxrwxr-x   1 hadoop hadoop        459 2017-04-04 18:21 /user/hive/warehouse/testdb.db/access_logs/accesslog02.log
-rwxrwxr-x   1 hadoop hadoop        459 2017-04-04 18:21 /user/hive/warehouse/testdb.db/access_logs/accesslog03.log
hdfs コマンドからテーブルの配置されているディレクトリを確認することができますね。
これらのファイルを cat してみると、SERDE を指定したテーブルに読み込ませたファイルであっても、ファイルの中身は変換前のオリジナルのファイルのままとなっていることが確認できます。このあたりの挙動から、HDFS への操作だけを HiveQL で代替している様子が伺えます。
今回は SERDE を利用しましたが、確実に特定の部分データしか使わないことがわかっているのなら、HDFS 上にも加工後のテキストファイルを置いた方が I/O 面やストレージ量的に良さそうです。

LOAD DATA LOCAL INPATH 句では、ローカルマシンのファイルシステム上に存在するテキストファイルを読み込ませることができます。
読み込み元のファイルには HDFS 上に配置されているファイルを指定することもでき、その場合は LOCAL 句をつけずに LOAD DATA INPATH 文を利用します。ただし、HDFS から読み込む場合には読み込み元のファイルは削除されます。というより、厳密には HDFS 上の配置ディレクトリが上で触れたテーブル配下に移動される動作となるようです。

HiveQL でデータを集計する

最後に、HiveQL を使って HDFS 上のファイルに対して実際にデータ集計を行います。
HiveQL の恩恵により、データ集計は SQL ライクな SELECT 文で行うことができます。

テーブルの全カラム・全行を参照するなら、おなじみ SELECT * で OK です。
hive> SELECT * FROM access_logs;
OK
192.168.0.1	21/Mar/2017:10:09:18 +0900	GET	/book/1	200
192.168.0.2	21/Mar/2017:10:09:18 +0900	GET	/book/2	200
192.168.0.4	21/Mar/2017:10:09:18 +0900	GET	/book/3	200
192.168.0.1	21/Mar/2017:10:09:18 +0900	GET	/book/1	200
192.168.0.2	21/Mar/2017:10:09:18 +0900	GET	/book/2	200
192.168.0.5	21/Mar/2017:10:09:18 +0900	GET	/book/3	200
192.168.0.2	21/Mar/2017:10:09:18 +0900	GET	/book/1	200
192.168.0.3	21/Mar/2017:10:09:18 +0900	GET	/book/2	200
192.168.0.6	21/Mar/2017:10:09:18 +0900	GET	/book/3	200
Time taken: 0.046 seconds, Fetched: 9 row(s)
SERDE によって、意図した部分文字列を各カラムにマッピングできていることがわかります。

前回同様に IP アドレス毎にアクセス回数を集計してみます。
利用する HiveQL 文は SQL とまったく同じです。
hive> SELECT COUNT(ip), ip FROM access_logs GROUP BY ip;
WARNING: Hive-on-MR is deprecated in Hive 2 and may not be available in the future versions. Consider using a different execution engine (i.e. spark, tez) or using Hive 1.X releases.
Query ID = hadoop_20170404182214_9077a35a-b719-4d6c-bf8a-f267df819b04
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>
Job running in-process (local Hadoop)
2017-04-04 18:22:15,405 Stage-1 map = 100%,  reduce = 100%
Ended Job = job_local652532494_0002
MapReduce Jobs Launched: 
Stage-Stage-1:  HDFS Read: 31212 HDFS Write: 11934 SUCCESS
Total MapReduce CPU Time Spent: 0 msec
OK
2	192.168.0.1
3	192.168.0.2
1	192.168.0.3
1	192.168.0.4
1	192.168.0.5
1	192.168.0.6
Time taken: 1.316 seconds, Fetched: 6 row(s)
先ほどの単純な SELECT 文と違い、MapReduce Jobs が実行されているのがわかります。
分析結果として出力される内容は(ソートされていないことを除いて)前回と同じになっていますね。

おわり

以上、hadoop コマンドを利用した場合と比較しつつ、HiveQL でのデータ集計を試してみました。HDFS への泥臭いオペレーションを、Hive では慣れ親しんだ SQL ライクなインターフェースで実行できました。HiveQL を利用できる JDBC も公開されているようなので、JDBCConnection を切り替えることで Hadoop によるデータ分析を使えるのはなかなか大きなメリットになりそうです。Hive を利用しつつ、HDFS の中身を除いてみると、hadoop コマンドをラッピングしている様子が見えてくるのも面白いですね。

ただし、hive コマンドを使っていると度々出ているこの警告によると、Hive2 では HiveQL は @Deprecated なんですね……。
WARNING: Hive-on-MR is deprecated in Hive 2 and may not be available in the future versions. Consider using a different execution engine (i.e. spark, tez) or using Hive 1.X releases.
Hive 自体はそれなりに歴史のある技術で、現在ではいくつか弱点が指摘されているようです。
代わりに spark や tez を使ってねとのことですが、spark は話題の技術なのでまた折をみて触ってみようと思います。
  Hadoop  コメント (0)  2017/04/05 12:19:00


公開範囲:
プロフィール HN: ももかん
ゲーム作ったり雑談書いたり・・・していた時期が私にもありました。
カレンダー
<<2018, 7>>
1234567
891011121314
15161718192021
22232425262728
2930311234