たごもりすメモ

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

WebHDFSとHttpFsについての簡単なベンチマーク

前のエントリの続き。実用上どうなのってことで、とりあえず簡単にベンチマーク的な負荷走行をしてみた。
実行環境は以下の通り。なお HttpFs Server はNameNode上に立ててある。

  • CDH4b2 + HttpFs/WebHDFS
  • NameNode x1
    • 2CPU 8GB Memory
  • DataNode x4
    • 2CPU 8GB Memory 2TB HDD
  • ベンチマークスクリプト実行ノードもNameNodeと同スペック

またベンチマークに使用したコードはこちらRubyスクリプトで、ruby 1.9.3 で動かした。簡単に処理内容をまとめると以下のような感じ。

  • 特定の1ファイルに対してappendを繰り返す
    • appendのサイズは1回あたり10MB
    • 時間あたりでappendに成功した回数を記録
  • 上記処理を行うプロセスを1〜3並列で1時間走行
  • HttpFs、WebHDFS についてそれぞれ実行
    • HttpFs は net/http を使って直接appendを実行 (httpfs)
    • WebHDFS はwebhdfs rubygemを使いappendを実行 (webhdfs)
    • WebHDFSについて、直接 net/http を使い、かつNameNodeの返す Location をキャッシュしてDataNodeにのみリクエストを投げ続ける処理も実行 (cachedwebhdfs)
      • このケースではHTTP接続も可能な限り持続させる
結果

ベンチマークは以下の順で実行した(それぞれグラフの山に対応する)。

  1. httpfs 1セッション
  2. httpfs 2並列
  3. httpfs 3並列
  4. webhdfs 1セッション
  5. webhdfs 2並列
  6. webhdfs 3並列
  7. cached webhdfs 1セッション
  8. cached webhdfs 2並列
  9. cached webhdfs 3並列
  10. cached webhdfs 4並列 (最後にひとつ離れた山)

結果はグラフをごらんいただくのがよいでしょう。まずベンチマークスクリプトを実行した側。



CPU使用率を見るとこっち側がボトルネックにはまずなってないというのはわかると思う。
トラフィックは大変わかりやすい結果。まず httpfs がたいへん良い結果で、webhdfsはやはり単純なスループットでは劣る。webhdfsについてはファイル毎にDataNode URLをキャッシュすれば1.5倍弱程度の性能が出るようだ。NameNodeにいちいち問合せに行くオーバーヘッドがなくなるので、まあ当然といえば当然。
TCP Establishedを見るとwebhdfsの場合に突出している、のはNameNodeへの接続とDataNodeへの接続がひっきりなしに接続/切断を繰り返すからですね。まあこれもわかりやすい。他のケースではHTTP接続は(切れない限り)持続するようになっている。これもパフォーマンスに影響するだろう。

つぎ、NameNode。




CPU使用率はHttpFsの場合にのみ user が上がっているのは普通に納得できると思う。iowaitはファイルへの追記にともなうメタデータ更新分ですね。これはほぼ単純にトラフィック(ファイルの更新回数)に比例してると思うので、特筆すべきことは無し。ロードアベレージもそれに準ずる状況で、特に不思議なことはない。
トラフィックも HttpFs の場合は全データを中継するので上がっており、webhdfsの場合ではNameNodeはいっさい実データをやりとりしないので全く上がっていない、というわかりやすい結果。TCP Establishedも cached webhdfs では低い、わかりやすい結果ですね。

最後、DataNode。*1




CPU使用率は入ってくるトラフィックに応じて上がっている。iowaitがそれなりにあるのも普通。systemがこんなにあるのはちょっと不思議だけど(あんまり考えてない)。ロードアベレージもまあ、どのケースでも大したことはない。
トラフィックを見るとどのノードも平均的に上がっている、のはレプリケーションがあるから、のはず。今回は4ノードしかなくて3レプリカ設定なので、2並列以上の書き込みだと全ノードにI/Oが発生する。
TCP Established は各試行の違いが顕著で、特に cached webhdfs だと、書き込み対象ファイルの権威ノードかどうかでクライアントからの接続/切断の回数が全く異なる。このあたりはDataNodeが直接接続を受けるwebhdfsの特性が出ている、と同時に、DataNode URLをキャッシュすることの弊害が直接見えている。

