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を解放し、次の同期パスで新しいブロックを確保する事が出来ます。
全てのケースにおいて、ギャングツリーは部分的失敗からの完全な復旧に対応します。
ZFS Fragmentation issue – examining the ZILの翻訳
前回に引き続き、ZFSのフラグメンテーションを調査してて有益な記事を見つけた。これまた筆者のThomas Gouverneur氏に翻訳の許可を貰ったので翻訳してみる。
翻訳には細心の注意を払っていますが、誤訳や変な箇所があればご指摘下さい。例によって、本記事を利用した事で生じた如何なる結果について、翻訳者は責を負いかねます。また本翻訳について原著者への問い合わせはご遠慮願います。
ZFSの断片化問題 - ZILの分析
Original Post: ZFS Fragmentation issue – examining the ZIL
Author: Thomas Gouverneur
Date: June 9, 2011, 2:32 pm
このところ私は、重いデータベースで使われている幾つかのZFSプールのトラブルシューティングに取り組んでいました。 顧客から報告された問題は、クラスタの再起動を最後に性能が著しく低下したというものでした。 この問題は遂に解決し、そして原因はZFSの断片化問題だと特定するに至りました。
まず最初に、ドリルダウン方式でその性能問題を調査しました。TeamQuestを用いて現在と前週の違いを視覚化し私たちが見たのは、データベースのDBFがあるプールのI/Oの、文字通りの爆発でした。 ある時は、その病んだプールの異なるvdevを均等に横切る50K IOPS以上の書き込みがありました。
プールは実際に次のように構成されていました:
# zpool status i-ora-pro06-dat1-pl pool: i-ora-pro06-dat1-pl state: ONLINE scrub: none requested config: NAME STATE READ WRITE CKSUM i-ora-pro06-dat1-pl ONLINE 0 0 0 mirror-0 ONLINE 0 0 0 c6t60060E800571FC00000071FC000020C3d0 ONLINE 0 0 0 c6t60060E800570FB00000070FB000020C3d0 ONLINE 0 0 0 mirror-1 ONLINE 0 0 0 c6t60060E800571FC00000071FC000020C4d0 ONLINE 0 0 0 c6t60060E800570FB00000070FB000020C4d0 ONLINE 0 0 0 mirror-2 ONLINE 0 0 0 c6t60060E800571FC00000071FC000020C5d0 ONLINE 0 0 0 c6t60060E800570FB00000070FB000020C5d0 ONLINE 0 0 0 mirror-3 ONLINE 0 0 0 c6t60060E800571FC00000071FC000020C6d0 ONLINE 0 0 0 c6t60060E800570FB00000070FB000020C6d0 ONLINE 0 0 0 errors: No known data errors
背後にあるSANディスクは大量のI/Oを捌くことが可能で、またSANはあらゆる不具合について点検がなされましたが異常はなく、この問題はLUNにおける大量のI/O処理負荷でした。
続いて、私たちの考えの裏付けを取るためにzpool iostat -v i-ora-pro06-dat1-pl 2
を実行し続けました。
これにより、vdevに対する激しい書き込みを確認しました。
再びTeamQuestで、私たちはディスクに行われた書き込み操作の種類を見る事ができ、実際に見たところ、それらは非常に小さなブロックの書き込みでした。
その後、私たちはオラクル-SUNサポートのサポートケースを開き、私たちが向き合う問題が露見している幾つかのGUDSトレースをアップロードしました。 本問題の検出・修正方法はもちろん、完全な説明を以下に述べます。
基本的に、この問題は“ZFS断片化”と呼ばれています。 ZFSが話題の利用可能なドキュメントで断片化の内部について言及した物はありませんが、これはどうして起るのでしょうか? 無論、ZFS断片化の検出や解決のためのツールはありません。
断片化が如何にして、利用が適さないまでにプールを退化させ得るかを少しばかり理解するために、私はZIL (ZFS Intent Log)とZILのプール内での扱われ方について、今から話します。 また、プールを使うOracleデータベースがどのように振る舞うかについての、いくつかの基礎的な情報から始めます。
1. Oracleデータベースの挙動
問題のあるZFSプールで実行中のデータベースは、私の顧客企業内で最も重要なDBのひとつでした。 それは“リアルタイム”データベースと見なされますが、それはつまり大量のINSERTが毎秒行われる事を意味します。 このデータベースはまた、その会社のビジネスの上で非常にクリティカルな事柄をモニターしていました。 それらの関係は、フロントエンドでのINSERT指示とデータベースへの実際の反映の間で、1時間の遅れがあるというものでした。
Oracle DBの階層では、データベース書き込みスレッド群が、ディスクの大変なサービス時間が原因で書き込み操作を抱えている事実を除き、特に何も見られませんでした。
Oracle DB自身は、ある意味ではあらゆる書き込み操作後にディスクを使用しますが、データベースがfsync()
を呼ぶと、OSによる書き込み操作のディスクへの直接コミットが生じます。
これは全てのOracleデータベースについて言えます。
2. ZILの挙動
既にご存知の方もいるでしょうが、ZFSはUFSファイルシステムの古いジャーナルをZIL (ZFS Intent Log)と呼ばれるものに置き換えました。 要するに、プールに対する全ての操作は、プール操作のリクエスタの完了を保証するためにZILトランザクションを引き起こします。 従って、書き込み操作の場合、実行予定の全ての操作を含む1つのZILブロックが作成され、このZILトランザクションが完了するとすぐに、リクエスタはリクエストの成功を告げられます。たとえ実際にはデータのプールへの書き込みが未完了だったとしてもです。 ZILトランザクションは、処理が適切に行われたであろう事を保証するでしょう。
ZILトランザクションのコミット後、関連するZILブロックは解放され、このZILトランザクションによってディスクに確保された一時領域は放出されます。 ZILレベルで思いがけず生じる他のメカニズムもありますが、それらは本記事の後でカバーします。
ZFSファイルシステムのアンマウント毎にZILはチェックされ、ディスクへコミットされ、そして完全に解放されます。 マウント毎にZFSはZILエントリーの存在を確認しますが、エントリーがあれば、それはファイルシステムが正しくアンマウントされなかった事を示し、ファイルシステムがマウントされる前に見つかったZILエントリーはコミットされます。
リクエスタがfsync()
を発行すると、ZILトランザクションはワークメモリの代わりに強制的にディスクに書き込みされるという事も知っておくと良いでしょう。
3. どのように断片化が起こるのか?
データベースが大量のINSERTを行い続けると、大量のZILトランザクションの作成、プールのディスクへの一時的な書き込み、そして削除に直結します。 とても小さいブロックの大量確保と解放がZILトランザクションのために発生し、それがディスクに連続して書かれるべき本来のデータに対して何の役にも立たないであろうことは、容易に理解出来ます。 それでも、これは問題ではありません。
それらの実行時間の果てに、ZILのトランザクションエントリーに連続ブロックを確保出来なくなる時が訪れます。 そうして、ギャングブロック機構が働きます。 ギャングブロックはZILの一部で、そしてまさに断片化したZILブロックです。
1つのギャングブロックは、1つのヘッダと最大3つのギャングメンバーから成ります。 ヘッダは、ZILトランザクションのデータを実際に保持するギャングメンバーへのただのポインタです。 ヘッダはただ1つのブロックのみを消費し、それ故にプールの断片化レベルにかかわらず確保することが出来ます。 他に留意すべき事は、ギャングブロックは入れ子となり得るため、ギャングメンバーもまたギャングブロックから構成することが出来ます。
ギャングブロック使用中、ZILは必要とされるたびに、ギャングツリー全体のメモリへの読み込みを強いられます。 ZILトランザクションがディスクに反映される時、ギャングブロックの削除は各ギャングメンバーとその子供の削除を意味します。
これが断片化の生じる場所です!
ディスクに確保と解放が行われる全てのZILトランザクションは、ZILエントリーおよびプールのデータの間に隙間を生じます。
我々のzpool iostat
が以前に報告した膨大な書き込み操作数についても説明します。
これら書き込み操作はディスクを読み込み、そしてアプリケーションが使用可能なプールの実帯域幅を狭めます。
4. 発生中の問題はどのように見えるか?
問題を見るために、私たちはZILがどのように作用しているか見るべきです。 私たちが通常の方法でZILトランザクションの生成と削除を見るだけでは、どんな極度の断片化の発生も推定する事は出来ません。 別の方法で、私たちはより深くへ行くことが可能です。
実際にギャンギングがシステム全体のレベルで問題を起こしているかどうか見つけ出すには、lockstat
を使います:
# /usr/sbin/lockstat -CcwP -n 50000 -D 20 -s 40 sleep 2
ギャングブロックによる問題がシステムに本当にあるならば、あなたは次のような項目を目にするでしょう:
# grep gang lockstat-* lockstat-C.out: 32768 |@@ 26 zio_gang_tree_assemble_done+0x74 lockstat-C.out: 262144 | 7 zio_gang_tree_assemble_done+0x74 lockstat-C.out: 4096 |@@@@@ 146 zio_gang_tree_issue+0x78 lockstat-C.out: 8192 |@@@@@@@@@@@@@@@@@@@@@ 586 zio_gang_tree_issue+0x78 lockstat-C.out: 16384 | 13 zio_gang_tree_issue+0x78 lockstat-C.out: zio_gang_tree_issue+0x78 lockstat-C.out: zio_gang_tree_issue+0x78 lockstat-C.out: zio_gang_issue+0x48 lockstat-C.out: 2048 | 14 zio_gang_tree_issue+0x78 lockstat-C.out: 4096 |@@@@@@@ 192 zio_gang_tree_issue+0x78 lockstat-C.out: 8192 |@@@@@@@@@@@@@@@@@@@@ 496 zio_gang_tree_issue+0x78 lockstat-C.out: 16384 | 1 zio_gang_tree_issue+0x78 lockstat-C.out: zio_gang_tree_issue+0x78 lockstat-C.out: zio_gang_issue+0x48 lockstat-C.out: 2048 |@@ 11 zio_gang_tree_assemble_done+0x74 lockstat-C.out: 16384 |@@@@ 18 zio_gang_tree_assemble_done+0x74 lockstat-C.out: 4096 |@ 2 zio_gang_tree_assemble+0x5c lockstat-C.out: 8192 |@@@@@@@@@@ 15 zio_gang_tree_assemble_done+0x74 lockstat-C.out: 65536 | 0 zio_gang_tree_assemble_done+0x74 lockstat-C.out: 2048 |@@ 3 zio_gang_tree_assemble+0x5c lockstat-C.out: 4096 |@@@@ 6 zio_gang_tree_assemble_done+0x74 lockstat-C.out: 32768 | 2 zio_gang_tree_assemble_done+0x74 lockstat-C.out: 262144 | 1 zio_gang_tree_assemble_done+0x74 lockstat-C.out: zio_gang_tree_assemble_done+0x74
この時点から、実際にギャングブロックの問題を持っていると認められます。
5. 問題の修正
この問題を完全に直すには、UFSの断片化したファイルシステムがそうであったように、正攻法はプールを再作成する事です。 問題の再発を防ぐための解決方法は、ミラー(もしくは非ミラー)のログデバイスをプールに追加します。これによりZILトランザクションはログデバイスに書き込まれ、プールのデータ用vdevの断片化を避けられます。
このケースが上手く動くのは、追加したログデバイスがオンラインになり、進行中のZILトランザクションがフラッシュされるまで少し待ち、そして新規トランザクションが独立したデバイスへ書き込まれるようになってからです。
注意: この解決策は性能向上の助けとなりますが、ZILが引っ掻き回した後のゴーダチーズのようなデータvdevのデフラグにはなりません。 今のところは、直面した問題への手助けとなっています。プール再作成にはダウンタイムを要しますが、デバイスの追加はそうではないので。
6. Proactive actions
プールのギャングブロックの使用状況について時々システムを監視することで、あなたは積極的にこの問題に対して取り組むことが可能となります。
# dtrace -qn 'fbt::zio_gang_tree_issue:entry { @[pid]=count(); }' -c "sleep 300" 26574 61 0 7141 26575 18949 26570 416399 # ps -eaf|egrep "26574|26575|26570" root 26574 0 0 May 26 ? 2778:02 zpool-i-ora-pro06-arc1-pl root 26570 0 0 May 26 ? 9155:49 zpool-i-ora-pro06-dat1-pl root 26575 0 0 May 26 ? 574:51 zpool-i-ora-pro06-rdo1-pl #
注意: 時としてギャンギングは平静を装いますが、これらプールはまだギャンギングの影響は出ていないようです。 しかし、実際にギャンギング問題が出たプールを特定するための多少の経験になったかもしれません。そして、ZIL断片化が本当の性能問題に変わる前に、先を見越してプールにログデバイスを追加する措置を取るのが良いかもしれません。
7. 謝辞
- Openindiana source code
- #opensolaris-fr @ freenode
8. 参考
Effects of ZFS Fragmentation on Underlying Storageの翻訳
ZFSの断片化を調べていたら、英国ケント大学のITインフラチームのblogに事例が載っていた。原文へのリンクを張る事を条件に翻訳の許可を貰ったので訳してみた。
@DecomoZ sure, but please refer to the original post
— Unseen IT at Unikent (@UnikentUnseenIT) 2014, 11月 2
翻訳には細心の注意を払っていますが、誤訳や変な箇所があればご指摘下さい。例によって、本記事を利用した事で生じた如何なる結果について、翻訳者は責を負いかねます。また本翻訳について原著者への問い合わせはご遠慮願います。
ZFSの断片化が及ぼす下層ストレージへの影響
以前よりZFSの断片化の影響は知られていますが、それは典型的にプールの使用率が80%を超えると問題化します。こうした事実にも関わらず、我々は断片化の影響が、負荷量によって使用率70~90%のどこでも出始める事例を見てきました。 大抵は、性能の低下および空き領域の小さな塊の探索、ならびに結合で必要となるディスク書き込みが引き起こすIO負荷の増大を目にする事になります。 ここに目を覆いたくならんばかりの技術的詳細の良い要約があります。
本問題への適切な対処は独立のZFSインテントログ(ZIL)デバイスを持つ事ですが、我々が持つZFSルートディスクを利用する物理・仮想サーバの数的に現実的ではありません。 また残念ながら、それは問題のある既存プールの修復にはなりませんし、そしてZFSのデフラグツールは存在しません。 問題を解決するためには、あなたはデータを削除するか、追加ストレージをプレゼントする必要があります。
次のグラフはこの問題を簡潔に示しています。 下記のボリュームは競合する2つの仕事をしていました──1つが大量の読み込み(緑)で、もう1つが少量の書き込みI/O(青、マイナス軸)です。 12:00に新しいボリュームがプールに追加されました。
この時点で、既存の断片化したディスクへの書き込みは止まり、そして読み込みジョブのために2~300 IOPSを開放しました(今やCPUバウンドに達しています。) 下のグラフで、あなたは新しい断片化されていないボリュームに対する劇的に削減された書き込みオペレーションを見る事ができます:
~200 IOPSが~4 IOPSに下がった訳ですが、書き込みオペレーションが50倍に増幅されていたのです。 さらに、RAID-6の書き込みによって余分なI/Oが発生し、下層のTier-3のSATAディスクは極めて激しく動かされていました。
当然、このファイルシステム(および本RAIDグループを共有している他の物)の性能は著しく向上しています。