[DESIGN]INTERMEDIATE

ashiftを間違えたまま2年運用していた

初代`homenas`は512eディスクの自動判定ミスで`ashift=9`のまま組んでしまい、気づいたのは2年後でした。RAIDZ1/2/3の選択、vdev幅、ashift — この3つはプール作成の瞬間に固定され、後からディスク1本足すような気軽さでは直せません。resilverリスクとashiftの不可変性という実務上の制約から整理します。

初代のhomenasを組んだとき、当時使っていたコンシューマ向け2TB HDDが「512e」(物理4K/論理512バイト申告)だと知らず、ZFSの自動判定に任せてプールを作りました。結果はashift=9。書き込みがなんとなく重い気はしていたものの、原因に気づいたのは2年後、zdb -Cをたまたま叩いたときでした。直すには新しいプールを作ってデータを丸ごと移送するしかなく、週末を丸々溶かしました。

RAIDZの設計判断のほとんどは、プールを作成した瞬間に固定されます。RAIDZ1/2/3の選択、vdevに束ねるディスク本数、ashift値 — このいずれも、後から「やっぱり変えたい」となってもディスクを1本足すような簡単さでは直せません。この記事では、この3つの決定をどう考えるべきかを、実務の判断基準として整理します。

パリティ数だけでRAIDZ1/2/3を選んではいけない

RAIDZ1/2/3の差は、単純に「何本まで同時故障に耐えられるか」(それぞれ1本・2本・3本)ですが、実務での選定基準はそこに留まりません。もう一つの軸は resilver中の脆弱性 です。

RAIDZ1でディスクが1本故障すると、プールは冗長性ゼロの状態でresilverを行います。この間に生き残っているディスクのどこか1か所でもUnrecoverable Read Error(URE)が発生すると、そのブロックは修復不能になります。大容量ディスク(現在主流の8TB超)では、resilverに要する時間そのものが長くなる上、UREの発生確率はディスク上の総ビット数に比例して上がるため、「resilver中にもう1本失う、あるいは部分的なUREに当たる」リスクが無視できない水準になっています。

pool: tank
state: DEGRADED
status: One or more devices could not be used because the label is missing...
action: Determine if the device needs to be replaced...
scan: resilver in progress since ...

この構造的な弱さから、現在の実務では新規構築のRAIDZは基本的にRAIDZ2以上という判断が一般的になっています。RAIDZ1が許容できるのは、頻繁にバックアップを取得している検証環境や、データ喪失時の許容度が高い一時領域など、限定的なケースにとどめるのが安全です。RAIDZ3は、ディスク本数が多い(20本超など)広いvdevや、resilverに丸1日以上かかるような大容量ディスク構成で検討します。

構成同時故障許容主な用途
RAIDZ11本検証環境、頻繁なバックアップがある一時領域
RAIDZ22本一般的な本番構成のデフォルト
RAIDZ33本大容量ディスクの広いvdev、resilverが長時間化する構成

vdevの幅: なぜ「広ければ広いほど良い」わけではないか

容量効率だけを見ると、パリティ本数を固定したまま1vdevのディスク本数を増やすほど、usable容量に対するパリティのオーバーヘッド比率は下がります。しかし、実務では以下の理由からvdev幅を無制限に広げることは推奨されません。

  1. resilver時間が伸びる。 vdev内のディスク本数が増えるほど、1本のディスク交換時に読み出す必要のあるデータ量(生き残っているディスク側の総容量)が増え、resilver完了までの時間が延びます。resilverが長引くほど、その間に追加障害が起きるリスクにさらされる時間も長くなります。
  2. IOPSはvdev本数に依存する。 RAIDZ vdevは、ディスク本数を増やしても1vdevあたりのランダムIOPSは単体ディスク1台分にとどまります。vdevを広くしてディスク総数を1vdev に集約するほど、プール全体のIOPSはむしろ伸びなくなります。IOPS要件がある構成では、広い1vdevより、狭いvdevを複数束ねる方向で設計すべきです。
  3. 障害の「まとまり」が大きくなる。 vdev内で許容量を超える故障が起きると、そのvdev全体、ひいてはプール全体が失われます。ディスク本数が多いvdev1本に依存する構成は、障害の影響範囲(blast radius)を1か所に集中させることになります。

