たごもりすメモ

コードとかその他の話とか。

Hive UDFを自分で追加するときの注意点メモ

メモ。CDH3u2 (hive-0.7.1-cdh3u2) での話。

(1/23 HiveServerについていくつか追記した)

Hiveで自分でつくった関数(User Defined Function: UDF)を使いたい! と思い艱難辛苦を乗り越えJavaのコードを書きjarにまとめたとする。書くまでの話はWikiの該当ページなどを熟読するのがよろしい。

で、じゃあどうやってHive起動時に読み込めばいいの、という話。

add jarコマンド

hiveコマンドを起動するマシンの適当なディレクトリにjarファイルを置き、そのディレクトリをカレントディレクトリとしてhiveを起動して以下のコマンドを実行する。

hive> add jar udfclass.jar;
hive> create temporary function myfunc as 'my.package.udf.Class';
hive> select myfunc(col1) from x_table where ...

これが存外うまくいくのでヨッシャヨッシャと思う。罠である。

(1/23追記) このjarのパスはhiveを実行しているマシン上のローカルファイルシステムのパスらしい。 /home/username/tmp/hoge.jar みたいに書けば読み込める。HiveServerに接続して実行するときはHiveServerを実行しているマシン上のjarの絶対パスで指定するのがよい。

hive-default.xml の hive.aux.jars.path を設定する

カレントディレクトリがどうとかに依存していると運用がうまくいくわけがないので、特定の場所にjarファイルを置いてそこを見るように設定したい。どうするか。これもWikiから設定項目リストを眺めると、ぴったりなもの hive.aux.jars.path というのがあるので、これを使えばいい、らしい。
とはいえ「パスを書け」としか書かれてなくてなにを書けばいいのかよくわからん。どうすればいいのかは機能追加時のチケットにあった。

  • ローカルファイルシステムを指定したい場合は file:///home/username/tmp/lib みたいに指定する(jarのあるディレクトリを指定)
  • HDFS上に置いたjarを指定したい場合は /hdfs/dir/path/tmp/lib のように書く

普通に / から始めるとHDFS上のパスを指定したことになるので注意。ディレクトリを指定することにも注意。末尾の / はあってもなくても大丈夫っぽい。

ということで、以下のように書く。

<!-- <configuration> ... </configuration> 内だよ -->
<property>
  <name>hive.aux.jars.path</name>
  <value>file:///home/username/tmp/lib</value>
</property>

その上で create temporary function などを実行。

hhive> create temporary function myfunc as 'my.package.udf.Class';
hive> select myfunc(col1) from x_table where ...

これでmapreduceが起動して万歳! だったらよかったんだけど。 うまくいかなかった。以下実例。

hive> create temporary function parse_agent as 'is.tagomor.woothee.hive.ParseAgent';                       
OK
Time taken: 0.25 seconds
hive> select parse_agent(agent),agent from access_log where service='top' and yyyymmdd='20111201' limit 30;
Total MapReduce jobs = 1
Launching Job 1 out of 1
Number of reduce tasks is set to 0 since there's no reduce operator
Starting Job = job_201110281852_0528, Tracking URL = http://namenode.analysis.livedoor:50030/jobdetails.jsp?jobid=job_201110281852_0528
Kill Command = /usr/local/hadoop-0.20.2-cdh3u2/bin/hadoop job  -Dmapred.job.tracker=master101.analysis.livedoor:50031 -kill job_201110281852_0528
2012-01-19 19:45:50,204 Stage-1 map = 0%,  reduce = 0%
2012-01-19 19:46:08,302 Stage-1 map = 100%,  reduce = 100%
Ended Job = job_201110281852_0528 with errors
java.lang.RuntimeException: Error while reading from task log url
        at org.apache.hadoop.hive.ql.exec.errors.TaskLogProcessor.getErrors(TaskLogProcessor.java:130)
        at org.apache.hadoop.hive.ql.exec.ExecDriver.showJobFailDebugInfo(ExecDriver.java:903)
        at org.apache.hadoop.hive.ql.exec.ExecDriver.execute(ExecDriver.java:694)
        at org.apache.hadoop.hive.ql.exec.MapRedTask.execute(MapRedTask.java:123)
        at org.apache.hadoop.hive.ql.exec.Task.executeTask(Task.java:130)
        at org.apache.hadoop.hive.ql.exec.TaskRunner.runSequential(TaskRunner.java:57)
        at org.apache.hadoop.hive.ql.exec.TaskRunner.run(TaskRunner.java:47)
Caused by: java.io.IOException: Server returned HTTP response code: 400 for URL: http://10.xxx.xx.xxx:50060/tasklog?taskid=attempt_201110281852_0528_m_000008_0&all=true
        at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1436)
        at java.net.URL.openStream(URL.java:1010)
        at org.apache.hadoop.hive.ql.exec.errors.TaskLogProcessor.getErrors(TaskLogProcessor.java:120)
        ... 6 more
Ended Job = job_201110281852_0528 with exception 'java.lang.RuntimeException(Error while reading from task log url)'
FAILED: Execution Error, return code 1 from org.apache.hadoop.hive.ql.exec.MapRedTask

なんかこんなエラーになって全く上手く動かない。どうやってもダメ。jarをlocal filesystemに置いてもhdfsに置いてもダメ。もうね……。

環境変数 HIVE_AUX_JARS_PATH をセットする

jarのパスはもうひとつセットする方法があって、環境変数で指定する。これは local filesystem のみ指定できる。

export HIVE_AUX_JARS_PATH=/home/username/tmp/lib
hive

その上でクエリを実行。

