ZFS-8000-5Eが出てもあきらめるな

先日、FreeBSD-7.2で運用中のzfsプールが吹っ飛びました。
結果としては何とか損失なく復旧できたのでせっかくだからその顛末をまとめてみるとします。

発端

しばらく前に鯖機のM/Bをアップグレードしてその移行中、あるzfsストレージプール(160GB)がトラブったので(詳細忘れた)、そのディスクイメージをAX300の録画データを溜め込んであるプール(プール名'video', WD15EADS, 1.5TB)に吸い出してる途中にフリーズ。リブートしたら録画データのプールが読めなくなっていました(詳細は記録してないので既に不明)

とりあえずぐぐってみるといったんexportしてimportしなおせばよいみたいなことが書いてあったのでexportしてみたらinvalid vdev configurationとか返されてimportできません。

zpool importで見つかった結果は、プールの状態がinsufficient replicasでUNAVAIL、ディスクはcorrupted dataでUNAVAIL。
エラーメッセージとしてはhttp://www.sun.com/msg/ZFS-8000-5Eが示されていたので参照してみると、

The pool cannot be imported - all data is lost and must be restored from an appropriate backup source.

とか残酷な宣告が。

けいおんとか咲-saki-とかGAとか化物語とかPandoraHeartsとか一気に失ってしまいかねない状態なのでした*1


ちなみに発端となった160GBのプールは数日後、何事もなかったかのようにimportして使えるようになってました。なんでー?

調査

いろいろと諦めきれないものがあるのでとにかくサルベージをしてみることに。

google先生にお願いして情報を集めます。上記のZFS-8000-5Eとかinvalid vdev configurationとかでぐぐってみるといろいろ出てきます。ほとんど英語ですがorz

とりあえず見つかったのがzdb -lでzfsのラベルを読んでみろとのこと。
やってみるとLABEL 2と3が読めてません。(guidとかhostidは削除しています。)

                                                                                      • -

LABEL 0

                                                                                      • -

version=13
name='video'
state=0
txg=518098
pool_guid=(削除)
hostid=(削除)
hostname='unset'
top_guid=(削除)
guid=(削除)
vdev_tree
type='disk'
id=0
guid=(削除)
path='/dev/ad4'
whole_disk=0
metaslab_array=23
metaslab_shift=33
ashift=9
asize=1500297035776
is_log=0
DTL=28

                                                                                      • -

LABEL 1

                                                                                      • -

version=13
name='video'
state=0
txg=518098
pool_guid=(削除)
hostid=(削除)
hostname='unset'
top_guid=(削除)
guid=(削除)
vdev_tree
type='disk'
id=0
guid=(削除)
path='/dev/ad4'
whole_disk=0
metaslab_array=23
metaslab_shift=33
ashift=9
asize=1500297035776
is_log=0
DTL=28

                                                                                      • -

LABEL 2

                                                                                      • -

failed to unpack label 2

                                                                                      • -

LABEL 3

                                                                                      • -

failed to unpack label 3

"faild to unpack label"でぐぐってみると似たようなのが見つかりました。

http://forums.freebsd.org/showthread.php?t=5936&highlight=zfs+import

ラベルが4つあるのは冗長性のためで内容は同一なのでこれをddでコピーすればよいとのこと。

さらにそこから飛べるOpenSolarisの文書にはvdev上でのラベルのとサイズがあってディスクの先頭と末尾にそれぞれL0L1とL2L3が書き込まれていること、ラベルのサイズが256KiBであることがわかりました。

とりあえずサルベージ用ディスクとして同じWD15EADS買ってきました。出費がorz。
あえて言うほどのものことでは無いかも知れませんが、サルベージするときは別のディスクを用意してディスクイメージを吸い出してイメージに対して作業します。こうすることでサルベージのはずが逆にトドメを刺してしまってもオリジナルから吸い出せばまたやり直せます。
さらにハード障害が関わってるような場合にはそのまま作業すると障害が悪化する危険性もあるのでなおさらです。

サルベージ用ディスクの準備

今回はサルベージ用のディスクにZFSプールを構築してその上にイメージファイルとして吸い出しました。
この場合だと本来はファイルシステムのオーバヘッド分の容量が必要になります。しかしながら今回サルベージするディスクはおろしたてで半分も使っていないというのがミソで、ddにconv=sparseを付けてスパースファイルとして吸い出します。
案の定1.5TBのイメージが810GiBほどで収まりました。
ちなみにZFSは圧縮機能もありますが元が圧縮されてる動画なのでほぼ効きません。
イメージが吸い出せたらスナップショットを取っておきます。これでイメージをいじくりまわしても一発で元通りです。オリジナルがあるとはいえ改めて吸い出すのは時間掛かりすぎるので。

吸い出したイメージはmdconfigでvnodeデバイスに仕立て上げてアクセスします。zfsのvdevはファイルをバッキングストアとして使えるらしいのですが、ウチの環境ではzpool import -d、zpool createのどちらもイメージファイルを開くことすらできない感じでした。

