Home > develop > hadoop Archive

hadoop Archive

Hive QL(HQL)でORDER BYするときの注意点

  • 2009年10月14日 23:57
  • hadoop

HiveでのSQLことHQLの小ネタ。HQLでは基本的にSQLはほぼ完璧に利用できますが、たまにハマりポイントもあります。その1つが並び替えのORDER BY。

ORDER BYとSORT BY

HQLの文法的にORDER BYは有効ですが、実際は並び替えは行われません。(無視されているような感じ)Hiveでは代わりに「SORT BY [column]」を利用することになります。

ただし、ここでも罠があって、SORT BYは結果がreducerの数に依存します。(各reducerがsort処理をしたものがマージされるものになるので、全体としてはおかしな結果を得ることになります) 通常、reducerは複数走っているはずなので、結局SORT BYを利用してもORDER BYと同等の結果を得ることができません。

では、どうするか?と言うと明示的にreducerの数を1に指定してからSORT BYを実行すればOKです。

set mapred.reduce.tasks=1;
SELECT key, value FROM table_name SORT BY key;

また、Hiveシェルを使わない場合は1ライナーで。

hive -e 'set mapred.reduce.tasks=1;SELECT key, value FROM table_name SORT BY key;'

HQL最適化

ただ、これだとreducerの数がボトルネックになって、SORT BYと条件文などが組合わさると途端に処理が遅くなることがあります。

なので、このような条件文付きSORT BYを実行するときは、

  1. 複数のreducerが条件文つきHQLでデータを整形し、中間テーブルAを作成
  2. 1つのreducerが、中間テーブルAを対象にSORT BYし、条件文は付けない

な、方針でHQLを分割して実行すると、高速に動作できるかと思います。

ちなみに、このテーブルを分ける(カラムを厳選した中間テーブルを作成する)のはHQLの最適化でかなり有効で、SORT BY以外でもかなり有効なケースが多くあります。このあたりの最適化の話は、また別途まとめたいと思います。

HiveのmetastoreをMySQLを使ってLocal Metastore形式で利用する

  • 2009年10月12日 00:56
  • hadoop

前回、紹介したHiveについての続き。

Hiveは内部で扱うメタデータを「metastore」というデータで保持しています。テーブルやパーティションなどの情報、またレコードが実際に保持されてある場所などのメタデータは全部このmetastoreにまとまっています。このmetastoreは、次の3種類の方法で保存することができます。

  • Embeded metastore
  • Local Metastore
  • Remote Metastore

Embeded metastore

Embeded metastoreは主にテスト用途に利用されます。テスト用途なので、単一プロセスからの接続しか許可されていません。 そのため、コンソールを複数起動して、それぞれのコンソールから別のMap&Reduceを走らせる...なんてことができません。ただし、Hiveは初期設定がこのEmbededモードになっているので、特に設定することなくすぐに利用するメリットはあります。

また、metastoreはmetastoreディレクトリ(hive-default.xmlのhive.metastore.urisセクションに記述されています)内の 単一のファイルに保存されることになります。メタデータのバックアップをとりたいときは、このファイルをバックアップ対象にすればOKです。

Remote Metastore

1つ飛ばして、Remote Metastoreについて。Remote Metastoreはネットワーク越しにmetastoreを扱いたい場合における設定方法です。Hiveクライアントとデータを保存するMySQLの間にThriftサーバをproxyのような形で挟むことで実現しています。Thriftプロトコルを実現できていれば通信が可能なので、実際はJavaのHiveクライアントだけではなく、異なる言語によるクライアントからも接続が可能です。たとえばPHPでの例があるみたいです。

Local Metastore

Local Metastoreは、上で述べた2つの形式の間の位置づけのものです。 metastoreを単一ファイルで扱わずに、Remote Metastoreの形式にようにMySQLに保存することで、同一ホスト内から同時に複数のセッションを張ることができます。 このLocal Metastoreは簡単に実現できる割にすごく便利なので、紹介しておきます。

DBの設定

まず、MySQLでmetastoreを保存する適当なDB、およびその接続ユーザを定義します。たとえばrootで接続し、次のように定義します。

create database hive;
grant all privileges on hive.* to user_hive@localhost identified by 'hive_password';
flush privileges;

