はじめに
前回投稿した「ZFSプールを壊してみた」という記事で、ZFSではディスク上の不正データをちゃんと検出していることがわかりました。
不正データが検出できることは、検出できないファイルシステムの不正データを読み込んで誤動作をするよりははるかにましです。しかし不正データが検出できるのであれば、それをリカバーすることはできないのか?と思うのも自然なことでしょう。
ZFSの場合は冗長性のあるプールを構成すれば、不正なデータが存在しても正しいデータを得ることができます。具体的にはミラーやRAID-Z、RAID-Z2などのプールを利用します。
ここではZFSプールを壊してみたと同様の実験を、ミラー構成のZFSプールで試してみます。
2022-12-26 scrub実行後のディスクの状態を追記しました
ZFSのミラープールを用意する
実験のために2台のUSB接続のHDDを用意して、それぞれに 4GB のパーティションを作成してミラー構成のZFSプールを用意します。
HDDは/dev/da1, /dev/da2で、GPTでパーティションを作成し、それぞれに tt1 と tt2 のラベルのパーティションを作成しsます。
$ gpart create -s gpt da1
da1 created
$ gpart create -s gpt da2
da2 created
$ gpart add -t freebsd-zfs -a 4k -s 4g -l tt1 da1
da1p1 added
$ gpart add -t freebsd-zfs -a 4k -s 4g -l tt2 da2
da2p1 added
$
$ gpart show da1
=> 40 488397088 da1 GPT (233G)
40 8388608 1 freebsd-zfs (4.0G)
8388648 480008480 - free - (229G)
$ gpart show da2
=> 40 312581728 da2 GPT (149G)
40 8388608 1 freebsd-zfs (4.0G)
8388648 304193120 - free - (145G)
$ gpart show -l da1
=> 40 488397088 da1 GPT (233G)
40 8388608 1 tt1 (4.0G)
8388648 480008480 - free - (229G)
$ gpart show -l da2
=> 40 312581728 da2 GPT (149G)
40 8388608 1 tt2 (4.0G)
8388648 304193120 - free - (145G)
$
これで、ミラー用に2つの2台のHDDにそれぞれパーティションができました。
この2つのパーティションを組み合わせてZFSミラーのプールを作成します。もちろん前回の実験と同様に、ZFSの圧縮は行いません。
$ zpool create -O atime=off ztest mirror gpt/tt1 gpt/tt2
$
次の通り、ミラー構成のZFSプールができました。
$ zpool status ztest
pool: ztest
state: ONLINE
config:
NAME STATE READ WRITE CKSUM
ztest ONLINE 0 0 0
mirror-0 ONLINE 0 0 0
gpt/tt1 ONLINE 0 0 0
gpt/tt2 ONLINE 0 0 0
errors: No known data errors
$ zpool list -v ztest
NAME SIZE ALLOC FREE CKPOINT EXPANDSZ FRAG CAP DEDUP HEALTH ALTROOT
ztest 3.75G 3.62G 128M - - 72% 96% 1.00x ONLINE -
mirror-0 3.75G 3.62G 128M - - 72% 96.7% - ONLINE
gpt/tt1 - - - - - - - - ONLINE
gpt/tt2 - - - - - - - - ONLINE
$ zfs list ztest
NAME USED AVAIL REFER MOUNTPOINT
ztest 420K 3.62G 96K /ztest
$ df /ztest/
Filesystem 512-blocks Used Avail Capacity Mounted on
ztest 7601576 192 7601384 0% /ztest
ダミーデータを書き込む
それでは前回の実験と同様にddコマンドを使ってファイルシステムをゼロデータで埋めます。
$ dd if=/dev/zero of=/ztest/dummy bs=1m
dd: /ztest/dummy: No space left on device
3711+0 records in
3710+1 records out
3891134464 bytes transferred in 151.249101 secs (25726662 bytes/sec)
$ df -h /ztest/
Filesystem Size Used Avail Capacity Mounted on
ztest 3.6G 3.6G 0B 100% /ztest
$ zfs list -o space ztest
NAME AVAIL USED USEDSNAP USEDDS USEDREFRESERV USEDCHILD
ztest 0B 3.63G 0B 3.62G 0B 672K
$ ls -l /ztest/dummy
-rw-r--r-- 1 root wheel 3891134464 Dec 22 17:43 /ztest/dummy
書き込んだデータの破壊
次にゴミのデータを無理やり書き込んで、ZFSとしてはエラーになるように細工します。ミラーによる冗長性とZFSの修復を確認するためですので、da1 側のみにゴミデータを書き込みます。
通常ミラーで構成されたディスクからの単純な読み出しは、負荷分散と高速化のために両方のディスクを交互にアクセスしますが、ZFSのミラー構成でも同様の挙動となります。そのため前回のように1ブロック程度の破壊では、読み出し時に正常なドライブ側をアクセスして、該当のブロックをアクセスしない可能性があります。そこで今回はより多くの領域のデータでエラーを起こすよう数十MB程度のデータを書き込みます。ちょうどカーネルが30MB近くありますのでこれを書き込んで壊してみます。
$ zpool export ztest
$ ls -l /boot/kernel/kernel
-r-xr-xr-x 2 root wheel 29343392 Nov 4 10:27 /boot/kernel/kernel
$ dd if=/boot/kernel/kernel of=/dev/gpt/tt1 oseek=1000000
dd: /dev/gpt/tt1: Invalid argument
57311+1 records in
57311+0 records out
29343232 bytes transferred in 21.647581 secs (1355497 bytes/sec)
$
壊したデータ部分の読み出し
エラーが起きることを確認するため、da1側のみを使ってインポートします。USBのHDDなので、da2を物理的に外してしまうのが簡単です。
$ zpool import
pool: ztest
id: 6304409293823647838
state: DEGRADED
status: One or more devices are missing from the system.
action: The pool can be imported despite missing or damaged devices. The
fault tolerance of the pool may be compromised if imported.
see: https://openzfs.github.io/openzfs-docs/msg/ZFS-8000-2Q
config:
ztest DEGRADED
mirror-0 DEGRADED
gpt/tt1 ONLINE
gpt/tt2 UNAVAIL cannot open
この通り da2側のgpt/tt2 にアクセスできません。このまま ztest をインポートします。
$ zpool import ztest
$ zpool status ztest
pool: ztest
state: DEGRADED
status: One or more devices could not be opened. Sufficient replicas exist for
the pool to continue functioning in a degraded state.
action: Attach the missing device and online it using 'zpool online'.
see: https://openzfs.github.io/openzfs-docs/msg/ZFS-8000-2Q
config:
NAME STATE READ WRITE CKSUM
ztest DEGRADED 0 0 0
mirror-0 DEGRADED 0 0 0
gpt/tt1 ONLINE 0 0 0
13953643250226400519 UNAVAIL 0 0 0 was /dev/gpt/tt2
errors: No known data errors
$ df /ztest
Filesystem 512-blocks Used Avail Capacity Mounted on
ztest 7601264 7601024 240 100% /ztest
$ ls /ztest
dummy
#
片方のドライブだけですが、/ztest/dummy がちゃんとあります。ではこれを実際に読み込んでみましょう。読み込みにはデータの内容がわかるように hd コマンドを使います。
$ hd /ztest/dummy
00000000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
hd: /ztest/dummy: Input/output error
9e1a0000
$
ファイルを読み込むことができず、エラーが発生しました。
ミラー構成での読み出し
改めてミラー構成に戻して同様のことを行ってみます。
$ zpool export ztest
ここで外してあった da2 側の USB HDD を接続します。
$ zpool import
pool: ztest
id: 6304409293823647838
state: ONLINE
action: The pool can be imported using its name or numeric identifier.
config:
ztest ONLINE
mirror-0 ONLINE
gpt/tt1 ONLINE
gpt/tt2 ONLINE
gpt/tt2側がONLINEになっています。それではztestをインポートします。
$ zpool import ztest
$ zpool status ztest
pool: ztest
state: ONLINE
config:
NAME STATE READ WRITE CKSUM
ztest ONLINE 0 0 0
mirror-0 ONLINE 0 0 0
gpt/tt1 ONLINE 0 0 0
gpt/tt2 ONLINE 0 0 0
errors: No known data errors
$
正常にインポートできました、では先ほどと同じように /ztest/dummy を読み出してみましょう
$ hd /ztest/dummy
00000000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
e7ee0000
$
この通り正常に読み出すことができ、すべての内容が0であることが確認できました。ztestの状態を確認してみると次の通りで問題無いように見えます。
$ zpool status ztest
pool: ztest
state: ONLINE
scan: resilvered 432K in 00:00:01 with 0 errors on Fri Dec 23 11:14:23 2022
config:
NAME STATE READ WRITE CKSUM
ztest ONLINE 0 0 0
mirror-0 ONLINE 0 0 0
gpt/tt1 ONLINE 0 0 0
gpt/tt2 ONLINE 0 0 0
errors: No known data errors
$
scrubによるディスクの整合性の確認
ZFS的には読み出せない部分があるはずなので、scrubを実行して整合性の確認を実施します。
$ zpool scrub ztest
$ zpool status ztest
pool: ztest
state: ONLINE
scan: scrub in progress since Fri Dec 23 11:18:02 2022
3.62G scanned at 530M/s, 378M issued at 54.0M/s, 3.62G total
0B repaired, 10.18% done, 00:01:01 to go
config:
NAME STATE READ WRITE CKSUM
ztest ONLINE 0 0 0
mirror-0 ONLINE 0 0 0
gpt/tt1 ONLINE 0 0 0
gpt/tt2 ONLINE 0 0 0
errors: No known data errors
$
scrubが終了したのを見計らって、状態を見てみます。
$ zpool status ztest
pool: ztest
state: ONLINE
status: One or more devices has experienced an unrecoverable error. An
attempt was made to correct the error. Applications are unaffected.
action: Determine if the device needs to be replaced, and clear the errors
using 'zpool clear' or replace the device with 'zpool replace'.
see: https://openzfs.github.io/openzfs-docs/msg/ZFS-8000-9P
scan: scrub repaired 15.8M in 00:01:06 with 0 errors on Fri Dec 23 11:19:08 2022
config:
NAME STATE READ WRITE CKSUM
ztest ONLINE 0 0 0
mirror-0 ONLINE 0 0 0
gpt/tt1 ONLINE 0 0 130
gpt/tt2 ONLINE 0 0 0
errors: No known data errors
$
da1側のtt1は盛大にチェックサムエラーが発生しているのがわかります。
改めてデータの読み出し
最後に改めてデータを読み出してみます。OSのディスクバッファの影響を除くため、いったんエクスポートしてインポートしてから実行します。
$ zpool export ztest
$ zpool import ztest
$ hd /ztest/dummy
00000000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
e7ee0000
$
この通りエラーが起きているディスクにも拘わらず、ミラー構成のおかげで正しいデータを読み出すことができました。
【追記】scrub実行後のda1の状態
記事公開後に「scrubによってda1側は修復されて読み出せるようになっているのでは?」という質問をいただきました。実際 scrub実行後はscan: scrub repaired
と表示してますから、確認認してみました。
といっても実験環境はすでに破棄してしまったので、改めてのやり直しで一部省略してda1側のデータを壊しscrub後にda1側を読み出すことをやってみます。
ZFSミラープールの作成からda1側のデータを破壊しscrub実行後にプールをエクスポートするところまで。
$ zpool create -O atime=off ztest mirror gpt/tt1 gpt/tt2
$ dd if=/dev/zero of=/ztest/dummy bs=1m
dd: /ztest/dummy: No space left on device
3711+0 records in
3710+1 records out
3891134464 bytes transferred in 153.170356 secs (25403966 bytes/sec)
$ zpool export ztest
$ dd if=/boot/kernel/kernel of=/dev/gpt/tt1 oseek=1000000
dd: /dev/gpt/tt1: Invalid argument
57311+1 records in
57311+0 records out
29343232 bytes transferred in 21.643263 secs (1355767 bytes/sec)
$ zpool import ztest
$ zpool scrub ztest
$ zpool status ztest
pool: ztest
state: ONLINE
status: One or more devices has experienced an unrecoverable error. An
attempt was made to correct the error. Applications are unaffected.
action: Determine if the device needs to be replaced, and clear the errors
using 'zpool clear' or replace the device with 'zpool replace'.
see: https://openzfs.github.io/openzfs-docs/msg/ZFS-8000-9P
scan: scrub repaired 27.9M in 00:01:06 with 0 errors on Mon Dec 26 10:55:49 2022
config:
NAME STATE READ WRITE CKSUM
ztest ONLINE 0 0 0
mirror-0 ONLINE 0 0 0
gpt/tt1 ONLINE 0 0 227
gpt/tt2 ONLINE 0 0 0
errors: No known data errors
$ zpool export ztest
エクスポートできたので、da2側を物理的に外してda1側をインポートして読み出してみます。
$ zpool import
pool: ztest
id: 11275383091719095959
state: DEGRADED
status: One or more devices are missing from the system.
action: The pool can be imported despite missing or damaged devices. The
fault tolerance of the pool may be compromised if imported.
see: https://openzfs.github.io/openzfs-docs/msg/ZFS-8000-2Q
config:
ztest DEGRADED
mirror-0 DEGRADED
gpt/tt1 ONLINE
gpt/tt2 UNAVAIL cannot open
$ zpool import ztest
$ hd /ztest/dummy
00000000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
e7ee0000
$ zpool export ztest
破壊した側のda1のデータを問題無く全データ読み出すことができました。つまりscrubによって書き戻されて修復できています。また実際にインポート後にstatusで確認したところ CKSUMの値も0でした(スクショとりそこなった…)。
改めて、エクスポートしたztestにda2を接続してインポートしてみます。
$ zpool import ztest
$ zpool status ztest
pool: ztest
state: ONLINE
scan: scrub repaired 27.9M in 00:01:06 with 0 errors on Mon Dec 26 10:55:49 2022
config:
NAME STATE READ WRITE CKSUM
ztest ONLINE 0 0 0
mirror-0 ONLINE 0 0 0
gpt/tt1 ONLINE 0 0 0
gpt/tt2 ONLINE 0 0 0
errors: No known data errors
$
この通りエラーはクリアされています。
まとめ
ZFSではディスクをミラーやRAID-Z構成にすることで、一部のデータエラーの回避ができることを確認できました。またscrubの実行によって修復できるデータに関しては元通りに修復されることもわかりました。
ZFSに限らず他のボリュームマネージャーやRAIDシステムでも、正しくミラーやRAIDを組むことによって同様に一部のデータエラーを回避できます。修復までできるかどうかについては実装に依存しそうです。