[DESIGN]ADVANCED

special vdevで一度プールを飛ばした話

「余っているNVMeをspecial vdevにすればHDDプールが速くなる」— 自宅のhomenasで実際にこれをやって、単一障害でプール自体を失いかけたことがあります。special vdevはL2ARC/SLOGと違い、失えばプール全体を道連れにする本番のメタデータ格納先です。冗長性の鉄則、容量見積もり、そして「後から外せると思っていたら外せなかった」問題を、実際の失敗談ベースで書きます。

自宅のhomenasは8台のExos X18(RAIDZ2)で組んでいますが、一度、余っていたNVMeを1本だけspecial vdevとして足したことがあります。当時の自分は、L2ARCやSLOGを追加する感覚の延長で「メタデータ参照が速くなるならお試しでいいか」くらいに考えていました。数週間後、そのNVMeがS.M.A.R.T.エラーを吐いて逝ったとき、RAIDZ2側のディスク8台は全部健全なのに、プール全体がUNAVAILになりました。バックアップからの復旧で数時間潰した苦い記憶があります。

special vdevは、L2ARCやSLOGとは根本的に性質が違います。この記事はその違いと、自分がやらかした3つの誤解(冗長性、容量見積もり、後から外せる問題)を、当時の失敗を軸に整理します。

special vdevが格納しているもの

special vdevは、プールのメタデータ(dnode、間接ブロックなど)を格納する専用のallocation classです。追加すると、以降のメタデータ書き込みは自動的にそちらへ向かいます。

zpool add tank special mirror /dev/nvme0n1 /dev/nvme1n1

special_small_blocksを設定すれば、指定サイズ以下の小さなブロックもspecial vdev側に寄せられます。

zfs set special_small_blocks=32K tank/vmstore

HDDプールでディレクトリ走査やメタデータ参照が遅いと感じているなら、これをNVMeに逃がすだけで体感速度はかなり変わります。ここまでは正直、L2ARC/SLOGの導入と大差ない発想です。問題は次です。

なぜL2ARC/SLOGと同じ感覚で扱ってはいけないか

L2ARCが壊れてもキャッシュミスが増えるだけ、SLOGが壊れてもin-pool ZILにフォールバックするだけで、どちらもプールは生き残ります。「あれば速い、なくても壊れない」タイプのデバイスです。

special vdevはこの前提が成り立ちません。メタデータはプール上の全データへの索引そのものであり、オプションではないからです。冗長性のないspecial vdevが1台故障すれば、データ側がRAIDZ2やRAIDZ3で守られていようと、索引ごとプールを失います。自分のhomenasで起きたのはまさにこれで、RAIDZ2の耐障害性は一切関係なく、NVMe1本の寿命にプール全体の可用性を賭けていたことになります。

冗長性はプール中で一番厳しいレベルに合わせる

special vdevの冗長性は、プールを構成するデータvdevの中で最も高いものと同等以上にするのが原則です。RAIDZ2なら2重障害に耐える構成(3-wayミラーが安全側)、RAIDZ3ならさらに上が要ります。単純な2-wayミラーのデータvdevに対してなら2-wayミラーのspecial vdevで釣り合いますが、一番手厚いデータvdevより弱いspecial vdevを作ってしまうと、そこがプール全体の弱点になります。

zpool add tank special /dev/nvme0n1

冗長性のないデバイスを足そうとすると、ZFSはレプリケーションレベルのミスマッチを警告してきます。自分がやったのはまさに、この警告を-fで黙らせて突っ込んだパターンでした。警告が出た時点で立ち止まって理由を確認すべきで、-fは本当に最後の手段だと今は思っています。

容量はデータ量ではなくファイル数で決まる

special vdevの必要容量は、プールの総容量に単純比例しません。メタデータ量は格納されているファイル/ブロック数に強く依存するため、同じ総容量でも小さいファイルが大量にあるワークロード(バックアップ、メールサーバー、大量のVMイメージ)ほど跳ね上がります。「プール容量の0.3〜数%」という目安をよく見かけますが、小ファイル中心のワークロードやspecial_small_blocksを積極的に使う設定では、この目安を平気で超えてきます。事前見積もりだけに頼らず、zpool listzpool iostat -vで使用率を継続的に見ておくべきです。

溢れても壊れないが、気づきにくい形で劣化する

special vdevが満杯になると、ZFSは新規のメタデータ/small block書き込みを通常のデータvdev側へ静かにフォールバックさせます。プールは壊れませんし、書き込みも失敗しません。ただし、それまで得ていた高速化効果はじわじわ失われ、体感速度はspecial vdev導入前に戻っていきます。エラーも警告も出ないので、「最近またディレクトリ一覧の表示が遅い気がする」くらいの曖昧な違和感でしか気づけないことが多いです。

さらに厄介なのは、一度データvdev側に溢れたメタデータは、special vdevに空きができても自動的には戻らないことです。special_small_blocksの変更も同様に、既存データには遡及せず新規書き込みにしか効きません。容量に余裕を持たせて導入し、使用率が上がってきたら早めに拡張する、という前倒しの運用が前提になります。

「後から外せばいい」は大抵の構成で成立しない

special vdevはzpool removeの対象に含まれており、原理上は取り外せます。ただし実務で成立する条件はかなり厳しく、次をすべて満たす必要があります。

条件内容
ashiftの一致削除対象のvdevと、残る通常allocation class側の全vdevのashiftが一致していること
RAIDZ非混在通常allocation class側にRAIDZ/dRAID構成のvdevが1つでも含まれていないこと
退避先の空き容量special vdev上のデータを書き戻すのに十分な空きが残るvdev側にあること

2つ目が致命的です。RAIDZ/RAIDZ2/RAIDZ3でプールを組んでいるなら、special vdevは事実上外せません。SLOGやL2ARCのつもりで「ダメなら後でzpool removeすればいい」と考えて導入すると、RAIDZ系プールが主力の環境ではその選択肢が最初から存在しないことになります。片道切符だと思って導入前に判断してください。

結局、何を確認すればよかったのか

自分のhomenasの件で言えば、確認すべきだったのは1点だけでした。「このspecial vdev、単体で壊れたらプールごと死ぬのに、なぜミラーにしていないのか」。これさえ自問していれば、警告を-fで黙らせる前に手が止まったはずです。

  • special vdevの冗長性は、プール中で最も冗長性の高いデータvdevと同等以上か
  • zpool addのミスマッチ警告を-fで黙らせていないか
  • 容量見積もりはファイル数/ブロック数ベースか。小ファイル中心なら目安より大きく見る
  • zpool list/zpool iostat -vでの使用率監視を運用に組み込んでいるか
  • RAIDZ系のプールでは、special vdevは事実上取り外せないと理解した上での導入か

RAIDZ本数やashiftの選択と同じく、special vdevもプール作成時点の判断がその後の選択肢を狭めます。うちのhomenasはこの一件のあと、special vdevを3-wayミラーに組み直しました。