この設定をHiveに反映させるために設定用xmlを編集します。

hive-site.xmlの編集

Hiveの初期設定はhive-default.xmlにまとまっていますが、この設定はhive-site.xmlという名前のファイルを同一ディレクトリに用意してあげることで上書きできます。 上記設定は次の項目を編集、または追加してあげることで反映されます。

<property>
  <name>hive.metastore.local</name>
  <value>true</value>
  <description>controls whether to connect to remove metastore server or open a new metastore server in Hive Client JVM</description>
</property>

<property>
  <name>javax.jdo.option.ConnectionURL</name>
  <value>jdbc:mysql://localhost/hive?createDatabaseIfNotExist=true</value>
  <description>JDBC connect string for a JDBC metastore</description>
</property>

<property>
  <name>javax.jdo.option.ConnectionDriverName</name>
  <value>com.mysql.jdbc.Driver</value>
  <description>Driver class name for a JDBC metastore</description>
</property>

<property>
  <name>javax.jdo.option.ConnectionUserName</name>
  <value>hive_user</value>
  <description>metastore mysql user</description>
</property>

<property>
  <name>javax.jdo.option.ConnectionPassword</name>
  <value>hive_password</value>
  <description>metastore mysql password</description>
</property>

<property>
  <name>hive.metastore.warehouse.dir</name>
  <value>/user/hive/warehouse</value>
  <description>location of default database for the warehouse</description>
</property>

どのプロパティ名も名前からどんなものかは凡そ予想はつくと思います。 ちなみにhive.metastore.warehouse.dirはHDFS上でのファイルパスになるので、ここだけ要注意です。

xmlを編集したら、hiveクライアントのシェルを立ち上げ直します。 シェル自体はEmbeded Metastoreのときとメッセージ形式も変わらないですが、同時に複数のHQLを実行することができるので、格段に使いやすくなります。 Hiveの利用を考える場合は、Local Metastoreの利用は検討してみる価値はあるのかな、と思います。

まとめ

Hiveのmetastoreの保存形式について、また、Local Metastoreの形式について、その設定方法をまとめてみました。これらの保存方式については、公式サイトにもまとまっている資料があるので、一度目を通してみるとコンポーネント間の関係が理解しやすいかと思います。

SQL感覚でMap Reduce処理できるHiveについて

  • 2009年9月 7日 00:44
  • hadoop

Hive

前回、JavaScriptでMap Reduceのコードが書けるHadoop Streamingについて紹介しました。 標準入出力さえサポートされてあれば、任意のコードでMap Reduuceの処理が書ける、というものでしたが、エンジニアはそもそも面倒くさがり。コードも書くのも面倒です。 と、いうわけで、今回はもうコードすら書かずにSQLライクでMap ReduceできるHiveというプロダクトについて、まとめたいと思います。

Hive

Hiveとは、簡単に言うとHadoop上で動作するRDBのようなものです。 HDFSなどの分散ファイルシステム上に存在するデータに対して、HiveQLというSQLライクな言語で操作できます。 で、面白いのがHiveQLの操作は基本的にMap Reduceのラッパーになっていること。 要するに、SELECT文実行すると裏でMap&Reduceのタスクが走り出して、分散処理されて結果を得ることができます。

そんなHiveは、もともとFacebookが開発を始めたものでした。2008年12月に、正式にHadoopプロジェクトにcontributeされて、いまはHiveの公式サイトもHadoopのサイト内でホスティングされてあります。

では、さっそく導入方法について確認してみます。

ビルド

Hiveのビルドに先立って、Hadoopをインストールしておきます。Hadoopのインストール方法は前回のエントリにも書いてある通りです。

Hiveのビルド自体は、Subevrsionからcheckoutして、antのタスクでビルドできる簡単なものです。1ヶ月ほど前のHiveは、patchを当てないとHadoop 0.20.0用にビルドできなかったのですが、最近のtrunkのものは特に何もせずにビルドできるようです。

svn co http://svn.apache.org/repos/asf/hadoop/hive/trunk hive
cd hive
ant -Dhadoop.version="0.20.0" package

ビルドできたらbuild/distを環境変数$HIVE_HOMEとでも設定しておきます。また、あわせて$HADOOP_HOMEも設定しておきましょう。

セットアップ