hive> create temporary function parse_agent as 'is.tagomor.woothee.hive.ParseAgent';
OK
Time taken: 0.26 seconds
hive> select parse_agent(agent),agent from access_log where service='top' and yyyymmdd='20111201' limit 3;
Total MapReduce jobs = 1
Launching Job 1 out of 1
Number of reduce tasks is set to 0 since there's no reduce operator
Starting Job = job_201110281852_0529, Tracking URL = http://namenode.analysis:50030/jobdetails.jsp?jobid=job_2
01110281852_0529
Kill Command = /usr/local/hadoop-0.20.2-cdh3u2/bin/hadoop job  -Dmapred.job.tracker=namenode.analysis:50031 -k
ill job_201110281852_0529
2012-01-19 19:53:33,768 Stage-1 map = 0%,  reduce = 0%
2012-01-19 19:53:34,779 Stage-1 map = 56%,  reduce = 0%
2012-01-19 19:53:35,786 Stage-1 map = 100%,  reduce = 0%
2012-01-19 19:53:36,794 Stage-1 map = 100%,  reduce = 100%
Ended Job = job_201110281852_0529
OK
DATADATADATADATADATADATADATADATADATADATADATADATADATADATADATADATADATADATADATADATADATA
DATADATADATADATADATADATADATADATADATADATADATADATADATADATADATADATADATADATADATADATADATA
DATADATADATADATADATADATADATADATADATADATADATADATADATADATADATADATADATADATADATADATADATA

できたできた!!!!

create temporary function を自動化したい

毎回 create temporary function を打つのは嫌だし HiveServer 使う場合どうするんだよというのもあるので、こいつをhive起動時に自動的に実行するようにしたい。
hiveコマンドにはオプション -i filename というのがあって、これを指定すると filename の中に書いたクエリを自動的に実行してくれるらしい。

ということで、やってみた。

$ cat hive/hiverc.hql 
create temporary function parse_agent as 'is.tagomor.woothee.hive.ParseAgent'
$ hive -i hive/hiverc.hql 
Hive history file=/tmp/edge-dev/hive_job_log_edge-dev_201201211526_471516605.txt
hive>

できたできた! 起動したhive CLIで普通に指定の関数が使えたよ! *1

(1/23追記) この -i オプション(やその他 hive コマンドのオプションとして一般に見られているもの)は --service hiveserver を指定したときには使えない。注意。

auxpathオプション (1/23追記)

jarを読み込むディレクトリの指定方法がもうひとつあった。HiveServerまわりのためhiveコマンドのコード読んでたら見付けた。

$ hive --auxpath /path/to/lib/dir # CLI
$ hive --auxpath /path/to/lib/dir --service hiveserver # HiveServer

これを指定すると HIVE_AUX_JARS_PATH にセットするのと同じ効果が得られる。

まとめ

Hive UDF用のjarを追加する方法はいくつかあるけど、運用を考えると環境変数 HIVE_AUX_JARS_PATH をセットしておくのがよいでしょう。動くし。
create temporary function はめんどくさいので初期化用クエリファイルを用意しといて hive -i で読み込むとよいでしょう。以下のようにしてもいいレベル。

alias hive="hive -i hive/hiverc.hql"

わーい、いろいろ楽になったぞー!

HiveServerの場合 (1/23追記)

HiveServerを使う場合はちょっと事情が異なる。まず -i オプションが使えない(起動時にセットアップクエリを走らせることができない)ので、外部から接続したときに初期化処理としてこれらのクエリを実行してやる必要がある。
また create function の結果はそのセッション内でしか有効ではないので、繋ぎなおすたびに初期化処理は実行する必要がある。jarの読み込みについても同じ。

jarの読み込み元(AUX_JARS_PATH)についてはHiveServerの起動時に環境変数か --auxpath で指定する。*2
ただしこれらを指定したHiveServerに対しても jar ファイルの読み込みについては、どうせ add jar しないといけないっぽいようだ。しないと失敗する。起動時のコマンドでは読み込んでいるようなんだけど……Javaのコードまで追ってないのでよくわからん。

ということで結局 add jar を実行することになるので、もうあれこれ考えるのも面倒くさいし、自分は HiveServer 上の絶対パスで指定することにした。

Hive CLIをHiveServerに接続して使う場合

普通に add jar と create temporary function してからクエリを実行すればいい。

$ hive -h localhost -p 10000
[localhost:10000] hive> add jar /path/of/hiveserver/localfs/lib/hoge.jar;
[localhost:10000] hive> create temporary function hoge as 'package.of.hoge.Hoge';
[localhost:10000] hive> select hoge(col1) from ...;

そういえば hive cli のHiveServerに接続する機能がCDH3u2使ってみたらいつの間にか取り込まれてましたね。ぐっど。

HiveServerにThrift接続して使う場合

thrift client でHiveServerに接続したら、その接続上で execute() を何回か発行して add jar や create temporary function を実行する。
そのとき以下の点に注意する。

  • add jar の引数の与えかた(jarのパス指定)は Hive CLI 経由でHiveServerに繋ぐ場合と同じ、つまりHiveServer上のローカルパスで指定する
  • add jar / create function はそれぞれ一度のexecute()に1行ずつ実行する
    • セミコロンで繋げてえいやと行けないかと思ったけど無理だった、エラーになる
    • というかセミコロンあるとエラーになるので注意な

まあだいぶめんどくさい、けど、コードからクエリが実行される分には、実行対象クエリの前に client オブジェクトをセットアップする(初期化用クエリを実行したあとのclientインスタンスを渡してくれる)メソッドをひとつ書けばいいので、まあ許容範囲内だった。

ということでだいたい解決しました!

*1:当初うまくいかないと書いておりましたがクラス名をtypoっておりまして大変申し訳ございませんでした。

*2:hive.aux.jars.pathはやっぱり動かなかった。原因がわからん。