start

空き容量0でZFSが壊れた?Input/Output errorが発生→再起動で直った

不注意でProxmox VEのZFSプールを使い切り、空き容量がゼロという状態になってしまった。すべてのデータセットのAVAILが0という本物のゼロである。VMのディスクがthinで図らずもオーバーコミット状態となっており、VM内で物理容量以上のファイルコピーを行ってしまったのが原因。当然ながらVMは固まるわ、PVEもWebコンソールから何もできないわで超焦った…。

幸い物理コンソールは生きていたので、不要なZVOLを消して事なきを得たと思いきや、ファイル操作をするとInput/output errorが起きるようになってしまった。

root@myserver:/etc/pve/nodes/myserver/qemu-server# touch test
touch: cannot touch 'test': Input/output error

ファイル/ディレクトリの作成、削除がダメ。既存ファイルの読み込みは問題なさそうで、書き込み系がダメっぽい。それもすべての場所でダメというわけではなく、ルートディレクトリ直下は大丈夫だったりする。同じデータセットなのに。

もちろんzpool scrubでエラーが出ないことは確認済み。というわけで実に厄介というかヤバい状況なのであーる。どうすんのこれ…

関係しそうなバグチケ報告もあるにはある。

が、ほとんど関係ない気がしなくもない。うちはサイレントじゃないし。実は静かに壊れてて今回ので発現した可能性もあるが、ほんの数日前にVM追加してるしちょっと考えにくい。FreeBSDの方では何だかんだ10年ほどZFSを使っているが、データが壊れたのはそれなりに原因が分かっている2回しかない(1回目2回目)。

容量ゼロをトリガーにLinux側とZFS側で何らかの齟齬が発生し、容量の回復がLinux側に伝わってないとかが原因なら再起動で直りそうなものの、シャットダウンしたが最後、完全に壊れてPVEが立ち上がらなくなる可能性もありそうで恐ろしい。この記事も書いているメイン環境は、そのPVE上で動いているのでPVEの死=メイン環境の死なので慎重にならざるを得ない。


(2021-11-24 追記)

意を決してPVEマシンを再起動してみたら、Input/output errorは出なくなった。何事もなかったようにVMも動いている。

ZFSではCoWの関係上、一般的に空き容量がプール容量の10~20%1)を切ると危険水域とされている。予めプール全体にquotaをかけておけば、今回のようなヤベェ自体は予防できるだろう。


1)
昨今の2桁テラバイト級のプールなら5%程度でも良さそうだが

OpenZFSにRAIDZ Expansionのプルリクができてた

今まで気づかなかったが、2か月ほど前の6/11に、待望のRAIDZ ExpansionのプルリクがOpenZFSに立てられていた!!2018年のプリアルファコード以来目立った動きがなく、どうなっとんじゃーいって感じだったが、June 2021 FreeBSD Developer Summitでの報告の翌日、PRが作られた模様。

コードレビューは始まったばかり、というかまだ進んでない?ようでリリースはまだまだ先っぽい。OpenZFSプロジェクトの状況としては、現在は2.1リリース作業の真っただ中で、取り込まれるのはどんなに早くとも来年リリースのOpenZFS 2.2あたりと見込まれている。まぁ、かなり大きい変更なのでレビューもテストも時間がかかるだろうしね、仕方ないね。

PRの議論を見るに、拡張時の挙動である「既存データのストライプサイズは変更しない=データ/パリティ比率は変わらない」という点が、結構引っかかってるっぽい雰囲気。

RAIDZ ExpansionでRAIDZ vdevにストレージを追加した場合、vdevの物理ストライプ幅は拡張後のサイズとなる。対する論理ストライプ幅については、既存のデータは拡張前の幅、再書き込みを含む新規データは拡張後の幅となる。具体的な数値を当てはめると、HDD 5本のRAIDZ2プールをHDD 6本に拡張した場合、既存データは論理5ストライプ(データ/パリティ比=3:2)のままで、新規データは論理6ストライプ(データ/パリティ比=4:2)で記録される。データによってストライプ幅が異なること自体は、RAIDZの元からの仕様なので問題ないらしい。

一方で、この仕様のため既存RAIDZプールの使用率が高いほど、RAIDZ Expansionでのプール拡張後の実効空き容量は増えにくくなる。例えば、1TB×4のRAIDZ1プールに1TB書き込むとプール使用率は33%なのに対し、1TB×3のRAIDZ1プールに1TB書き込んだあと(この時点での使用率は66%)で1TBのHDDを追加しても使用率は66%のままとなる。既存データのデータ/パリティ比が変わらない以上、この容量オーバーヘッドは避けられない。