まとめ

webhdfsは全ノードとの通信*2をHTTPで行うものだ。で、当然Java実装のネイティブプロトコルより効率が悪い、ので HttpFs よりも性能が落ちる。ある意味当然の結果になった。

なっちゃった。さて、どうしよっかなー。

なお、今回はたった4ノード(かつ2CPUずつ!)というかなり小規模な環境でのベンチマークだったので、これは実はwebhdfsには不利な状況だった。DataNodeが直接通信を受けるwebhdfsにおいてはDataNodeのノード数が増えれば当然それに応じて全体のパフォーマンスがスケールする。
また cached webhdfs 4並列の結果が3並列とほとんど変わらないことを見ても、合計8CPUは3並列の書き込み+レプリカ作成でほとんどリソースを食われており、余剰がなかったことがわかる。特にCPUリソースがあまり潤沢でない状況でwebhdfsがスケールしないのはある意味当然なので、CPUコア数の多いハードウェアを使えばまた多少違う結果が出るだろう。

とはいえ、HttpFs使っとけば、というのが比較的わかりやすい一般解になるかもしれない。webhdfsは簡単に使えるし安心感もなくはないが、パフォーマンス的につらい状況が生まれやすい未来は想像しやすい。

追記(2012/06/06)

kzk/webhdfs · GitHub

このライブラリをWebHDFS/HttpFs両方に使えるようにしたので、それを使ってもういちど簡単なベンチマークをとってみた。
負荷サーバからのトラフィックがこのグラフ(の右の4本)。

単独セッションでほぼ倍の性能差がある。HttpFsは2セッションで性能的にはほぼ頭を打っている、のは2CPUのNameNode上にHttpFsが上がってるからかな。WebHDFSはセッション数に応じて割とスループットが上がっていっているので、DataNodeがもっと増えれば上限はまだ上がる、と思う。ノード数が20を超えるようなケースだとWebHDFSの方が性能出るんだろうなあ。

あとTCP Establishedをベンチサーバ、NameNode(HttpFsサーバ)、DataNodeのひとつ、の順で。



なんとなくHttpFsの方が見てて落ち着くなー。

*1:のうち1ノード。どのノードも傾向としては変わらない。

*2:レプリケーションを除いて

CDH4b2におけるWebHDFSとHttpFsについて

CDH4b2でWebHDFSとHttpFsについていろいろ試しているので、分かっている内容をまとめてみる。なお注意点だが、各々以下のような状況であることに注意。

WebHDFS
たぶんHadoop 1.0ベース
HttpFs
たぶんHadoop 0.23ベース(あるいは 2.0 alpha ベース) でHoopとは別物

HttpFsはコードとしてはClouderaオリジナルのHoopのものがベースになっているんだろうし、アーキテクチャ自体も同じ(詳しくは以前のエントリを参照のこと)だけど、細かいところがあちこち異なっているので注意が必要。
またCDH4ベースなのでCDH3ベースのものとは特に性能特性が異なる可能性が高い、が、性能特性についてはまた別に。

HTTP API

WebHDFSのAPIは年初に確認したときから変わっていない。

HttpFsのHTTP APIHoopとしてClouderaが公開したものからは大きく変わっている。
というか、もっと言うと、ほとんど WebHDFS のAPIに準拠するようになっている。パスの先頭には /webhdfs/v1 とつき、またHTTPリクエストメソッドでHoopとWebHDFSで違いがあった部分はほとんど(すべて?)WebHDFSのものに合わせる形で変更されている。
で、それが実際どう変更されたかはHTTP REST APIリファレンスを見ればよい、と思うんだが、それが公開されていない。どこにもない。こまった。今のところは手探りでリクエストを発行してみて確認するしかない。

