たごもりすメモ

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

Hoop (HDFS over HTTP) を試してみた

バッチ処理にかける対象ファイルの抽出には中身を見る必要があって、headやtailでいいんだけどhadoop fsコマンドではそういうオペレーションができない*1ので FUSE hdfsLinuxHDFSをmountしてやっている。
が、これがまたビルドするのにひと苦労だったりmount元と先が密結合になっちゃったり遅かったりいつまでメンテされるもんかと思われたりするし外部コマンドを起動して結果を読んでゴニョゴニョしたりするのが面倒なのでどうにかしたいなーと思っていたら、Clouderaから Hoop なるプロダクトが少し前にリリースされた

Hoop - Hoop, Hadoop HDFS over HTTP - Documentation Sets 0.1.0-SNAPSHOT

HTTP REST APIを経由してHDFSの内容にアクセスできるし、オペレーションもできる。GETのところを見てみると offset と len を指定してファイルの内容を取得できるので、ファイル長の取得を組合せれば head も tail もやりたい放題。これだ!

試してみた

前のエントリ で作った環境のNameNode/JobTrackerのノードに同居させる形で Hoop Server を動かしてみようとインストールすることにした。組合せるソフトウェアは大雑把には CDH3u1 で、細かくは先述エントリ参照。
あとmavenが必要らしいので 3.0.2 のバイナリをダウンロードして展開しパスを通しておいた。

手順は Hoop - Hoop, Hadoop HDFS over HTTP 0.1.0-SNAPSHOT - Server Setup を読んで、このままやってみた。
ら、この通りにやってもうまくいかない。

Hadoopクラスタの設定変更

Hoopがアクセスする権限をあらかじめHadoopクラスタにおいて指定しておく必要がある。セットアップ手順における "Configure Hadoop" のところ。
これをHoopのビルド以前にやっておかないと、ビルド時に実行されるテストが通らない。

また hadoop.proxyuser.#HOOPUSER#.hosts に対応するvalueは何を指定するのかだけど、多分 Hoop Server を起動するサーバ名(かIP)を入れる。が、アスタリスクで全許可になるっぽいのでとりあえずそうしておいた。

  <property>
    <name>hadoop.proxyuser.tagomoris.hosts</name>
    <value>*</value>
  </property>
  <property>
    <name>hadoop.proxyuser.tagomoris.groups</name>
    <value>*</value>
  </property>

groupsの方はまだ意味がわかってないけど、まあ、いいや。(基本的にNW的にHadoopクラスタに接触されたらもう負けだと思っている。)

ビルド

Hadoopクラスタの設定変更を先にやってても "Build Hoop" の手順で実行されるテストは結局通らなかった。NullPointerException とか出てたからどうにも。
該当部分のコードを読む限り何かの properties ファイルが存在しなくてそれを読み込むコードがこけてるらしいけど、これバグだよなあ。

あと他にもテスト中で200になるはずのレスポンスが401だとか、ホスト名が 'localhost' になると思っていたら手元の環境だと 'localhost.localdomain' になったりとかつまんないコケかたしてた。めんどくさいなあ。テスト通すために hosts 書き換えるのもアホらしいし。

で、通らないものはしょうがないので、テストをスキップするようにしてビルドする。

$ mvn clean package site assembly:single -Dmaven.test.skip=true
Hoopの設定

"Configure Hoop" のところで conf/hoop-site.xml を編集しろとあるが、続けて書かれてる設定例が = で繋ぐ .properties ファイルのスタイルの記述でびびる。え、俺はなにをどう書けばいいの、という。
とりあえず conf を見てみたら hoop-site.xml というファイルはあったので、xmlスタイルで設定することにして、こう書いた。

<configuration>
  <property>
    <name>hoop.hadoop.conf:fs.default.name</name>
    <value>hdfs://tagomoris01.admin.xen:50071</value>
  </property>
</configuration>

プロパティ名にコロンが入ってるのに見慣れなくてびびるけど、うまく動く。設定のプロパティリストを見てもこう書いてあるから正しいんだろう多分。

ところで設定ファイルは実行バイナリとかとは別の場所に置きたいなあと思って bin/hoop.sh や bin/hoop-sys.sh を読んだところ $HOOP_CONFIG の内容が hoop.config.dir にセットされるらしいので export HOOP_CONFIG=/hoge/pos とかやっとけばよさそう。しかしHadoopにあわせて $HOOP_CONF_DIR とかにしといてほしかった気もする。

起動

ともあれ、上述の項目に注意しつつセットアップ手順をこなして bin/hoop.sh start すれば起動する。

試してみた

ドキュメントに書いてある操作は動くんだろうから、使いたい操作だけに絞ってさくっと試してみた。つまりそこそこの大きさのファイルの head と tail。
テスト用にクラスタに4つのログファイルをPUTしてそいつを相手にした。

  • (1) 1GB 非圧縮
  • (2) 5GB 非圧縮
  • (3) (2) をgzip圧縮
  • (4) (2) をbzip2圧縮

まずディレクトリ中のエントリのリスト。ファイル名をパターンで指定できたりもするらしいけど、とりあえずざらっと。メタデータを取得するAPIのレスポンスはJSONで返ってくる。(見易いように自分が手で整形した。)

