HDFSのリースリカバリ、ブロックリカバリ、およびパイプラインリカバリ動作詳解 (その2)

投稿日: 2015/03/18

本記事は、HDFSの内部動作を説明するブログポストの第二部であり、Clouderaのソフトウェア・エンジニアでHadoopプロジェクトのコミッタであるYongjun Zhangによる記事を翻訳したものです。原文についてはこちらをご覧ください。


HDFSの重要な設計要件のひとつとして、連続的かつ正確な動作の保証が挙げられる。そのため、問題が発生したときにHDFSがどのようにリカバリを行うか理解するのは非常に重要であると言える。本ブログシリーズのその1では、リースリカバリ、ブロックリカバリについて述べた。本ポストではパイプラインリカバリについて説明しよう。

おさらい
HDFSではファイルがブロックに分割され、ファイルへのアクセスはmulti-reader, single-writerセマンティクスに従っている。障害耐性の要件を満たすため、複数のブロックレプリカが異なるデータノードに格納される。レプリカの数は複製係数と呼ばれる。新しいファイルブロックが作成されるか、または既存のファイルが追記のために開かれたとき、HDFSの書き込み処理はレプリカの受信と保存のためにデータノードのパイプラインを作成する(複製係数は一般に、パイプライン内のデータノードの数を決定する)。そのブロックへの連続 書き込みは、同じパイプライン(図1)を通過することになる。

[caption id=”attachment_5236" align=”alignnone” width=”620"]

図1. HDFSの書き込みパイプライン

図1. HDFSの書き込みパイプライン[/caption]

読み取り操作の際は、クライアントはブロックのコピーを保持しているデータノードのひとつを選択し、データ転送を要求する。
より詳細についてはその1の参照をおすすめする。

パイプラインリカバリ
書き込みパイプライン
HDFSクライアントがファイルに書き込む場合、データは連続したブロックとして書きこまれる。書き込み、あるいはブロックの構築にあたり、HDFSはブロックをパケットに分割し、図4に示すように、書き込みパイプライン内のデータノードに伝播する。

[caption id=”attachment_5284" align=”alignnone” width=”400"]

図2. 書き込みパイプラインの各ステージ

図2. 書き込みパイプラインの各ステージ[/caption]

書き込みパイプラインは3つのステージに分けられる。以下に見ていこう。

1. パイプラインのセットアップ
クライアントがパイプラインに対してWRITE_BLOCKリクエストを送信し、最後のデータノードは、ACKを返す。ACKを受信すれば、パイプラインは書き込みの準備ができていることになる。
2. データストリーミング
データは、パケット内のパイプラインを介して送信される。クライアントはパケットがいっぱいになるまでデータをバッファリングし、その後、パイプラインにパケットを送信する。パケットがいっぱいでない場合でも、クライアントがhFlush()を呼び出した場合はパイプラインに送られ、前のhFlushされたパケットの確認応答がクライアントによって受信されるまで次のパケットは送信されない。
3. クローズ(レプリカの確定とパイプラインのシャットダウン)
クライアントは、すべてのパケットが確認されるまで待機後、クローズリクエストを送信する。パイプライン内のすべてのデータノードが対応するレプリカをFINALIZED状態に変更し、ネームノードに報告する。ネームノードはその後、少なくとも設定されているデータノードの最小複製数に対応するレプリカの状態がFINALIZEDであると報告があった場合に、ブロックの状態をCOMPLETEに変更する。

パイプラインリカバリ
ブロックが書き込まれている間、パイプライン内の1つまたは複数のデータノードが上記3ステージのいずれかでエラーが発生すると、パイプラインリカバリが開始される。

パイプラインのセットアップ障害からのリカバリ
1. パイプラインが新しいブロックのために作成された場合、クライアントはブロックを破棄し、ネームノードに新しいブロックとデータノードの新しいリストを要求する。そして、パイプラインは新しいブロックのために再度初期化される。
2. パイプラインがブロックへの追記用に作成された場合、クライアントは残りのデータノードとのパイプラインを再構築し、ブロックのGSをインクリメントする。