実務でよく使われる目安は、RAIDZ1は3〜5本前後、RAIDZ2は6〜10本前後、RAIDZ3は11〜15本前後です。これを超える本数が必要な場合は、1つの広いvdevにするのではなく、同じ冗長レベルのvdevを複数作って束ねる構成を優先的に検討します。

# 悪い例: 20本を1つのRAIDZ2 vdevに詰め込む
zpool create tank raidz2 disk1 disk2 ... disk20

# 良い例: 10本ずつ2つのRAIDZ2 vdevに分け、プール内でストライピングする
zpool create tank raidz2 disk1 .. disk10 raidz2 disk11 .. disk20

後者の構成は、パリティディスクの総本数は同じ4本(RAIDZ2 × 2vdev)のまま、IOPSはvdev本数(2本)ぶん伸び、1回のresilverで読み出す必要のあるデータ量もvdevあたり半分に抑えられます。容量効率を優先するあまり1vdevに詰め込みすぎるのは、性能と復旧リスクの両方を犠牲にする典型的な設計ミスです。

ashiftはプール作成後には変更できない

ashiftは、ZFSがディスクに対して行うI/Oの最小単位(セクタサイズ)を2の指数で表したプロパティです。ashift=9は512バイト、ashift=12は4096バイト(4K)を意味します。この値は vdevを作成した瞬間に固定され、後から変更できません。 初代homenasでやったのが、まさにこれの踏み方です。

問題は、ZFSがashiftを自動判定する際に、ディスクが自己申告する論理セクタサイズと物理セクタサイズが食い違うケースがあることです。いわゆる「4Kn」(物理4K/論理4K)ディスクでは問題になりにくい一方、当時使っていたような多くのコンシューマ向けディスクは「512e」(物理4K/論理512)で、論理セクタサイズを512と申告します。ZFSが論理側の申告を鵜呑みにしてashift=9でプールを作成してしまうと、実際の物理4Kセクタに対して512バイト単位の書き込みが発生するたびにread-modify-writeが起き、書き込み性能が大きく低下します。

プール作成前に、ディスクの物理/論理セクタサイズを確認しておきます。

lsblk -o NAME,PHY-SEC,LOG-SEC

PHY-SEC(物理セクタサイズ)が4096なのにLOG-SEC(論理セクタサイズ)が512と表示される場合は要注意です。判定を自動検出に任せず、明示的にashift=12を指定してプールを作成するのが安全です。

zpool create -o ashift=12 tank raidz2 disk1 disk2 disk3 disk4 disk5 disk6

現行世代のディスク(HDD/SSDとも)であれば、実務上ashift=12(4K)を明示指定しておけばまず問題ありません。将来的に4Kより大きなセクタサイズのメディアを混在させる可能性がある場合のみ、ashift=13(8K)以上を検討します。作成済みのashiftはzdbで確認できます。

zdb -C tank | grep ashift

もう一つの実務上の注意点は、同じvdev内にセクタサイズの異なるディスクを混在させないことです。あるvdev内の1本だけが実は8Kセクタのメディアだった、というケースでは、そのvdev全体のashiftをそのディスクに合わせて引き上げる必要があり、後から気づいても手遅れです。プール構築前に、投入予定の全ディスクのセクタサイズを揃えて確認しておくことが、この落とし穴を避ける唯一の方法です。

今のhomenasで必ずやっていること

2代目を組み直してからは、プール作成前に次を必ず確認するようにしています。

  • 想定する同時故障許容数(RAIDZ1で足りるか、RAIDZ2/3が必要か)。新規構築ではRAIDZ2以上をデフォルトにする
  • ディスク容量とresilver時間の見込み(大容量ディスクほどRAIDZ1のリスクは大きい)
  • IOPS要件の有無(要件があるなら、広い1vdevより狭いvdevの複数束ねを優先)
  • vdev幅の目安(RAIDZ1: 3〜5本、RAIDZ2: 6〜10本、RAIDZ3: 11〜15本を超えるならvdev分割を検討)
  • 全ディスクの物理/論理セクタサイズ(lsblk -o NAME,PHY-SEC,LOG-SEC)
  • ashiftの明示指定。自動判定に任せず-o ashift=12を基本にする
  • vdev内でセクタサイズが異なるディスクを混在させていないか

zdb -C tank | grep ashiftは、プールを作った直後に必ず1回叩くようにしています。2年後に気づくよりずっとマシです。