また、実際にデータの転送が起きるリクエスト(ファイルの作成、データのappend)において、アクセスパターンに以下のように変更がある。

Hoop
リクエストが受け付けられ、ファイルが作成/追記され、201/200が返る
HttpFs
リクエストに対してリダイレクトがレスポンスとして返り、Locationで指定されたURLに対して改めてリクエストすることでファイルが作成/追記され、201/200が返る

HttpFsの挙動、まるでWebHDFSみたい……。が Location が返るURLをちゃんと見ると、リクエストパラメータに data=true というものが加わっているだけだった。新設されたパラメータらしい。これを最初からつけてリクエストを送ると一発で 201/200 が返るようになったので、そのようなものだと理解しておけばいいのかもしれない。*1

好意的に見ておけば、WebHDFS用のクライアントライブラリとHttpFs用のクライアントライブラリは全く同じものを使えるようにこうなっている、のかもしれない。細かいパラメータの一致までは見られていないが、もしきちんと一致がとれているのであれば可能になる。現状見た限りでは、可能になっていそうな雰囲気を感じる。

セットアップ

WebHDFS

WebHDFSは NameNode/DataNode で元々動いているWebサーバ(Webコンソールとか出すやつね)がそのまま処理するため、使うには設定をひとつ有効にするだけでいい。

<!-- in hdfs-site.xml -->
<configuration>
  ...
  <property>
    <name>dfs.webhdfs.enabled</name>
    <value>true</value>
  </property>
</configuration>

これを変更して NameNode および DataNode を再起動すると有効になる。アクセス先のポートは(CDHのデフォルトなら) NameNode の 50070。

HttpFs

HttpFsは独自のサーバプロセスを起動することになる。それ自体は Hadoop Core のパッケージ*2に含まれている。設定はHadoop側とHttpFs用のものとでいくつか必要。

まずHadoopクラスタ側の core-site.xml でHttpFsがアクセスするユーザの設定。めんどくさいのでとりあえず全通し。

<!-- core-site.xml -->
<configuration>
  ...
  <property>
    <name>hadoop.proxyuser.USERNAME.hosts</name>
    <value>*</value>
  </property>
  <property>
    <name>hadoop.proxyuser.USERNAME.groups</name>
    <value>*</value>
  </property>
</configuration>

それからHttpFsがHadoopクラスタの情報を参照するための設定を、HttpFs側の httpfs-site.xml で行う。これはHoopのときは直接NameNodeのアドレスを指定する設定項目だったが、HttpFsでは HADOOP_CONF_DIR を指定しておけばHttpFs側で読んでくれるようになった。

<!-- httpfs-site.xml -->
<configuration>
  <property>
    <name>httpfs.hadoop.config.dir</name>
    <value>/path/to/hadoop/conf/dir</value>
  </property>
</configuration>

これを指定したら以下のコマンドで起動する。起動後はそのサーバのポート 14000 にアクセスすれば使える。

$ sbin/httpfs.sh start

雑感

まあ、まだどうなるかよくわからんすね。WebHDFSは簡単に動いていいなあ、とかは思う。
ところでそろそろ誰か自分以外にも試しませんか。

*1:これは実はやってはいけない操作だという可能性もある、が、リファレンスが出ないことにはなんともわからん。

*2:CDH4b2の場合は hadoop パッケージ

CDH4にいったん挫折した

CDH3で使ってる設定ファイル群を基本的にそのまま(ノードリストなどだけ書き換え)でCDH4の検証クラスタを作ろうとしてみた。

ら、以下のような事情でいろいろ面倒くさかったのでいったん挫折したのが本日あったこと。

  • 使う環境変数がいろいろ変わってる
    • YARN_HOGE とか HDFS_HOGE みたいなのを使うようになっている
    • 基本的には HADOOP_CONF_DIR などから組み立てるようになっている
    • ただしその処理は libexec/hadoop-config.sh で行われる
    • $HADOOP_CONF_DIR/hadoop-config.sh を自分で用意して使っている場合は libexec/hadoop-config.sh が読まれないので新しい変数の組み立てができない
    • このため各種の環境変数がセットされずいろいろコケる