データストリーミング障害からのリカバリ
1. パイプライン内のデータノードが(例えばチェックサムエラーやディスクへの書き込みに失敗した場合など)エラーを検知すると、そのデータノードは自らすべてのTCP/IP接続をクローズすることで、パイプラインから脱退する。データが破損していないとみなされれば、データノードは関連するブロックとチェックサム(メタデータ)ファイルにバッファリングされたデータも書き込む。
2. クライアントが障害を検出すると、パイプラインへのデータ送信を停止し、残りの良好なデータノードを使用して新しいパイプラインを再構築する。結果として、ブロックのすべてのレプリカは新しいGSを持つことになる。
3. クライアントは、この新しいGSとデータパケットの送信を再開する。送信されたデータがすでにデータノード側で受信された場合、データノードは単にパケットを無視し、パイプラインの下流に受け渡す。

クローズ障害からのリカバリ
1. クライアントがクローズ状態での障害を検知した場合、残りのデータノードとのパイプラインを再構築する。各データノードはブロックのGSを増やし、レプリカがまだ確定されていない場合は確定する。

あるデータノードが不良であった場合、そのデータノードは自らパイプラインから脱退する。パイプラインリカバリの処理中に、クライアントは残りのデータノードとの新たなパイプラインの再構築が必要となる場合があるだろう(不良なデータノードを新しいデータノードで置き換えるか否かは、次のセクションで説明するデータノードの置換ポリシーによる)。レプリケーションモニタは、設定された複製係数を満たすためにブロックの複製を受け持つ。

障害時のデータノード置換ポリシー
残っているデータノードでリカバリのためにパイプラインを設定する際に、不良のデータノードを置き換えるため追加のデータノードを加えるかどうかを決定する設定ポリシーが4つある。これらのポリシーは以下のとおりである。

1. DISABLE: データノードの置換を無効にし(サーバーで)エラーを投げる。これはクライアント側でNEVERのように動作する。
2. NEVER: パイプラインが失敗したときにデータノードを置換しない(一般的に望ましいアクションではない)。
3. DEFAULT: 以下の条件に基づき置換する:
a. rを設定された複製数とする
b. nを既存のレプリカデータノードの数とする
c. r >= 3かつ以下のどちらかの場合のみに新しいデータノードを追加する
d. (1) floor(r/2) >= n; あるいは
e. (2) r > n かつ、ブロックがhflushあるいは追記された
4. ALWAYS: 既存のデータノードに障害が発生したときは常に新しいデータノードを追加する。データノードが置き換えられない場合、この作業は失敗することになる。

次のプロパティをfalseに設定することで、これらのポリシーをいずれも無効にすることができる(デフォルトはtrue)。

dfs.client.block.write.replace-datanode-on-failure.enable

有効な場合、デフォルトのポリシーはDEFAULT。ポリシーは以下のプロパティで変更する:

dfs.client.block.write.replace-datanode-on-failure.policy

DEFAULTまたはALWAYSを使用しているときに、ひとつのデータノードのみがパイプラインで成功した場合リカバリは決して成功せず、クライアントは書き込みを実行することができない。この問題は以下のプロパティで対処することが可能である。

dfs.client.block.write.replace-datanode-on-failure.best-effort

このプロパティのデフォルトはfalseである。デフォルトの設定では、指定されたポリシーが満たされるまでクライアントは書き込み続けようとする。このプロパティをtrueに設定すると、指定したポリシーが満たされない場合であっても(例えばポリシー要件より少ない、パイプラインで成功した唯一のデータノードがある場合など)、クライアントがまだ書き込みを継続することが許可される。

解決されている問題
HDFS-5016では、データノードが死んだとマークされる問題を引き起こす、パイプラインリカバリにおけるデッドロックのシナリオを詳述している(HDFS-3655: Datanode recoverRbw could hang sometime」とHDFS-4851「Deadlock in pipeline recovery」のduplicateである)。何が起こるかというと、リカバリが進行中のとき、複数の関連スレッドが互いに待ち合うことでデッドロックが発生するというものである。このデッドロックではFSDatasetのロックが保持されているため、ハートビートスレッド、データ送受信スレッドがFSDatasetのロックで待機しブロックされる。解決策としては、デッドロックを解除するためのタイムアウトの仕組みを導入することである。

HDFS-4882は、ネームノードのLeaseManagerがcheckLeasesで永遠にループし続けるというケースを報告している。ハードリミットが満了したときにLeaseManagerはリースを回復しようとし、最後から2番目のブロックがCOMMITTEDで最後のブロックがCOMPLETEの場合、internalReleaseLease()はリースを解放せずにreturnし、かつLeaseManagerは同じリースを解放し続けようとするので無限ループになる。FSNamesystem.writeLockがループに陥っているので、事実上ネームノードが応答不能になるということである。この修正は、継続的よりも定期的にリースの解放を試みるというだけのものであった。

