ハードウェア上級 #4 ZFS 深掘り — RAID とファイルシステムがひとつになるとき

読了 10分

中級 6 編 で RAID 運用の暗い面を扱いました。リビルドがもっとも危険な時間であること、大容量ディスクでは URE が RAID5 を脅かすこと、スクラブなしでは不良セクタが最悪の瞬間まで隠れていることまでです。その記事で名前だけ登場した ZFS は、これらの問題の多くを運用テクニックではなく 構造 で解決しました。今回は ZFS が何をどう違う形で設計したのか、そしてその代償として何を要求するのかを整理します。

従来スタックの問題 — 層が分かれると情報も分かれる #

従来のストレージスタックは層で積み上がります。いちばん下で RAID カードや mdadm がディスクを 1 つのブロックデバイスにまとめ、その上で LVM のようなボリュームマネージャーが空間を分け、いちばん上で ext4 や XFS のようなファイルシステムがファイルを載せます。LVM 自体は RHEL 運用の領域なので、ここでは名前だけにとどめます。核心は、各層が互いの事情を知らないという点です。

  • RAID 層はどのブロックにデータがあるかを知りません。 そのためリビルドは空きブロックまで含めてディスク全体をコピーします。10% しか使っていないアレイでも、リビルド時間は 100% 使ったアレイと同じです。
  • 書き込みホール (write hole) が生まれます。 RAID5 の 1 つのストライプを更新するにはデータブロックとパリティブロックの両方を書く必要がありますが、その間に電源が落ちるとパリティがデータとずれたまま残ります。RAID 層はファイルシステムのトランザクションを知らないので、どの書き込みがひとまとまりなのか知るすべがなく、ずれたパリティは次のリビルドで誤ったデータを作り出します。
  • 静かな破損を捕まえられません。 ディスクがエラー報告なしに誤ったビットを返すと (ビットロット)、RAID 層はそれをそのまま上に渡します。パリティという答え合わせの紙を持っていながら、普段の読み取りでは照合しないためです。

各層は自分の仕事を誠実にこなしますが、層の間の情報断絶が構造的な隙間を作ります。

ZFS の答え — 1 つの層に統合し、その場で上書きしない #

ZFS は RAID、ボリューム管理、ファイルシステムを 1 つのソフトウェアに統合しました。ディスクを vdev (仮想デバイス) にまとめ、vdev をプール (pool) に合わせ、その上にファイルシステムを作りますが、この全部が 1 つの層なので、どのブロックが生きているか、いま書いているブロック群が 1 つのトランザクションなのかを、ファイルシステムが RAID レベルまで把握しています。コマンドで見ると、従来スタックの複数の手順が 2 行に縮みます。

# ディスク 6 本をパリティ 2 本の RAIDZ2 プールにまとめる
zpool create tank raidz2 sda sdb sdc sdd sde sdf

# プールの上にファイルシステムを作る (フォーマット・マウント設定は別途不要です)
zfs create tank/data

パーティショニング、RAID 構成、フォーマット、fstab 登録がそれぞれ別のツールに分かれていた作業が、プール作成とファイルシステム作成で終わります。作ったファイルシステムは即座にマウントされて使えます。

ここに CoW (Copy-on-Write) が加わります。ZFS はデータをその場で上書きしません。修正する内容を空きブロックに新しく書き、書き込みが終わってからポインタを新しいブロックに切り替えます。ポインタの切り替えはアトミックなので、電源がどの瞬間に落ちても、ディスクには常に一貫した状態 (切り替え前か切り替え後) だけが残ります。更新の途中という中途半端な状態がディスクに存在しないため、書き込みホールが根本から消え、ブート後に fsck でファイルシステムを点検する必要もありません。

チェックサムと自己修復 — 読み取りのたびに検証する #

ZFS はすべてのブロックのチェックサムを、そのブロック自身ではなく 親ブロックのポインタの隣 に保存します。ブロックを読むたびにチェックサムを照合するので、ディスクがエラー報告なしに誤ったデータを返してもその場で発覚します。チェックサムをデータと同じ場所に置かないおかげで、ブロック全体が見当違いの内容に変わる破損も捕まえられます。