たいへん困った。設定ファイル群をイチから作り直す覚悟が必要な模様。

雑感

なおここまでセットアップ用にあれこれ眺めたり読んだりした感想。

  • NameNode HAには共有ストレージが必要
    • NFSでもいいよ!」とか書いてあるけど、それSPOFじゃん……HAの意味ねーっすよ
    • だいぶがっかり
  • CLIコマンド群がだいぶリネームされてる
    • hdfs や mapred, yarn などといったコマンドに機能が細分化されている
      • えええええ、すごい色々作り直さないといけない予感……
      • コマンド依存はRPCベースへの移行で減らしててまだ助かったけど、運用バッチまわりのチェックが大変
    • デーモンの起動が sbin/ 以下に分離されてる、これは良い

とにかく手がかかるなーという印象はある。CDH3u5に本当にWebHDFS入るなら、NameNode HAも今のところ使えないし、CDH4はしばらく放置の方がいいのかなー。そうしようかなあ。

「tagomorisが騙る はじめてのHadoop」

深夜に「はじめてのHadoop」が出版されるとしたら欲しいネタの一覧をなんとなく考えてた。Hadoopクラスタのセットアップを新規にやるのが予定にあるので、それも含めてトピックだけ書き出しておこうかなと思った次第。

  1. 設計
    • HDFS総容量と処理対象のデータ量について
    • ファイルの形式について (TextFile/SequenceFile/RCFile)
    • データの圧縮について
    • ノードあたりのHDD台数、ディスクの選択
    • CPUおよびメモリの選択
    • ノード数
    • HDFS設定の設計
    • Hadoopのバージョン選定、導入方法(tar/rpm etc)、Hadoop起動ユーザ名の選択
    • データ投入方法
    • ジョブ実行方法
  2. セットアップ
    • Linuxのセットアップと設定変更
    • 最初に変更しておくべきHadoopの設定値
    • Hadoopのセットアップと設定変更
    • NameNode HA (CDH4 or laterベース?)
    • 周辺サーバ(HiveServer / Hive Metastore / HoopServer / WebHDFS)
  3. データ投入
    • 圧縮について
    • hadoop fs
    • WebHDFS
    • Httpfs
  4. ジョブの実行
    • Hadoop Streaming
    • Hive
    • HiveServer経由でのクエリ実行、結果取得
  5. 運用
    • Hadoopの設定変更
    • ノードの追加
    • DataNode障害時の対応
    • DataNode ディスク障害時の対応
    • NameNode障害時の対応
    • Hadoopのバージョンアップ

うーん、いっぱいある。そしてMapReduceについてがすごい少ないw
これ、誰か書いてくれないものかなあ。社内ドキュメントなり個別にblogエントリなりにはいずれ書かないといかんのだが、めんどい……。買えるならいくらでも出しますよ。

hiveの出力が圧縮される方法がよくわからない、と思っていたら俺があほでした

きわめてざっくり書くと以下のような感じ

  • 同じようなクエリを発行しているふたつの出力の圧縮方法が違う
    • 片方はHDFS上でのファイル全体がgzip圧縮されている (ファイルを見ると 00000_0.gz のようになっている)
    • 片方はレコード/ブロック単位でのgzip圧縮になっている (ファイルを見ると 00000_0 で中身先頭にSequenceFileの部分圧縮用ヘッダが書かれている)
  • そもそもこれは何故起きるのか?
    • 似たようなクエリだが最適化の方法が違うなどの理由でこうなるのか?
    • そもそもファイル全体を gzip 圧縮する方法と SequenceFile 内部でgzip圧縮する方法ってどう指定を変えたらどっちになるの?

hiveむづかしい。