HDFS-5557では、ブロック内の最後のパケットのための書き込みパイプラインのリカバリが、ブロックレポートを扱う際に誤ったGSを記録することで、有効なレプリカの拒否を引き起こす可能性について詳しく説明している。最悪の場合はすべての良好なレプリカが拒否され、不良のものが受け入れられてしまう。この場合、対応するブロックが確定してしまうが、データは有効なレプリカの1つを含んでいる次のブロックレポートの受信まで読み取ることができない。解決策は、GSの記録を修正することである。

HDFS-5558では、最後のブロックが完了していて最後からの2番目のブロックがそうではない場合、LeaseManagerのモニタスレッドがクラッシュする可能性があると報告している。ファイルにはCOMPLETE状態でない最後と最後から2番目のブロックがあるが、ファイルをクローズしようとすると最後のブロックがCOMPLATEに変更され、最後から2番目のブロックがそうならない場合がある。この状態が長く続くいてファイルが放棄されている場合、LeaseManagerはリースをリカバリし、ブロック上でブロックリカバリを試みる。しかしinternalReleaseLease()はこの種類のファイルで無効なキャスト例外(invalid cast exception)で失敗する。解決策は、ファイルをクローズする前に最後から2番目のブロックがCOMPLETE状態にあることを保証することである。

既知の未解決の問題
・dfs.client.block.write.replace-datanode-on-failure.best-effortの導入により、データノードが1つの場合であっても、クライアントは書き込み続けることができるようになる。この場合、ブロックは1つのレプリカのみを持っているかもしれないので、それが複製される前に何かがこの単一のコピーに起こった場合、データロストが発生する。
問題を軽減するため、HDFS-6867ではクライアントが単一のレプリカに書き込んでいる間にパイプラインリカバリを行うためのバックグラウンドスレッドの導入を提案している。

HDFS-4504では、DFSOutputStream#closeが常に(リースと同じように)リソースを解放していないケースについて詳しく説明している。DFSOutputStream#closeがIOExceptionを投げる場合があると報告されている。一例としては、パイプラインのエラーがあった場合にパイプラインリカバリが失敗するというものである。残念ながらこの場合には、DFSOutputStreamが使用する一部のリソースがリークする。一つの特に重要なリソースは、ファイルのリースである。
従って、Flumeのような長期間存続するHDFSクライアントではファイルに多くのブロックを書き込むが、その後クローズに失敗する可能性があるということである。ただし、クライアント内部のLeaseRenewerThreadは「undead」ファイルのリースを更新していく。ファイルをクローズするため、将来的には単に前の例外を再度スローするのがよいだろう。そうすれば、クライアントからは何もできないようになるだろう。

HDFS-6937では、チェックサムエラーによるパイプラインリカバリの問題について詳しく説明している。(複製係数が3だと仮定して)中間のデータノード上のデータが何らかの理由で破損すると、検出されないことがある。最後のデータノードはチェックサムエラーを発見し、パイプラインからそれ自身を取り出す。リカバリ処理は、パイプライン中の最後のデータノードを新しいデータノードで置き換えようとするだけでなく、中間のデータノードからデータを新しいものに複製しようとする。レプリケーションが(中間のデータノードでの破損したレプリカにより)チェックサムエラーが原因で失敗するたびに、新しいデータノードは本来は問題ないにもかかわらず不良とマークされ、破棄されるのである。最終的に全てのデータノードを使い果たした後にリカバリが失敗する。

HDFS-7342では、最後から2番目のブロックがCOMMITTEDで最後のブロックがCOMPLETEの場合、リースリカバリが成功しないケースを報告している。提案された解決策のひとつは、最後のブロックがCOMMITTEDの場合の処理と似ているやり方で、リースのリカバリを強制することである。HDFS-7342、HDFS-4882、HDFS-5558は、最後から2番目のブロックがCOMMITTED状態であることに関連していることがわかるだろう。問題の細部は現在も調査中である。

まとめ
リースリカバリ、ブロックリカバリ、およびパイプラインリカバリはHDFSの耐障害性における本質部分といえる。これらが協調することで、ネットワーク/ ホスト障害下においても永続性、一貫性を保証するのである。この記事を通して、3つのリカバリ処理が起き、どう動作するのか理解できたら幸いである。

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.