$ curl -X GET 'http://localhost:14000/scribe/blogimg?user.name=tagomoris&op=list'
[
  {"path":"http:\/\/tagomoris01.admin.xen.livedoor:14000\/scribe\/blogimg\/blogimg-2011-09-07_00000",
   "isDir":false,
   "len":1000325471,
   "owner":"tagomoris","group":"supergroup","permission":"-rw-r--r--","accessTime":1315370544553,"modificationTime":1315370544553,
   "blockSize":268435456,"replication":3},
  {"path":"http:\/\/tagomoris01.admin.xen.livedoor:14000\/scribe\/blogimg\/blogimg-2011-09-07_00001",
   "isDir":false,
   "len":5001627355,
   "owner":"tagomoris","group":"supergroup","permission":"-rw-r--r--","accessTime":1315374391419,"modificationTime":1315374391419,
   "blockSize":268435456,"replication":3},
  {"path":"http:\/\/tagomoris01.admin.xen.livedoor:14000\/scribe\/blogimg\/blogimg-2011-09-07_00002.gz",
   "isDir":false,
   "len":826691340,
   "owner":"tagomoris","group":"supergroup","permission":"-rw-r--r--","accessTime":1315374525129,"modificationTime":1315374525129,
   "blockSize":268435456,"replication":3},
  {"path":"http:\/\/tagomoris01.admin.xen.livedoor:14000\/scribe\/blogimg\/blogimg-2011-09-07_00003.bz2",
   "isDir":false,
   "len":454923674,
   "owner":"tagomoris","group":"supergroup","permission":"-rw-r--r--","accessTime":1315374548076,"modificationTime":1315374548076,
   "blockSize":268435456,"replication":3}
]

ファイル単体を対象にステータスを取ることもできる。が、内容はディレクトリ内のエントリリストをとったときと同じだった。あんまし意味ない。

とりあえずファイル情報とる
$ curl -X GET 'http://localhost:14000/scribe/blogimg/blogimg-2011-09-07_00000?user.name=tagomoris&op=status'
{"path":"http:\/\/tagomoris01.admin.xen.livedoor:14000\/scribe\/blogimg\/blogimg-2011-09-07_00000",
 "isDir":false,
 "len":1000325471,
 "owner":"tagomoris","group":"supergroup","permission":"-rw-r--r--","accessTime":1315370544553,"modificationTime":1315370544553,
 "blockSize":268435456,"replication":3}
$ curl -X GET 'http://localhost:14000/scribe/blogimg/blogimg-2011-09-07_00001?user.name=tagomoris&op=status'
{"path":"http:\/\/tagomoris01.admin.xen.livedoor:14000\/scribe\/blogimg\/blogimg-2011-09-07_00001",
 "isDir":false,
 "len":5001627355,
 "owner":"tagomoris","group":"supergroup","permission":"-rw-r--r--","accessTime":1315374391419,"modificationTime":1315374391419,
 "blockSize":268435456,
 "replication":3}

head がわりに (1) のファイルについて先頭から200バイト取得してみる。1行目が取れたあと2行目の先頭5文字を読んで切れてる。200バイトかちゃんと数えてないけど、まあ多分そうなんでしょう。特定の行数が欲しければ、それが充分含まれる程度に大きいバイト数を読んで、いらない部分は読み捨てるとかになるだろう。offset=-1 でファイル先頭を示すらしい(offset=0と何か違うんだろうか……)が、省略したら多分デフォルトで先頭になるでしょう。

$ curl -X GET 'http://localhost:14000/scribe/blogimg/blogimg-2011-09-07_00000?user.name=tagomoris&offset=-1&len=200'
nn.nnn.nnn.nn - - [06/Sep/2011:23:59:08 +0900] "GET /xxxxxxxxxx/imgs/b/2/b2b3c48e.jpg HTTP/1.1" 302 161 "http://xxxxxxxx.jp/archives/yyykkkww.html" "-" "xxx.xxx.xxx.xx" livedoor.blogimg.jp 0.000
nnn.n

こっちはtailがわり。offset にはファイル長から len(取得したいバイト数) を引いた数値を指定すればいい。lenは省略すれば末尾まで読んでくれそうだけど。どうでもいいがREST APIの 'Read File' のところにある offset / len の -1 の説明は逆ですよね。

 curl -X GET 'http://localhost:14000/scribe/blogimg/blogimg-2011-09-07_00000?user.name=tagomoris&offset=1000325271&len=200'
 - [07/Sep/2011:00:01:11 +0900] "GET /wwwwwwwwww/imgs/0/f/0f8a7b99.jpg HTTP/1.1" 302 161 "http://xxxxxxxx.com/list/id/id/2956/?hash=0000000000aaaa0000a000a0aa00a000" "-" "-" livedoor.blogimg.jp 0.000

5GB(4GB超)のファイルで試したけど全く問題なかった。
またわずかな期待にかけて gzip および bzip2 圧縮済ファイルに対して head / tail してみたが、当然バイナリがそのまま出てきた(ターミナルの表示がぶっこわれた)。これはまあ、しょうがあるまい。でもこれ、どうにか方法を考えないとなあ。

まとめ

とりあえず使えそう。FUSE hdfs は一刻も早く捨てたかったので、次にセットアップするクラスタでは使うこと前提で考えようと思っている。
また Hadoop 0.23.0 ではHadoop本体に取り込まれるようなので、今から使っていて将来的にハシゴを外されるということもあるまい。よかったよかった。

*1:-tail というオプションはあるけどバイト数とか指定できないし、headがない