Hoopの性能を確認してみたらもうlibhdfsとかオワコンでHoop使えって結果になった
前に書いた エントリ の通りHoopが有望な感じだったんだけどどのくらいの性能が出るのか見てみないことには本番投入して性能出ませんでした乙、ということになりかねない。ので見てみた。
なお検証に関係する環境としては以下の通り。ちなみに前はCDH3u1で試してたけど、今回はCDH3u2 (JDK6u29) on CentOS5。メモリが問題になることは全くないので全て省略。
- ベンチ用サーバ
- データ中継サーバ (deliver)
- Hadoop NameNode (+JobTracker) (namenode)
- Hadoop DataNode + TaskTracker x9
Hoopを起動したのはデータ中継サーバとNameNodeの2ヶ所で、それぞれに対して試してみた。上記サーバはすべて別のラックにありGbEで接続されている。Hadoopクラスタの各ノードはNameNodeと同一および隣のラックに配置。
なお データ中継サーバ(deliver)はこのテスト中も scribed が走っていて、やってくるログをhdfs(scribed with libhdfs)に書き込んでいる。が、scribedによるCPU負荷やネットワーク使用率が問題になることはあるまいということで、そのまま実施した。(実際に、多分問題なかった。)
書き込み/読み込みのスループット
とりあえずナイーブな書き込みおよび読み込みについて、hadoop fsコマンドと較べてどのくらいの数字差が出るか。以下の条件でやってみた。
- 2GBのファイルのPUTを以下の順序で(最初にそのまま実行、次に time コマンドをかませて計測)
- 2GBのファイルのGETを同じように実行
結果は以下のとおり。まずPUT。
hadoop fs -put 2gdata.txt /tmp/put1.txt ;\ time hadoop fs -put 2gdata.txt /tmp/put2.txt ;\ curl -X POST 'http://namenode:14000/tmp/put3.txt?op=create&user.name=hoge' --data-binary @2gdata.txt --header "content-type: application/octet-stream" ;\ time curl -X POST 'http://namenode:14000/tmp/put4.txt?op=create&user.name=hoge' --data-binary @2gdata.txt --header "content-type: application/octet-stream" ;\ curl -X POST 'http://deliver:14000/tmp/put5.txt?op=create&user.name=hoge' --data-binary @2gdata.txt --header "content-type: application/octet-stream" ;\ time curl -X POST 'http://deliver:14000/tmp/put6.txt?op=create&user.name=hoge' --data-binary @2gdata.txt --header "content-type: application/octet-stream" # hadoop fs -put real 1m8.564s user 0m8.736s sys 0m1.635s # curl - Hoop(namenode) real 1m2.889s user 0m0.830s sys 0m1.676s # curl - Hoop(deliver) real 1m2.837s user 0m0.811s sys 0m1.532s
どれも対して変わらない、っていうか hadoop fs コマンドが一番遅いって……。あとから考えたがJVMの起動コスト分ですかねえ。user 8.736s の大部分がそれかな。2GB/60secだとビットレートにして266Mbps。わお。
で、つぎはGET。
hadoop fs -get /tmp/put1.txt - > /dev/null ;\ time hadoop fs -get /tmp/put1.txt - > /dev/null ;\ curl -X GET -s 'http://namenode:14000/tmp/put3.txt?user.name=hoge' > /dev/null ;\ time curl -X GET -s 'http://namenode:14000/tmp/put3.txt?user.name=hoge' > /dev/null ;\ curl -X GET -s 'http://deliver:14000/tmp/put5.txt?user.name=hoge' > /dev/null ;\ time curl -X GET -s 'http://deliver:14000/tmp/put5.txt?user.name=hoge' > /dev/null # hadoop fs -get real 0m31.224s user 0m12.519s sys 0m4.926s # curl - Hoop(namenode) real 0m49.797s user 0m2.741s sys 0m4.727s # curl - Hoop(deliver) real 0m30.389s user 0m1.354s sys 0m2.404s
これもdeliver経由だとhadoop fs -getより速くて、やっぱりJVM起動コストですかね。2GB/30secだと532Mbps? わーお! namenodeはCPU弱いサーバなので、たぶんそこで差が出ちゃったかな。本当は同一スペックのサーバで試せればどこにHoopを置くか決まって良かったかもしれないけど、でもまあ49秒で終わる程度にパフォーマンス出るなら実質充分。
継続的な追記のスループット
とはいえ単発のGET/PUTの性能はあんまり関係なくて、継続的に大量のログを書くときの中継点としてHoopが使えるのか知りたい。ので、特定のパスにひたすら追記(append)を繰り返す処理を試してみた。
ファイルへのappendは Hoop REST API 経由で、ベンチマークのクライアント側はRubyでコードを書いた。これは一定時間、指定されたデータソースを指定されたHoopサーバにappendしつづける。データソースは最初にメモリに読み込むのでクライアント側のdisk I/O負荷は考えなくていい。
continuous write over hoop
これを一度起動すると、指定したサーバに対して指定した秒数だけappendを繰り返し*1、最終的に何度のappendが成功したか、何度失敗したか*2、概算の平均スループットはどうだったか、およびtimeコマンドの出力を以下のように出す。
written chunk:13421, failed:0 rate: 149 Mbps real 120m0.573s user 0m7.917s sys 0m37.009s
これをシェルスクリプトで並列起動させ、以下のような処理になるようにした。
- 最初の60分
- 1つ起動、10MBのファイルをメモリに読み込んでひたすらappend
- 次の60分
- 2つめも起動(2並列)、おなじくappendを別ファイルへ
- 次の60分
- 3つめも起動(3並列)、おなじくappendを別ファイルへ
結果がこちら。まずnamenode上のHoop、次にdeliver上のHoopに実行している。コメントは説明のため加えた。
starting namenode .... Mon Oct 31 20:24:39 JST 2011 # 1st process (namenode) written chunk:17644, failed:0 rate: 130 Mbps real 180m0.816s user 0m10.253s sys 0m49.879s # 2nd process (namenode) written chunk:10285, failed:0 rate: 114 Mbps real 120m0.642s user 0m5.701s sys 0m28.964s # 3rd process (namenode) written chunk:4360, failed:0 rate: 96 Mbps real 60m0.704s user 0m8.196s sys 0m41.168s ended master101.analysis Mon Oct 31 23:24:40 JST 2011 starting deliver Tue Nov 1 00:24:40 JST 2011 # 1st process (deliver) written chunk:20787, failed:0 rate: 153 Mbps real 180m0.165s user 0m12.570s sys 0m57.139s # 2nd process (deliver) written chunk:13421, failed:0 rate: 149 Mbps real 120m0.573s user 0m7.917s sys 0m37.009s # 3rd process (deliver) written chunk:6088, failed:0 rate: 135 Mbps real 60m0.326s user 0m16.180s sys 1m14.040s ended deliver101.att.scribe.admin Tue Nov 1 03:24:42 JST 2011
これだとわかりにくいよねー、ってことで、ずばり上記の試験期間中のグラフはこんな。
まとめ
性能を見る限り deliver でのスコアは3並列で430Mbpsくらい出ていて*3、そのいっぽう該当の時間帯のdeliverサーバのCPU使用率を見ても1コアを使い切った数値(12.5%)にまったく届いていない。Hoop自体のパフォーマンスとしては430Mbps出て、まだ余裕あり、というところだろう。
2並列から3並列への数値の伸びを見るに1.5倍にはなってないので、このあたりでネットワークなりなんなりの要素が絡んできてるかなーという気はする。これ以上やろうと思うとネットワークまわりの環境をもっといじったりしないと難しいかも。
とはいえ、ぶっちゃけこんだけ出てれば何も問題ない。400Mbps書き込めることがわかってればなんでもできるでしょ。というかlibhdfsとか要らないってこれ。
libhdfsみたいにJVMに依存しJNIに依存しHadoopのバージョンであれこれあり、というものに較べてクライアントは軽量なHTTP REST APIを叩くだけでこんだけ性能出るんだもん。もう全部これでいいんじゃないですかね。
Hoop REST APIの罠
今回やってて気付いたこと。REST API経由でのはなし。
- Append時に対象のパスが存在しなかった場合はエラー(FileNotFound)になる
- Create時に対象パスのディレクトリが存在しなかった場合は勝手に作られる
- Createのデフォルトオプションは overwrite=true
- つまりファイルが存在した場合は無かったことになって、新たにCreate時のデータで新規作成される
これはどうしたもんかなーという挙動。本当は逆で、以下みたいなのがいいんだけど。
- Append時に対象のパスが存在しなかった場合は新規作成されて書き込まれる
- Create時に対象パスのディレクトリが存在しなかった場合はエラー
とはいえこうなってないものは仕方無い。ので、実用上は以下のようにするしか無いと思う。
- 書き込みはすべてAppendでやる
- エラーになったらリトライとしてCreateを overwrite=false でやる
- Createもエラーになったら*4Appendを再度試みる
これで誰かと競合しても大丈夫かなあ。ということでHoopを経由して書き込むときは競合でデータが失われないよう、みなさん気をつけましょう。