(あとで書いた) TextFile だとファイル全体で圧縮がかかって、SequenceFile だと mapred.output.compression.type の指定に従って圧縮を行うわけですね。SequenceFileを扱う場合はこれを BLOCK とかにしておくと圧縮効率が上がります。というか、そうしないと圧縮効率が下がって悲しいことになります。

ということでした! わかった!

詳細

定期的に他のテーブルから1日分のデータをまとめたりしてdailyのテーブル(のパーティション)を編成してる。流れとしては以下のような感じ。

  1. hourlyでパーティションをつくっているテーブルから24時間分をまとめて daily (full) のテーブルに1パーティションとしてINSERT
  2. daily (full) の1パーティション(1日分)から特定の条件でSELECTをかけ、一部分を抽出して daily (part) のテーブルに1パーティションとしてINSERT

このとき、前者より後者の方がデータは少ない(レコードを選別してるし、フィールドも一部のもののみ取り出している)ので後者のデータサイズが小さくなることを当然期待したが hadoop fs -du してみるとそうなってない。あれー?

ということで調べてみたら、以下のようになっていた。

  • daily (full) の方は各パーティション内のファイルは 00000_0.gz のようなファイル名になっており、ファイル全体がgzip圧縮されている
  • daily (part) の方は各パーティション内のファイルは 00000_0 のようなファイル名になっている
    • 中身を見ると 以下のようなヘッダで始まっている

SEQ"org.apache.hadoop.io.BytesWritableorg.apache.hadoop.io.Text'org.apache.hadoop.io.compress.GzipCodec

まったく同じ設定で連続して発行したふたつのクエリでなぜこうなるのか。テーブル定義は両方とも STORED AS SEQUENCEFILE に

……なってなかった!!!!! ここまで書いて気付いた! daily (full) のテーブルは STORED AS TEXTFILE だった!!!!!!

結論

mapred.output.compression.type はちゃんと BLOCK とかで指定しておきましょう。デフォルトの RECORD はたいへん効率わるいです。

「Hadoop Hacks」読んだ

Hadoop Hacks」を著者陣のご高配を得てオライリー・ジャパンから献本いただきました。ありがとうございます。

Hadoop Hacks ―プロフェッショナルが使う実践テクニック
中野 猛 山下 真一 猿田 浩輔 上新 卓也 小林 隆
オライリージャパン
売り上げランキング: 2139

で、ざっと読んだ(自分でやってないところは眺めた程度)ので感想をざらっと。

なんというか、さすがにちょっと扱う内容が広過ぎる&プログラミングを避けられない箇所が多過ぎる感はあって、苦労したんだろうなー、という気がする。読んで「ああこれは役に立つよね」というのがだいぶ少なくてちょっと残念。100行単位でコードを書かないといけない内容をこういう本で「hack」といって紹介するのはやっぱりちょっときびしいなー。

とはいえ、第1章「システム構築/運用Hacks」はHadoopを使っているすべての人にお勧めできる、と思う。実際にHadoopクラスタを構築・設定する上でとりあえず最初にやっとけ的なことがちゃんと順番に書いてあるので、まずここを読んどけばいい。自分も次に(CDH4で?)クラスタ構築しなおすときに舐め回すように読んで設定するつもり。こういうのが書かれているのを本という形で手元に置いておけるのは大きい。

2章「アプリケーション開発Hacks」および4章「Hive Hacks」はひととおり読んでみたところ「これは知らなかったな」的なものもいくつか。とはいえHadoopクラスタ/ジョブの運用方法が違いすぎて残念ながら(知ると面白い知識だけど)直接の参考にできるものがほとんどなかった。同じHadoop/Hiveを使っていながらここまで違うか、という感想自体の方が参考になるというか、ちょっと面白い。

他の章も含めて全体的に「役立つものがある可能性がある、ので手元に置いとくとよい。Hadoop関連で新しいことをやるときはこの本の目次をぱらぱらめくってみるべし」みたいな感じ。Hacks系はだいたいそんなだけど、Hadoopまわりは作業粒度が大きい分、そのへんの扱いが微妙な気はする*1
とはいえ「Hive Hacks」の「クエリの高速化」なんかはHiveを巨大RDBMS的に使いたい人には良さそうなことがいろいろ書いてあったので、著者陣と各コンポーネントの使いかたががっちりハマったときにはすごく有用なんだろうな、という気がする。