RAIDZ ExpansionでRAIDZプールは何度でも拡張可能だが、こんな感じゆえに、最小構成で始めて必要に応じて後からディスクを追加する、という戦略は取りにくいのは否めない。

使っていく中で既存データも新しいストライプ幅に置き換わり、このオーバーヘッドは徐々に解消されていく。このあたりの挙動は他のプロパティと一緒で、ZFSの思想っぽいというかなんというか。可及的速やかにプール容量を最大効率で増やしたい!という場合には厳しいだろうが、現実的にそんなケースがどれだけあるのだろうか?そもそもZFS的にはそんなカツカツ運用するなよって感じ?

拡張時に全データの再配置を行ってるにもかかわらず、既存データの論理ストライプ幅を変えないは、実装の簡便さと拡張中のRAIDZ1/2/3の冗長性担保が理由とのこと。

このまま無事マージされて欲しい。

デグレったZFSプールは他マシンでのインポートが制限されるっぽい?

色々と事情があって、FreeBSDで作ったHDD×4台からなるRAIDZプールを、HDD×3でデグらせた状態でZoL環境でインポートした。

すると自動scrubが走るわけだが、処理途中で一旦エクスポートし、FreeBSDの方でHDD×4の状態でインポートしようとしたら出来なかった。

# zpool import zdata
cannot import 'zdata': no such pool available

こんなエラーが出るわけ。プールをFreeBSDとLinuxの間で、あまつさえデグレった状態で行き来させたせいで壊れた!?と超焦るわけ。

ZoL環境だと正常にインポートできたのでプールの無事は確認でき、HDD×4に戻してRAID修復が終わるのを待ったところ、FreeBSDの方でも何事もなくインポートできるようになった。

ここから分かることは、どうやらデグレった状態のプールを一度でもインポートすると、プールの健全性が回復するまで別のシステムでのインポートができなくなるらしい?これがZFSの正常な挙動なのか、FreeBSD (Legacy ZFS)とZoLを行き来したが為の特殊挙動なのかは分からない。

もし正式挙動だとしたら、プール修復中にマシンが壊れるとプールが死ぬわけだから、正式挙動ではないとは思うんだけど、実際に起こった事象として記録しておく。

ディスクがどのzpoolに所属していたか調べる

複数のHDDを抜き差ししてると、どのHDDがどのzpoolの構成員か分からなくなる事がある。ちゃんと管理しとけって話だが、そんな時はzdb -lでZFSのラベル情報を表示すればよい。

ProxmoxVE (ZFS on Linux)での実行なので、デバイス名はsdXになっている。

# zdb -l /dev/sdh1
------------------------------------
LABEL 0
------------------------------------
    version: 5000
    name: 'zdata' ★これ
    state: 0
    txg: 23527188
    pool_guid: 15920220212014191793
    hostid: 1525007054
    hostname: 'hostname.example.com'
    top_guid: 1118325231086088749
    guid: 9773797371878116701
    vdev_children: 1
    vdev_tree:
        type: 'raidz'
        id: 0
        guid: 1118325231086088749
        nparity: 1
        metaslab_array: 34
        metaslab_shift: 37
        ashift: 12
        asize: 31989740601344
        is_log: 0
        create_txg: 4
        children[0]:
            type: 'disk'
            id: 0
            guid: 3334618698730764157
            path: '/dev/ada5p1'
            phys_path: 'id1,enc@n3061686369656d31/type@0/slot@3/elmdesc@Slot_02/p1'
            whole_disk: 1
            DTL: 291
            create_txg: 4
        children[1]:
            type: 'disk'
            id: 1
            guid: 4503436449772901953
            path: '/dev/ada3p1'
            phys_path: 'id1,enc@n3061686369656d31/type@0/slot@1/elmdesc@Slot_00/p1'
            whole_disk: 1
            DTL: 290
            create_txg: 4
        children[2]:
            type: 'disk'
            id: 2
            guid: 9773797371878116701
            path: '/dev/ada2p1'
            phys_path: 'id1,enc@n3061686369656d30/type@0/slot@3/elmdesc@Slot_02/p1'
            whole_disk: 1
            DTL: 289
            create_txg: 4
        children[3]:
            type: 'disk'
            id: 3
            guid: 1033141966906037929
            path: '/dev/ada4p1'
            phys_path: 'id1,enc@n3061686369656d31/type@0/slot@2/elmdesc@Slot_01/p1'
            whole_disk: 1
            DTL: 288
            create_txg: 4
    features_for_read:
        com.delphix:hole_birth
        com.delphix:embedded_data
    labels = 0 1 2 3