ミラーや RAIDZ のように冗長性のある構成なら、検出だけでは終わりません。チェックサムの合わないコピーを見つけると、別のコピーから正しいデータを読んで応答し、壊れた側をそのデータで書き直して修復します。これが自己修復 (self-healing) です。従来 RAID の「どちらが正解かわからない」状況も、ZFS ではチェックサムという審判がいるので常に判定がつきます。修復の履歴は zpool status の CKSUM 列に残ります。

        NAME        STATE     READ WRITE CKSUM
        tank        ONLINE       0     0     0
          mirror-0  ONLINE       0     0     0
            sda     ONLINE       0     0     0
            sdb     ONLINE       0     0     3

CKSUM カウントが 0 でないディスクは、エラー報告なしに誤ったデータを返したことがあるという意味です。数字が増え続けるなら、ケーブル、コントローラ、ディスクの順に疑い、交換を検討します。従来スタックでは存在自体に気づかず通り過ぎていた破損が、ここでは数値として見えます。

resilver — データだけをコピーするリビルド #

ZFS で死んだディスクを交換すると、リビルドの代わりに resilver が走ります。名前が違うだけではありません。どのブロックが生きているかをファイルシステムが知っているので、resilver は 実際にデータのあるブロックだけ をコピーします。30% 埋まったプールならコピー量も 30% です。中級 6 編で見た「リビルドが長いほど 2 本目の故障と URE の窓が広がる」という問題に対して、窓の長さ自体を縮める答えです。

コピーするブロックごとにチェックサム検証も同時に行います。resilver 中に読み取りエラーに遭遇しても、アレイ全体が崩れるのではなく、該当ブロックが属するファイルがどれかまで特定して知らせてくれます。失う範囲が「どこかのセクタ」から「このファイル」に狭まります。

RAIDZ — パリティ 3 本まで #

ZFS のパリティ構成は RAIDZ1/2/3 で、それぞれパリティ 1・2・3 本に対応します。mdadm の RAID5/6 と比べるとこうなります。

  • RAIDZ1 ≈ RAID5、RAIDZ2 ≈ RAID6 に対応しますが、CoW のおかげで書き込みホールがなく、resilver が速いという違いがあります。
  • RAIDZ3 はパリティが 3 本 です。mdadm には対応物がありません。ディスクが数十 TB に育った時代に、resilver の数日間にさらに 2 本死んでも耐える保守的な選択肢です。
  • 弱点もあります。 RAIDZ vdev 1 つのランダム IOPS はおおよそディスク 1 本ぶんにとどまります。容量効率が必要なら RAIDZ、ランダム I/O 性能が必要ならミラー vdev を複数、という使い分けが ZFS 設計の基本公式です。

表にまとめるとこうです。

構成パリティmdadm 対応書き込みホール復旧方式
RAIDZ11 本RAID5なしresilver (データのみ)
RAIDZ22 本RAID6なしresilver (データのみ)
RAIDZ33 本対応なしなしresilver (データのみ)
mdadm RAID5/61〜2 本-ありリビルド (全体コピー)

ARC とメモリ — ZFS が RAM を多く使う理由 #

ZFS は OS のページキャッシュの代わりに、独自キャッシュの ARC (Adaptive Replacement Cache) を使います。最近使ったブロックと頻繁に使うブロックを同時に追跡するアルゴリズムなのでヒット率が高い代わりに、デフォルトではシステム RAM の半分まで持っていきます。「ZFS はメモリを食う」という評判の正体がこれですが、正確には余っているメモリをキャッシュに使っているだけで、ほかのプロセスが要求すれば返します。ただし返却が即座ではないので、DB のようにメモリを自分で管理するアプリケーションと同じマシンに置くときは、ARC の上限を調整するのが定石です。

RAM が足りなければ、SSD を 2 次キャッシュの L2ARC として追加できます。注意点は、L2ARC のブロックを指すインデックスが RAM に住んでいるため、RAM の少ないマシンに大きな L2ARC を付けるとかえって ARC を侵食することです。RAM を増やすのが先です。なお「1TB あたり 1GB」のような重い要求は重複排除 (dedup) を有効にする場合の話で、一般用途なら 8GB 以上で無理なく回ります。