雑感

Hadoop Hacksというか、Hadoop全体的に思うことなんだけど、設定(プロパティ)をこう変えてチューニングしろとか、これをこう変えてクラスタを再起動しろとか、Hive CLIでこれこれこのように打っておけとか、そういうのが多いのがたいへん気になる。
普通にオンラインでデータ処理をやろうとすると当然CLIでクエリを打つなんてメンテナンスとかでしかやらないようになり、基本的にはサーバを介してアプリケーションがクエリなりジョブなりを発行する*2ことになると思う。んだけど、Hadoopまわりを見ているとそういう構造をとっていることが前提な話がぜんぜんなくて、やれプロパティを変えて再起動しろ、環境変数を変えて再起動しろという話が多いのでだいぶ面喰らってしまう。

できるか!

たとえば、いま手元で困ってるのはHiveクエリを実行したとき、そのクエリに関連するMapReduceジョブの状況がどうなったのかを知りたい(killったりもしたい)、んだけどそういう方法がないなー、とかそういう話。そりゃJobTrackerのWebコンソール見ればわかるんだけど、そういうことじゃなくてさあ、みたいな。
そろそろ、どのくらい「再起動せずに調整するか」「再起動せずに移行するか*3」「外部システムとどう連携するか」みたいなことに注目して本を書く人が出てきてもいいんじゃないかなーと思います。というか、そういう情報が欲しいなあ。

*1:そういう意味では Hacks シリーズじゃなくて、なんかもうちょっと違う本にできたんじゃないの、という気もするけど……どういう本がいいのかはどうにもこうにも

*2:HiveであればHiveServerを介したクエリの実行

*3:データの置き場とか、Hadoopのバージョンとか

HDFSでMissing blocksが出た場合、DataNodeに問題があって外したい場合の対応メモ

手元でちょっとやったのでメモっとく。

Missing blocksが出た場合

HDFSのブロックが読めなくなることがある。手元ではHoop Server経由でappendが高頻度で発生している状況で、さらにHiveのクエリ実行が重なって派手にiowaitが出たときに起きた。
こうなると、そのブロックを含むファイルをMapReduceジョブが読みにいったときにIOErrorでコケてどうにもならなくなる。

状況は以下のコマンドで確認できる。

$ hadoop dfsadmin -report

Configured Capacity: 35339596017664 (32.14 TB)
Present Capacity: 33745796892986 (30.69 TB)
DFS Remaining: 13764055724032 (12.52 TB)
DFS Used: 19981741168954 (18.17 TB)
DFS Used%: 59.21%
Under replicated blocks: 37021
Blocks with corrupt replicas: 0
Missing blocks: 0

-------------------------------------------------
Datanodes available: 9 (9 total, 0 dead)

Name: 10.xx.xx.xx:50010
Decommission Status : Normal
Configured Capacity: 3905711992832 (3.55 TB)
DFS Used: 2237537268559 (2.04 TB)
Non DFS Used: 191584869553 (178.43 GB)
DFS Remaining: 1476589854720(1.34 TB)
DFS Used%: 57.29%
DFS Remaining%: 37.81%
Last contact: Thu Feb 16 13:38:15 JST 2012

ここの Missing blocks の数を見ていればいい。もしこいつが1以上になった場合は読めなくなっているファイルがあるはず。これを次のコマンドで確認する。これはNameNodeでないと動かない(多分)。

$ hadoop fsck / -files -blocks
(中略)