実験1

4GBのSDHC全体でプールを作ってイメージを吸い出し、スナップショットとったらvnodeデバイスに仕立て上げます。これをzpool import。正常にimportできることを確認。

ddでラベル領域をゼロクリアしたり相互にコピーしたり試行錯誤。

末尾のL2L3をゼロクリアすると同じZFS-8000-5Eのエラーになりました。
ここで先頭のL0L1をL2L3にコピーしてやるとエラーが消えて再びimportできるようになりました。

さらに書いたり消したり試行錯誤してみるとどうもL0とL3が生きていればよい模様*2

実践1

ラベルのコピーで復活できるんジャマイカ?という感触が得られたので実践。

先頭のラベルが生きてると仮定して、L0L1に相当するイメージ先頭の512KiB(=1024セクタ)をファイルに吸い出してみます。
この吸い出したファイルをzdb -lしてみるとLABEL 0〜3まできちんと表示されます。ラベルは2つ分しかないはずですが、先頭から2つと末尾から512KiB手前(すなわちファイル先頭)を読むので4つあるように見えます。

これをイメージの末尾の1024セクタ手前から書くわけですが、そのオフセットはいくつ?と言うことに。
スナップショットでいつでも戻れるので試行錯誤です。

  • mdデバイスに対してfdisk(8)が返すCHSパラメタ(1182401/255/63)から算出→NG。L2L3はfailed to unpackのまま
  • 実際のディスクに対してfdisk(8)が返すパラメタ(2907020/16/63)で算出→NG
  • mdconfigのオプションでCHS実ディスクに合わせてmdデバイスを構築→NG
  • atacontrol capが返したセクタ数から算出→NG
  • イメージファイルのファイルサイズから算出→NG

どうもzfsが見てるオフセットは違うようです。

ラベルのvdev_tree->asizeがそれっぽい値なのですが今ひとつはっきりしません。バイト単位とすればディスクのサイズより小さいので管理領域等を除いた実データ領域か何かっぽいですが…?

実験2

もういちど正常なプールを調べてみることにしました。

vdev_tree->asizeがやっぱり気になります。仕様書とかソースじっくり読めば分かるんでしょうがそこまで忍耐力ありません(ぉ

SDHCのイメージはファイルサイズ-512KiBでラベルとして認識されるので関係を調べてみます。

  • SDHCイメージのファイルサイズ:4,025,483,264バイト
  • vdev_tree->asize: 4,020,764,672(バイト?)
  • 4,052,483,264 - 4,020,764,672 = 31,718,952
  • 31718952 / 512(1セクタのバイト数) = 9216
  • 9216では気付かなかったのですが、ラベル2つ分の1024セクタを引いてみると8192とか(一部の人にとって)キリのいい数字が出てきましたよ?

実践3

'video'プールのイメージに戻って、8192 + (asize/512)セクタ目に先頭の1024セクタをコピーしてみます。

zdb -l。LABEL 2,3復活キタ━━━━(゜∀゜)━━━━!!

zpool import。ONLINEキタ━━━━(゜∀゜)━━━━!!

zpool scrub。エラーなしで完了キタ━━━━(゜∀゜)━━━━!!

というわけで無事復旧できたようです。

まとめ

  • http://www.sun.com/msg/ZFS-8000-5Eが出たときはzdb -lでラベルをチェック。一つでも生きてれば書き戻して復活できるかもしれない。
  • zfsのラベルは1つ256KiB(512セクタ)。先頭に2つと末尾に2つある。
  • 末尾のラベルのオフセットは8192 * 512 + vdev_tree->asizeバイト目

余談

とりあえずデータが無事なのは分かったのであとはどう書き戻すかです。馬鹿正直に1.5TB書き戻すのも気が引けます。同じデータ入ってるんだしZFSでミラーすればいいのでは?とattachしようとしたらactiveとかなんか出て駄目でした。たぶんvdevのguidが同じなので弾かれたんでしょう。
それではとラベル消してアタッチしてみたら今度はdevice is too smallとか言われました。
アタッチしようとしたディスクのラベル読んでみるとasizeがイメージファイルのものより小さくなってます。何故?イメージ作成で何か間違えた?*3

ラベル消してから思ったのですが、無理にattachしようとせずに実ディスクのラベル部分だけ書き直して念のためscrubしておけば良かったんですよねえ。

というわけで今回も何とかサルベージできたのですが、つくづくバックアップの大切さを思い知らされます。

*1:そのプールには他にも他にもAX300導入してからの約5年分の録画データもあったのですが、そちらはWD15EADS導入前に使ってたディスクを手つかずにしていたので無事でした

*2:試してませんが先頭+末尾でL0+L2とかL1+L2とかL1+L3でもいいのかも

*3:あとでもうちょい調べたらサルベージ用の同一型番、同一ファームのWD15EADSともジオメトリが違いました。