ZFSのギャングブロックについて理解を深める

ZFS(のZIL)の断片化では「ギャングブロック」がキーのようなので、理解を深めるべくzio.cのギャングブロックの説明を翻訳してみた。

ギャングブロックは、DMUのような1つの大きなブロックを模倣した、小さなブロックの集合体です。 プールが殆ど満杯だったり深刻な断片化が原因で、zio_dva_allocate()が要求サイズのブロックを見つけられなかった時、それは小さなフラグメントからブロックを構築するためにzio_write_gang_block()を呼びます。

ギャングブロックは、1つのギャングヘッダ(zio_gbh_phys_t)と最大3つ(SPA_GBH_NBLKPTRS)のギャングメンバーから構成されます。 ギャングヘッダは丁度インダイレクトブロックのようなもので、ブロックポインタの配列です。 ギャングヘッダは1セクタしか消費しません。それ故に、断片化にかかわらず確保可能です。 ギャングヘッダのbp(訳注:blkptr_t)はギャングメンバーを指し、それがデータを保持します。

ギャングブロックは、SHA256チェックサムの一意性を保証するための検証値にbpのvdev, offset, txgを用いた自己チェックサムを行います。 重要なのは、そのギャングブロックのbpのblk_cksumはデータのチェックサムであり、ギャングヘッダのものではないという事です。 これは、データブロックシグネチャ(重複排除に必要)の物理的な格納状態に依存しない事を保証します。

ギャングブロックは入れ子になり得ます。つまりギャングメンバーはそれ自体がギャングブロックかもしれません。 それ故に、全てのギャングブロックは、根および全ての内部のノードがギャングヘッダーである木です。また、葉はユーザーデータを含む普通のブロックです。 ギャングツリーの根はギャングリーダーと呼ばれます。

ギャングブロックへのあらゆる操作(読み込み、書き込み、解放、要求)遂行のため、zio_gang_assemble()はまず、ギャングリーダーとその下の全てのギャングヘッダーを再帰的に読むことにより、オリジナルの論理I/Oのio_gang_treeフィールドにギャングツリーを組み立てます(データリーフを除く)。 これは、全てのギャングヘッダとその全てのギャングブロックを構成するためのbpを含む、コア内ツリー(in-core tree)を生成します。

今しがた組み立てられたギャングツリーで、zio_gang_issue()はそのギャングツリーを走査し、各bpのコールバックを呼びます。 ギャングブロックの解放のために、zio_gang_issue()zio_free_gang()zio_free()のちょっとしたラッパーを各bpに対して呼びます。 zio_claim_gang()は、同様にzio_claim()のラッパーを提供します。 zio_read_gang()は、ギャングヘッダの読み込みを除くzio_read()のラッパーです。なぜならば、それらはio_gang_treeで既に持っているからです。 zio_rewrite_gang()は、上述したようなギャングヘッダのblk_cksumを更新するために、データのzio_rewriteを、あるいはギャングヘッダについては、ギャングヘッダのzio_reqrite()とデータのzio_checksum_compute()を実行します。

two-phase assemble/issueモデルは部分的失敗の問題─もし、ギャングブロックの一部を読んでいた時に、別の部分へのギャングヘッダが読めなかったらどうなるでしょうか?─を解決します。 解放や確保、書き込みの実際の作業の開始前に、まずギャングツリー全体を組み立てる事は、全ての必須のギャングヘッダーI/Oの成功を保証します。 いったんギャングツリーが組み立てられれば、解放と確保はメモリに対する操作となり、失敗する事はありません。

ギャング書き込み失敗イベントでは、全ての確保済み領域を直ちに解放(すなわち空き領域マップの後ろに追加)するために、zio_dva_unallocate()がギャングツリーを走査します。 これは、あてにならないデバイス(flaky device)が原因のサスペンド・レジュームが繰り返されている間に、ENOSPCを受け取らない事を保証します。

ギャングの再書き込みはsync-to-convergenceの間にだけ発生します。 ギャングツリーを組み立てられなかった場合、そのブロックが変更される事はありません。従って、その解放は安全に保留する事が出来ます。(知っての通りブロックはまだ無傷です。) ギャングツリーを組み立てる事が出来た場合、たとえ再書き込みのうちの幾つかが失敗しても、zio_dva_unallocate()は構成要素の各bpを解放し、次の同期パスで新しいブロックを確保する事が出来ます。

全てのケースにおいて、ギャングツリーは部分的失敗からの完全な復旧に対応します。




  • blog/2014/2014-11-09.txt
  • 最終更新: 2020-12-03 16:10
  • by Decomo