スナップショットと send/recv — CoW がくれるボーナス #

CoW 構造ではスナップショットはほぼタダです。どのみち古いブロックを上書きしないので、スナップショットは「この時点のポインタを消さないこと」という印 1 つです。作成は即座に終わり、容量も以後変更された分だけしか占めません。そのため ZFS 運用では、時間単位のスナップショットを数十個維持するのが日常の風景です。

スナップショットは zfs send でシリアライズして別のプールや別のマシンに送れますし、2 つのスナップショットの差分だけを送る増分転送もできます。

# スナップショット作成 (即座に終わります)
zfs snapshot tank/data@2026-06-15

# バックアップサーバーへ転送
zfs send tank/data@2026-06-15 | ssh backup zfs recv pool/data

# 翌日からは差分だけ増分転送
zfs send -i tank/data@2026-06-15 tank/data@2026-06-16 | ssh backup zfs recv pool/data

ファイル単位で比較する rsync と違い、変更ブロックを最初から知っているので増分バックアップが速いです。ただし、同じプール内のスナップショットは誤操作からの復旧用であってバックアップではありません。プールが丸ごと死ねばスナップショットも一緒に消えるので、中級 6 編 の結論はここでも有効です。send/recv で 別のマシンに 送ったコピーからがバックアップです。

圧縮 — lz4 は有効にするのが基本 #

ZFS はブロック単位の透過圧縮をサポートします。lz4 は圧縮・展開がとにかく速いので、CPU コストより減るディスク I/O の利得のほうが大きい場合がほとんどで、圧縮の効かないデータは早めに見切るロジックもあるため、損をする状況はまれです。そのため「compression=lz4 はとりあえず有効に」が ZFS コミュニティの長年のデフォルトで、最新の OpenZFS はそもそもデフォルトで有効にします。より高い圧縮率が必要なら、zstd をレベルで調節して選べます。

運用の注意 — スクラブとプール拡張 #

  • スクラブは依然として必要です。 チェックサム検証は読むときにしか起きないので、読まないデータの破損はスクラブが走るまで潜伏します。zpool scrub を月 1 回ペースでスケジュールに入れ、結果が人に届く通知経路まで確認します。
  • プール容量には余裕を持たせます。 CoW は常に空きブロックを探して書く構造なので、プールが満杯に近づくと断片化で性能が急激に悪化します。80〜90% のラインを運用上限とするのが通例です。
  • vdev 拡張には制約があります。 プールに vdev を追加して育てるのは簡単ですが、既存の RAIDZ vdev にディスク 1 本を差し込むのは長らく不可能でした。OpenZFS 2.3 から raidz expansion で可能になりましたが、既存データは古いパリティ比率を保ったまま残り、空間効率が計算より低くなることがあります。数本のディスクで始めて 1 本ずつ育てていく計画なら、最初から vdev 構成を慎重に決めるのが依然として正解です。

まとめ #

今回つかんだ絵です。

  • 従来スタックは RAID・ボリューム・ファイルシステムの層の間の情報断絶のために、書き込みホール、全体コピーのリビルド、静かな破損という隙間を抱えています。
  • ZFS は 3 つの層を統合し、CoW で上書きをなくして書き込みホールを構造的に取り除きました。
  • すべての読み取りがチェックサム検証を通り、冗長性があれば自己修復まで行います。resilver はデータだけをコピーして危険な窓を縮めます。
  • RAIDZ はパリティ 3 本までサポートしますが、ランダム IOPS はミラーが優勢です。ARC のためメモリは余裕を持って確保します。
  • スナップショットはほぼタダですが、同じプール内ではバックアップではありません。send/recv で別のマシンに送ったコピーがバックアップです。

次回 — データセンターの電力 #

ここまでがサーバーの中の話でした。CPU から始まりメモリ、ディスク、そしてそれらを束ねるファイルシステムまで降りてきたので、次の記事「ハードウェア上級 #5 データセンターの電力」からはサーバーの外に出ます。数百台のサーバーが集まる建物に電気がどう入って分配されるのか、UPS と発電機がどの隙間を埋めるのか、電力効率指標の PUE が何を語るのかを扱います。

X