Status: HEALTHY
 Total size:	6540390145448 B
 Total dirs:	6383
 Total files:	104299
 Total blocks (validated):	112734 (avg. block size 58016127 B)
 Minimally replicated blocks:	112734 (100.0 %)
 Over-replicated blocks:	2542 (2.2548654 %)
 Under-replicated blocks:	26 (0.02306314 %)
 Mis-replicated blocks:		0 (0.0 %)
 Default replication factor:	3
 Average block replication:	3.0239325
 Corrupt blocks:		0
 Missing replicas:		26 (0.00762687 %)
 Number of data-nodes:		9
 Number of racks:		1
FSCK ended at Thu Feb 16 13:47:28 JST 2012 in 2064 milliseconds

The filesystem under path '/' is HEALTHY

ここでHEALTHYになってない場合、「(中略)」した部分に、該当のブロックがどのファイルのものなのかが出てくる。出力される行数がすごいので、いったんファイルにリダイレクトしておいて、そこから頑張って grep するのがよい。該当行は以下のような感じ。

/path/to/somewhere/accesslog-20120211-06.log: CORRUPT block blk_-5847475750566557128

あとはこのブロックをどうにかするんだけど、多分復活させる方法はないので、該当ファイルをいったん削除して作り直すなどする。データが読み込めなくても削除はできるので、さくっと hadoop fs -rm するのがよい。

(2/17追記) コメントより、確かに hadoop fsck / -delete というオプションがあって、これやると不整合の出てるファイルを消せます。ただ確認なしにやるのはありえないのでこのオプションなしで実行し、それから対象を確認して実行、とするのがたぶんいい。自分の場合は対象が Hive テーブルに読まれてるブロックだったので、Hive上から alter table drop partition した。

DataNodeに問題があって外したい場合

DataNodeのディスクでI/Oエラーが出てどうにもならない感じなので交換したい、というケース。象本2版(英語版) p315 "Decommissioning old nodes" に書いてある内容。

このノードを外すために mapred.hosts, dfs.hosts で指定する include リスト(ノードのリスト)および mapred.hosts.exclude, dfs.hosts.exclude で指定する exclude リストを使用する。
これも象本(p314)に記載があるが、include ファイルは slaves ファイルとは役割が違う。slavesファイルは start-*.sh や stop-*.sh などのスクリプトが操作対象を決定するためのもので、include/excludeファイルはNameNode/JobTrackerなどが見るためのもの……らしい。なんで区別があるのかは知らん。たぶん何か理由があるんだろう。

なお自分の手元環境ではこれらの設定を *.xml に指定していなかったのでどうするかと思ったが、NameNodeは設定ファイルに追記した上でコマンドを叩けばこれを読み直してくれるようなので再起動の必要はない。JobTrackerは何もジョブが走っていない状態にして(?)再起動すればよい。

また include/exclude ファイルにはNameNode/JobTrackerが各ノードを認識している名前そのままで書く必要がある。NameNodeのWeb UIから辿れる "Live Nodes" あたりに出てくるノード名で書けばたぶん正解。正引きできるDNS名で書いても、名前が一致していないと別ノードとして扱われ、クラスタには1台もいなくなりました、という破滅的な状態になる。JobTrackerであればそういう事態になっても比較的傷が浅いので、まずJobTrackerで試し、結果を確認してからNameNodeにも反映するとよい。(以下、そういう手順を書く。)

具体的な手順は以下の通り。

include/excludeファイルを指定していなかった場合

include/excludeファイルがなければ作成する

  • NameNode上の適当なパスに作成する
  • まず、全ノードを include ファイルに書いておくこと
  • これをやって一度読ませないと正常に Decommissioning になってくれない

hdfs-site.xml および mapred-site.xml に記述を追加する

  <property>
    <name>dfs.hosts</name>
    <value>/home/hadoop/include</value>
  </property>

  <property>
    <name>dfs.hosts.exclude</name>
    <value>/home/hadoop/exclude</value>
  </property>
  • mapred-site.xml
  <property>
    <name>mapred.hosts</name>
    <value>/home/hadoop/include</value>
  </property>

  <property>
    <name>mapred.hosts.exclude</name>
    <value>/home/hadoop/exclude</value>
  </property>
  • なおここでは mapred と dfs で同じリストファイルを参照しているが、ほとんどのケースではこれで問題ないと思う