注目すべきはnameの項目で、プール名がそのまんま入っている。さらに他の項目からプールの詳細がわかる。

  • name:
    • プール名
  • hostname
    • プールを作成したホスト名
  • vdev_tree: type
    • プールの構成方法
  • vdev_tree: children
    • vdevを構成するストレージの数
  • vdev_tree: children: path
    • プール構成ストレージとして最後に認識された時のデバイスパス

これらを読み解くと、/dev/sdh1は「FreeBSDマシン2)hostname.example.comで作られた4台構成のRAID-Zプールzdata」の構成デバイスだった、ということが推測できる。


2)
pathのadaXから推定

ZFSのSpecial Allocation ClassのSpecial VDEVの容量を見積もる

SSDをSpecial VDEVとしてZFSプールに追加すれば性能向上が見込めそうなのは分かった。続いてSpecial VDEVに必要な容量を見積もってみる。

Special VDEVに格納されるデータは大きく2つに分けられる。

  • メタデータ
  • 小ブロックのデータ(スモールI/Oの結果として生成される小さなレコード)

zdbコマンドでプールのこれらの現在量を確認し、Special VDEVの容量を見積もることができそうだ。

実際に、稼働中の家鯖のプールで実際に確認してみよう。対象プールの下表のとおり。

用途 システム用プール (zroot)
種類 ミラー
使用量/容量 37.2/99.8GiB
特記事項
  • FreeBSD 12.2-RELEASEが入っている
  • 9-BETAの頃から連綿と続く年季の入ったプール
  • ホームディレクトリは別プールの
  • 細かなファイルが多め(/usr/srcや.svnフォルダ、portsのソース&ビルドファイルなど)

なお、zdb実行時はメモリの空きに注意すること。プールの使用量がテラバイト級だと数ギガ単位で消費する。メモリ不足でzdbが落ちるようなら、Special VDEVより先にメモリを追加しましょう。何はなくともZFSはメモリが重要なので。

メタデータの使用量は簡単に確認できる。

Allocation Classにおける「メタデータ」とは、ファイルデータとzvolデータを除いたデータである。正確に言うと、レベル0のZFS plain file(いわゆる普通のファイルのデータ)とレベル0のzvol object(zvolのデータブロック)を除いたものがメタデータとなり、それら全てがSpecial VDEVに載るとのこと。

zdb -bbb プール名を実行するとプールの詳細情報がズラズラ出るが、このうちTypeがTotalのASIZEからL0 ZFS plain fileとL0 zvol objectのASIZEを引いた値がメタデータサイズとなる。

$ zdb -bbb zroot
Traversing all blocks to verify nothing leaked ...

loading concrete vdev 0, metaslab 398 of 399 ...
36.3G completed (1522MB/s) estimated time remaining: 0hr 00min 00sec
        No leaks (block sum matches space maps exactly)

        bp count:               1693872
(省略)
        Dittoed blocks on same vdev: 147925

Blocks  LSIZE   PSIZE   ASIZE     avg    comp   %Total  Type
     -      -       -       -       -       -        -  unallocated
     2    32K      8K     24K     12K    4.00     0.00  object directory
(省略)
     -      -       -       -       -       -        -  ZFS V0 ACL
    85  3.62M    222K    688K   8.09K   16.76     0.00      L2 ZFS plain file
 25.2K  2.85G    103M    214M   8.50K   28.36     0.56      L1 ZFS plain file
 1.43M  47.3G   33.9G   35.9G   25.0K    1.40    96.63      L0 ZFS plain file ★これ
 1.46M  50.1G   34.0G   36.1G   24.7K    1.48    97.19  ZFS plain file
     4    64K     16K     32K      8K    4.00     0.00      L2 ZFS directory
 1.02K   118M   4.26M   8.61M   8.47K   27.60     0.02      L1 ZFS directory
 86.2K   237M    120M    456M   5.29K    1.97     1.20      L0 ZFS directory
 87.2K   355M    125M    464M   5.32K    2.84     1.22  ZFS directory
     -      -       -       -       -       -        -  zvol object      ★これ