ビルドが成功したら、Hive用のデータ保存領域のセットアップをHDFS上で行います。Hadoopを起動した状態で次のようにセットアップします。

$HADOOP_HOME/bin/hadoop fs -mkdir       /tmp
$HADOOP_HOME/bin/hadoop fs -mkdir       /user/hive/warehouse
$HADOOP_HOME/bin/hadoop fs -chmod g+w   /tmp
$HADOOP_HOME/bin/hadoop fs -chmod g+w   /user/hive/warehouse

すると、次のコマンドでHiveが起動されます。

$HIVE_HOME/bin/hive

テーブルの作成

では、早速Hiveで扱うテーブルを作成します。 テーブルの作成方法はMySQLなんかの一般的なRDBMSのそれとほぼ同等です。 たとえば、ユーザのアクセスログをまとめたログがあると仮定します。このログを保存するテーブルは最低限、ユーザIDとそのアクセス時間が保存されればOKなので、次のように定義できます。

CREATE TABLE user_logs(id INT, created_at STRING);

構文については見たまんまなので。理解しやすいかと思います。ちなみに、昔のバージョンだとカラムにDATETIMEなんかの型が定義できたようですが、現在はDATETIME型はなくなっているようです。(Hive/LanguageManual/DDL

また、CSVなどの形式ですでに既存のログファイルがある場合、便利な機能があります。 Hiveではテーブル定義時に(CSVなどの)ある形式のデータを扱うことをあらかじめ指定しておくと、 CSV形式のログデータをそっくりそのままHiveのテーブルのレコードとしてロードさせることが可能です。

たとえばCSV形式のファイルを扱う場合、このような定義方法になります。

CREATE TABLE user_logs(id INT, created_at STRING) row format delimited fields terminated by ',' lines terminated by '\n';

この上で、次のようなコマンドで既存のログデータをロードできます。

LOAD DATA LOCAL INPATH '/path/to/log_20090907.csv' OVERWRITE INTO TABLE user_logs;

これで、CSVデータをHiveQLで扱えるようになりました。この時点でHDFS上にlog_20090907.csvは保存されています。/user/hive/warehouse/以下にテーブル名である「user_logs」ディレクトリが作成されてあり、該当ファイルが保存されてあります。

$ $HADOOP_HOME/bin/hadoop fs -ls /user/hive/warehouse/user_logs/
Found 1 items
-rw-r--r--   1 katsuma supergroup   17800731 2009-09-06 11:01 /user/hive/warehouse/user_logs/20090907.csv

では、ここで早速HiveQLを実行してみましょう。全件取得するときは一般的なSELECT文が利用できます。

SELECT * from user_logs;

ただし、このままだとローカルからデータをロードするだけで、ただのCSVのSQLラッパーでしかありません。 HiveQLが真価を発揮するのはいろいろ条件付けたSQLを発行してから。

ユニークユーザ数をHiveQLで算出

ここで、ユニークユーザ数(UU)を算出してみます。普通のMap Reduceの処理だと、IDごとのHashMapを作成してそのサイズからUUを算出することになりますが、HiveはSQLライクに算出することができます。つまり、こういうかんじ。

select count(distinct id) from user_logs;

すると、いきなりここからMap&Reduce処理が走り出します。 この、「SQL実行でMap&Reduceが実行される」という様子があまりにすごいので、最初これ見たときはかなりウケました。

Total MapReduce jobs = 1
Number of reduce tasks determined at compile time: 1
In order to change the average load for a reducer (in bytes):
  set hive.exec.reducers.bytes.per.reducer=
In order to limit the maximum number of reducers:
  set hive.exec.reducers.max=
In order to set a constant number of reducers:
  set mapred.reduce.tasks=
Starting Job = job_200909061015_0001, Tracking URL = http://hdp-01:50030/jobdetails.jsp?jobid=job_200909061015_0001
Kill Command = /home/katsuma/hadoop/latest/bin/../bin/hadoop job  -Dmapred.job.tracker=hdp-01:9001 -kill job_200909061015_0001
2009-09-06 10:41:27,049 map = 0%,  reduce = 0%
2009-09-06 10:42:27,520 map = 5%,  reduce = 0%
2009-09-06 10:42:28,540 map = 50%,  reduce = 0%
2009-09-06 10:42:33,594 map = 69%,  reduce = 0%
2009-09-06 10:42:34,610 map = 100%,  reduce = 0%
2009-09-06 10:42:55,748 map = 100%,  reduce = 100%
Ended Job = job_200909061015_0001
OK
21509
Time taken: 105.58 seconds

すると、結果が取得できました。上記例だと21509ユーザという結果を取得できていますね。あまりに簡単すぎてウケます。

ちなみにHiveのコンソールはサイレントモードが用意されてあって、Hiveのコンソールを起動させることなく、普段のシェルからも実行させることができます。サイレンとモードは-Sオプションをつけて実行すればよく、

$HIVE_HOME/bin/hive -S -e 'select count(distinct id) from user_logs'

と、すれば、途中経過をすっとばして結果だけ取得できます。もちろんこれはバッチ処理など、Hiveコンソールを利用せずにいろんな値を取得させたいときに利用すればよさそうですね。

Partitionを設定

Hiveの特徴的な機能としてPartitionというものがあります。 これはテーブル自体にバージョン管理の概念を持たせるもので、ログ管理にすごく便利そうなものです。

たとえばデイリーのログファイルをHiveで管理したいと思ったときに、 毎日HDFS上にログファイルを転送することになるかと思いますが、Partitionを利用することで、 ログを追記でテーブルに書き込む(=ファイルにappendする)のではなく、 日付ごとに独立したファイルのままHDFSに保存することができます。 なので、「やっぱりHiveより素のMqp&Reduce書くようなフローに戻そう」と 思ったときに、Partitionを利用してロードしておいたほうが後々都合がよいかと思います。(あと、初期のHadoopはファイルのappendができない、という制約があったので、Hive側はこの制約を回避するために、このような概念を持たせて独立したファイルを透過的に見せるような仕様で実装したんじゃないかな?と推測しています)

Partition自身は、ふだんのテーブルにおけるカラムのように扱えるので、実際は特別に何か意識する必要はありません。Partition付きテーブルはこのように定義できます。

CREATE TABLE user_logs(id INT, created_at STRING) partitioned by(dt STRING) row format delimited fields terminated by ',' lines terminated by '\n';

この場合だと、dailyの情報をpartitionに持たせるために「dt」という名前のpartitionに設定させています。その上で、このテーブルにデータをロードする場合は次のようになります。

LOAD DATA LOCAL INPATH '/path/to/20090907.csv' OVERWRITE INTO TABLE user_logs partition (dt='20090907');
LOAD DATA LOCAL INPATH '/path/to/20090908.csv' OVERWRITE INTO TABLE user_logs partition (dt='20090908');

すると、HDFS上ではバージョン情報をもったまま保存されていることが確認できます。

$ $HADOOP_HOME/bin/hadoop fs -ls /user/hive/warehouse/user_logs/
Found 2 items
drwxr-xr-x   - katsuma supergroup          0 2009-09-06 11:18 /user/hive/warehouse/user_logs/dt=20090907
drwxr-xr-x   - katsuma supergroup          0 2009-09-06 11:17 /user/hive/warehouse/user_logs/dt=20090908

これらのPartitionの情報は、もちろんHiveQL上で扱うことができて、たとえば上記例だと9月8日のUUを算出したい場合は

select count(distinct id) from user_logs where dt='20090908';

で、算出できます。また、9月7日以降のUUを算出したい場合だと

select count(distinct id) from user_logs where dt>='20090907';

な、風に書けます。直感的で便利ですよね。(この比較式はたぶん、JavaのString#compareToに従ってるんじゃないか、と推測)

まとめ

Hiveはまだ立ち上がったばっかりの若いプロジェクトですが、基本的なログ解析をするくらいの分には、かなり使えそうな印象です。上記例のように、基本的に元ファイルはそのままHDFS上に保存されているので、Hiveの不具合で何かおきた場合も自前のMap&Reduce処理に戻すことができるのも安心ですね。

今回は、あまり細かな説明までしませんでしたが、HiveQLはJoinやUnionなど割と複雑なテーブル間処理も行うことができます。このあたりも興味ある方は一度、オフィシャルのマニュアルに目を通してみてはいかがでしょうか?

Index of all entries

Home > develop > hadoop Archive

Search
Feeds

Return to page top