これを一度クラスタに読み込ませる

  • まず JobTracker に反映させるため、そちらを再起動する
 $ stop-mapred.sh
 $ start-mapred.sh
  • JobTracker の Web UI を見てノード数などが変わっていなければOK
    • ノードの指定などにおかしい部分があると Nodes が 0 になったりするので、直してまた再起動する
  • JobTracker が問題ない状態なのを確認したら NameNode にも読み込ませるため以下のコマンドを叩く
 $ hadoop dfsadmin -refreshNodes
    • NameNode の Web UI を見て、ノード数が変わっていないことを確認すればOK
ノードの除外

続けて、問題のあるノードを外す。まず exclude に登録して Decommissioning 状態にする。この状態になると該当ノードに存在するブロックを強制的に他のノードにレプリケーションする処理を行うため、完了までは待つ必要がある。完了すれば全ブロックのレプリカが他のノードに規定数存在することになり、安全にこのノードを落とすことができるようになる。

exclude ファイルに該当ノードを追記する

  • NameNode上の exclude ファイルを編集してしまう

JobTrackerを再起動して、正常に反映されるか確認する

  • 以下のコマンドで
 $ stop-mapred.sh
 $ start-mapred.sh
    • 実行後に JobTracker の Web UI を見ると "Excluded Nodes" のところに該当ノードが入るはずなので、それを確認する

NameNodeにも反映する

 $ hadoop dfsadmin -refreshNodes
  • 実行すると "Live Nodes" は変わらないままで "Decommissioning Nodes" に該当ノードが入るはず
  • 大量のブロックのレプリケーションが開始されるので、この完了を待つ
  • (以下まだ実施してないので予定) 完了したら "Decommissioned" となる

該当ノードを落とす

  • とりあえずTaskTracker/DataNodeを落とせばいいのかな、たぶん

NameNodeからノードを除去

  • include ファイルから該当ノードを削除し、その上でコマンドを叩いてNameNodeに反映させる
 $ hadoop dfsadmin -refreshNodes

slavesからも除去する

  • start-all.sh とかやっちゃったときに間違えて該当ノードを操作してしまわないように

疑問がひとつ。exclude ファイルからも include から削除するのと同じタイミングで取り除いちゃっていいんだよね? 象本では記述がない。まあ大丈夫だろうと思うんだけど。

あ、もうひとつあった。slaves とか include/exclude ってコメントアウトできないのかな。再セットアップしたら同じ名前でクラスタに戻したいんだけど、いちいち削除とか面倒なんでコメントでどうにかしたいなあ。

(2/17追記) 試してみたけど include から # でコメントアウトしようとしてみたら -refreshNodes した後で 「'#' というノードが見付からない」という大変残念な画面 になった。残念だ。

2/17追記: その後の顛末

"Decomissioning in progress" になってから4時間くらい激しくブロックのレプリケーションが行われたが、それがひと段落ついてからもいっこうに "Decommissioned" にならず22時間が経過した*1ので、腹に据えかねてその後の作業を強行した。その状況を追記しておく。

  1. 該当ノードのsecondary namenodeおよびdatanodeを停止
    • 停止方法がよくわからなかったので kill った
  2. include から該当ノードを除去して dfsadmin -refreshNodes を実行
    • この時点で NameNode のWeb UI上で該当ノードが "Decomissioned" になった
  3. slavesからも外して処理完了
    • しばらく待ってたら該当ノードが "Dead" に移ってた

Decomissioned になるタイミングがよくわからなかった。自動的になるんじゃないの? レプリケーションが終わったっぽいタイミングで自分でやれってことなのかなあ。うーん。新しいブロックの書き込みは Decomissioning になった時点でなくなってたっぽいから多分この状況で実害は無いんだけど、なんかモヤモヤする。

*1:Hoop Serverからの書き込みが連続しているせいかなあ、といったん停止してみたりしたけど、変わらなかった