(省略)
 1.62M  51.6G   34.4G   37.2G   23.0K    1.50   100.00  Total         ★これ

この例だと、37.2G - 35.9G - 0 = 1.3G がメタデータサイズとなる(zvolは使っていないのでL0 zvol objectは出てこない)。

必要となる小ブロック用の領域は、データセット毎に指定するspecial_small_blocksプロパティの値で変わってくる。

このプロパティはSpecial Allocation Classとして扱うブロックサイズ、すなわちSpecial VDEVへの読み書きとなる閾値で、512~128kの2の累乗値で指定する。この値以下の読み書きがSpecial VDEV行きとなるので、recordsizeと同じ値にするのは危険。基本的には64k以下を指定することになるだろう。

zdb -bbbb プール名を実行すると、データの種類ごとにレコードサイズの使用状況が表示される。

ここでも注目すべきはL0 ZFS plain fileとL0 zvol objectの分布である。非常に長いログのため、L0 ZFS plain fileの一部のみ掲載。

 1.43M  47.3G   33.9G   35.9G   25.0K    1.40    96.63      L0 ZFS plain file
psize (in 512-byte sectors): number of blocks
                          0:  36843 *******
                          1: 213506 ****************************************
                          2: 139110 ***************************
                          3: 137030 **************************
                          4:  98715 *******************
                          5:  70100 **************
                          6:  56304 ***********
                          7:  43444 *********
                          8: 158789 ******************************
                          9:  13123 ***
(省略)
                        255:     36 *
                        256: 179774 **********************************

0:, 1:, …, 256: はブロックサイズを、その後ろはブロック数を表す。1ブロック512バイトなので、上記の8:の行は4096バイトブロックが158789個で約620MiBと読める。

各レコードサイズ以下のデータ量は下表の通りだった。

レコードサイズ データ量
4KiB以下 1.69GiB
8KiB以下 2.53GiB
16KiB以下 3.40GiB
32KiB以下 4.60GiB
64KiB以下 7.9GiB

ここではSpecial VDEVをフル活用するとして、全部盛りの7.9GiBを採用する。

(2021-12-14追記)

FreeBSD 13.0 (OpenZFS 2.0)のzdb -bbbでBlock Size Histogramという、まんまの情報が出ることに気づいた。ご丁寧に対象ブロック以下の合計バイト数まで出してくれるので、一撃で見積もることができる。

Block Size Histogram

  block   psize                lsize                asize
   size   Count   Size   Cum.  Count   Size   Cum.  Count   Size   Cum.
    512:   310K   155M   155M   218K   109M   109M      0      0      0
     1K:   353K   394M   549M   190K   225M   334M      0      0      0
     2K:   149K   402M   951M   150K   392M   726M      0      0      0
     4K:   346K  1.42G  2.35G   139K   747M  1.44G      0      0      0
     8K:   303K  2.96G  5.31G   113K  1.24G  2.67G   773K  9.06G  9.06G
    16K:   391K  8.17G  13.5G   183K  3.49G  6.16G   613K  14.4G  23.4G
    32K:   528K  23.7G  37.2G   138K  6.11G  12.3G   553K  25.4G  48.8G
    64K:   945K  83.4G   121G   137K  11.6G  23.8G   592K  57.7G   107G
   128K:  19.5M  2.44T  2.56T  21.3M  2.66T  2.68T  20.2M  4.22T  4.32T
   256K:   198K  70.1G  2.63T  11.2K  4.00G  2.69T   125K  47.3G  4.37T
   512K:  39.6M  19.8T  22.4T  40.0M  20.0T  22.7T  39.7M  33.5T  37.9T
     1M:   602K   602G  23.0T   602K   602G  23.3T   602K  1009G  38.8T
     2M:      0      0  23.0T      0      0  23.3T      0      0  38.8T
     4M:      0      0  23.0T      0      0  23.3T      0      0  38.8T
     8M:      0      0  23.0T      0      0  23.3T      0      0  38.8T
    16M:      0      0  23.0T      0      0  23.3T      0      0  38.8T

上記は26TBのプール(使用量は23TB)で、64KB以下のブロックが121GBだからプールに占める割合は0.46%となる。Special VDEVの容量は、一般的な用途ではプールの1~2%を確保しておけば十分なのかも。

以上より、メタデータ量1.3GiBと小ブロックデータ量7.9GiBの合算、9.2GiBが現時点のSpecial Allocation Classのサイズとなる。

プールの使用量とメタデータ量/小ブロック量の関係は読み切れない部分があるけど、今後も同じ割合でSpecial Allocation Classが増えるとすれば、Special VDEVに必要なサイズは25GiB程度となる。

プールサイズの25%というと結構な割合となるが、細々とした大量のファイルの影響が考えられる。実際、このプールでは4KiB以下のファイルが全ファイル数の8割を占めており、中でも特に1KiB以下が5割を占めている。

Special VDEVの必要量は、プールの使われ方にも大きく依存すると考えられる。

そこで、手元の6つのプールについてSpecial VDEVの容量に影響しそうな項目を調査した。

プール名 種類 使用量/容量 ファイル数 平均ファイルサイズ メタデータ 64kB以下のレコード総量 使われ方
システムプール ミラー 37.2/99.8GiB 1286414 0.029MiB 1.3GiB
(3.5%)
7.9GiB
(21.2%)
FreeBSDのシステム格納用。
見積作業で使ったプール。小粒で大量のファイル成分強い。
データプール1 ミラー 2.54/7.12TiB 1003104 2.654MiB 20GiB
(0.8%)
31.7GiB
(1.2%)
ホームディレクトリ用のプール。
日常生活での書類データ、数MB~数十MBの音楽ファイル、数十MBオーダーの写真などが主。
データプール2 RAIDZ1 20.2/20.4TiB 229356 92.351MiB 700MiB
(0.003%)
6.74GiB
(0.03%)
データプール1より重要度が下のデータ群。
数GB級の動画ファイル、数百KBクラスの画像、アプリのアーカイブ(ISO, zipなど様々)など。
データプール3 単体 6.49/7.1TiB 1099395 6.190MiB 10GiB
(0.2%)
64.5GiB
(1.0%)
データプール2より重要度が下のデータ群。
バックアップデータ、Time Machineのスパースバンドル、~2GB程度の動画、数百KBクラスの画像など。
業務用プール1 RAIDZ1 5.09/8.93TiB 2674793 1.995MiB 30GiB
(0.6%)
120.2GiB
(2.3%)
業務で使われているプール、その1。
主に間接部門の書類、資料性の高いデータ、流動性の低いデータなど。
業務用プール2 ミラー 0.954/1.99TiB 183582 5.451MiB 2GiB
(0.2%)
1.04GiB
(0.1%)
業務で使われているプール、その2。
数十KB~数百KBの多数の画像、数MBのアセットなど。

ファイル数が多いほど、またファイルサイズが小さいほど、メタデータと小ブロックの量は増える傾向にあるものの、一概にプール容量の何パーセントと言える感じではなさそうだ。

システムプールが少々特殊な気がするので除外すると、多くの場合、Special VDEVのサイズはプールサイズの5%あれば十分と言えなくもない?が、断定するにはサンプルが不足してるかな…。少々時間はかかるけど、今のところ都度zdbで計算する方がよさげ。潤沢な資金があるならともかく、20TiBの5%は1TiBになるので、丸ごとSpecial VDEVにおごるのは勿体ない気も…。

ファイルサイズの分布。

用途ごとにプールを分けてることもあって、ファイルサイズはプールごとにそれなりにバラつきが見られる(目論見通り)。

続いてレコードサイズの分布。

ZFSのトランザクショングループ(txg)のおかげか殆どが128KiBレコードとなっており、グラフにする意味もなかった。txgって予想以上に効くんだね…。これでは何も読み取れないので、各プールの使用率上位3位のレコードサイズを表にまとめた。

プール名 1位 2位 3位
システムプール 512 (14.2%) 128k (11.95%) 4k (10.54%)
データプール1 128k (88.1%) 4k (0.98%) 512 (0.89%)
データプール2 128k (99.6%) 512 (0.11%) 4k (0.02%)
データプール3 128k (90.7%) 4k (0.98%) 8k (0.54%)
業務用プール1 128k (63.6%) 1 (8.34%) 112k (2.10%)
業務用プール2 128k (97.7%) 512 (1.75%) 11k (0.02%)

ZFSの書き込みは、ほぼほぼ128k/4k/512バイトに集約されると言っても過言ではなさそう。

  • start.txt
  • 最終更新: 2019-08-19 11:45
  • by Decomo