これは私の思い違いだったってだけの記事です。
要約
「RAID*1の目的は複数のデバイスを使って冗長化する仕組み。データのバックアップや、データ保護といった仕組みはない。」
ということだけ、持ち帰ってもらえればと思います。
- Linux MD(mdadm)におけるRAID1は1byteの異常では故障扱いにならない
- Linux MDのRAIDはデータ破損を防ぐようなことはないとraid.wiki.kernel.orgにも書いてあります。
- 1blockの読み取り不備が起きるぐらいでは故障扱いにならない
- 具体的に故障扱いになる理由はよくわからなかった
- データの破損がないか確認する仕組みにも配慮したほうがよさそう(未完)
背景
新しくNASのハードウェアを組み立ててから1年経っているがまだ本番投入していない…
RAID構成やZFSの採用でずっと悩んでいた。手元でベンチマークを雑にとると、ZFSは比較的安定しているが、CPU使用率が高くなったり、Linux MDのほうがCPU使用率が明らかに少ないとかでずっと悩んでいた。
Linux MDでRAID6にすべきかZFSでRAID-Z2にすべきか、としか考えてなかったが、次の記事を見て少し考えが変わった。
この記事自体は「ZFSではRAID-Zを使うな、mirror vdevをpoolして使え」という記事で、実際それはどうなんだ?と思っていたんですが、考えてみればストレージ効率が50%になるのと、2台故障かつ屑運引いた場合に復旧できないことがある以外にデメリットが全くないとも思えたんですね。
で、なら今まで通りLinux MDでRAID1でいいんじゃないか、ってちょっと思ったわけですが、ふと、RAID1ってどこまで故障を直してくれるのか、って思ったんですよ。
故障って何なんですかね。故障を検知はできるのは過去に経験はしていたんだけども、溜まっているデータを見るとまれに日付が1970年みたいなデータもあって、あれこれこんな日付だったっけ、っておもうこともあったり。
そもそも、RAID1なら1台で誤ってるデータがあったらそれ比べて異常検知できるんじゃないんか?って。
じゃ、やってみましょう、というのが今回の記事。結果は要約の通りです。
手順
- ループバックデバイスを2つ作る(ただの1GBのファイルをブロックデバイスのように認識させる)
- ループバックデバイスを2つ使いmdadmを使ってLinux MDのRAID1を組み、ext4でフォーマットし、ファイルを書き込む
- ループバックデバイスの1つを1byteだけ文字化けさせて動きを見る
という感じです。言うのは簡単ですがやるのはちょっと面倒だった。実際はもっとうまいやり方があると思います。
MD用のデバイスは /dev/md7 としています。数字に意味はなくて元の環境で被らない値にします。
あとrootで作業してます。sudo厨は適宜読み替えてください。
ループバックデバイス準備~ファイルシステム作成とマウントまで
# ループバックデバイス用のファイルを用意 dd if=/dev/zero of=./vd1.img bs=1M count=1024 dd if=/dev/zero of=./vd2.img bs=1M count=1024 # ループバックデバイスとして対応させる losetup /dev/loop0 ./vd1.img losetup /dev/loop1 ./vd2.img # MDアレイを構成する(yesを応答する必要あり) mdadm --create /dev/md7 --level=1 --raid-devices=2 /dev/loop0 /dev/loop1
MDアレイの様子は cat /proc/mdstat や mdadm --detail /dev/md7 などから確認できる。
# cat /proc/mdstat
Personalities : [raid1]
md7 : active raid1 loop1[1] loop0[0]
1046528 blocks super 1.2 [2/2] [UU]
# mdadm --detail /dev/md7
/dev/md7:
Version : 1.2
Creation Time : Sat Oct 5 22:36:33 2024
Raid Level : raid1
Array Size : 1046528 (1022.00 MiB 1071.64 MB)
Used Dev Size : 1046528 (1022.00 MiB 1071.64 MB)
Raid Devices : 2
Total Devices : 2
Persistence : Superblock is persistent
Update Time : Sat Oct 5 22:38:31 2024
State : clean
Active Devices : 2
Working Devices : 2
Failed Devices : 0
Spare Devices : 0
Consistency Policy : resync
Name : localhost:7 (local to host localhost)
UUID : a2bc0b3a:fcebbe92:6143d802:c2ddd7d5
Events : 17
Number Major Minor RaidDevice State
0 7 0 0 active sync /dev/loop0
1 7 1 1 active sync /dev/loop1
適当にFSを構成
mkfs.ext4 /dev/md7
mkdir ./mnt
mount /dev/md7 ./mnt
整合性確認をしておく
echo check > /sys/block/md7/md/sync_action
進捗や状況はcat /proc/mdstat や mdadm --detail /dev/md7 で確認できる
# cat /proc/mdstat
Personalities : [raid1]
md7 : active raid1 loop1[1] loop0[0]
1046528 blocks super 1.2 [2/2] [UU]
[===========>.........] check = 57.4% (601984/1046528) finish=0.0min speed=300992K/sec
# cat /proc/mdstat
Personalities : [raid1]
md7 : active raid1 loop1[1] loop0[0]
1046528 blocks super 1.2 [2/2] [UU]
mdadm --detailでは、StateやCheck Statusの有無で状況がわかる。
# mdadm --detail /dev/md7
/dev/md7:
Version : 1.2
Creation Time : Sat Oct 5 22:36:33 2024
Raid Level : raid1
Array Size : 1046528 (1022.00 MiB 1071.64 MB)
Used Dev Size : 1046528 (1022.00 MiB 1071.64 MB)
Raid Devices : 2
Total Devices : 2
Persistence : Superblock is persistent
Update Time : Sat Oct 5 22:42:08 2024
State : clean, checking
Active Devices : 2
Working Devices : 2
Failed Devices : 0
Spare Devices : 0
Consistency Policy : resync
Check Status : 57% complete
Name : localhost:7 (local to host localhost)
UUID : a2bc0b3a:fcebbe92:6143d802:c2ddd7d5
Events : 21
Number Major Minor RaidDevice State
0 7 0 0 active sync /dev/loop0
1 7 1 1 active sync /dev/loop1
# mdadm --detail /dev/md7
/dev/md7:
Version : 1.2
Creation Time : Sat Oct 5 22:36:33 2024
Raid Level : raid1
Array Size : 1046528 (1022.00 MiB 1071.64 MB)
Used Dev Size : 1046528 (1022.00 MiB 1071.64 MB)
Raid Devices : 2
Total Devices : 2
Persistence : Superblock is persistent
Update Time : Sat Oct 5 22:42:10 2024
State : clean
Active Devices : 2
Working Devices : 2
Failed Devices : 0
Spare Devices : 0
Consistency Policy : resync
Name : localhost:7 (local to host localhost)
UUID : a2bc0b3a:fcebbe92:6143d802:c2ddd7d5
Events : 23
Number Major Minor RaidDevice State
0 7 0 0 active sync /dev/loop0
1 7 1 1 active sync /dev/loop1
作成したファイルシステムでファイルの書き込みを試してみる。
echo "hello world 12345" > ./mnt/hello.txt cat ./mnt/hello.txt
hello world 12345
当然できる。
壊す場所を探す
壊すための位置を特定する必要があります。grep -abo "検索文字列" ファイル とすると、見つかればオフセット値とともに表示されます。
# grep -abo "hello world 12345" ./vd1.img 136843264:hello world 12345 # grep -abo "hello world 12345" ./vd2.img 136843264:hello world 12345
RAID1だからか全く同じ場所に書かれている感じだった。前後20byteをhexdumpで見てみる。
# オフセット値を設定 OFFSET=136843264 # 検索文字の長さ PATTERN_LEN=17 # 前後 N byteを指定 N=16 # 表示 dd status=none if=./vd1.img bs=1 skip=$((OFFSET - N)) count=$((PATTERN_LEN + N * 2)) | hexdump -C dd status=none if=./vd2.img bs=1 skip=$((OFFSET - N)) count=$((PATTERN_LEN + N * 2)) | hexdump -C
結果は、まぁそうですね、という感じ。
# dd status=none if=./vd1.img bs=1 skip=$((OFFSET - N)) count=$((PATTERN_LEN + N * 2)) | hexdump -C 00000000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00000010 68 65 6c 6c 6f 20 77 6f 72 6c 64 20 31 32 33 34 |hello world 1234| 00000020 35 0a 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |5...............| 00000030 00 |.| 00000031 # dd status=none if=./vd2.img bs=1 skip=$((OFFSET - N)) count=$((PATTERN_LEN + N * 2)) | hexdump -C 00000000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00000010 68 65 6c 6c 6f 20 77 6f 72 6c 64 20 31 32 33 34 |hello world 1234| 00000020 35 0a 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |5...............| 00000030 00 |.| 00000031
一応この時点でチェックを走らせてみる。
# echo check > /sys/block/md7/md/sync_action
# cat /proc/mdstat
Personalities : [raid1]
md7 : active raid1 loop1[1] loop0[0]
1046528 blocks super 1.2 [2/2] [UU]
[===>.................] check = 19.2% (201984/1046528) finish=0.0min speed=201984K/sec
unused devices: <none>
# cat /proc/mdstat
Personalities : [raid1]
md7 : active raid1 loop1[1] loop0[0]
1046528 blocks super 1.2 [2/2] [UU]
unused devices: <none>
# cat ./mnt/hello.txt
hello world 12345
問題ないですね。
壊す
では壊してみましょう。vd1.imgのほうのhello worldの"h"を"i"にしてみる。vd2.imgと差が出る状態になります。
printf "i" | dd of=./vd1.img bs=1 seek=$((OFFSET)) count=1 conv=notrunc
hexdumpで見てみましょう。
# dd status=none if=./vd1.img bs=1 skip=$((OFFSET - N)) count=$((PATTERN_LEN + N * 2)) | hexdump -C 00000000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00000010 69 65 6c 6c 6f 20 77 6f 72 6c 64 20 31 32 33 34 |iello world 1234| 00000020 35 0a 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |5...............| 00000030 00 |.| 00000031 # dd status=none if=./vd2.img bs=1 skip=$((OFFSET - N)) count=$((PATTERN_LEN + N * 2)) | hexdump -C 00000000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00000010 68 65 6c 6c 6f 20 77 6f 72 6c 64 20 31 32 33 34 |hello world 1234| 00000020 35 0a 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |5...............| 00000030 00 |.| 00000031
書き換わってますね。これを下位の存在であるLinux MDは検知できるのでしょうか。
# cat ./mnt/hello.txt hello world 12345
壊れてない?いいえ、きっとこれはディスクキャッシュのせいです。ディスクキャッシュを消して再度試してみると…
echo 3 > /proc/sys/vm/drop_caches
再度確認。
# cat ./mnt/hello.txt iello world 12345
壊したデータが読めてしまった。
今回はたまたま壊したループバックデバイスを見ていたということですね。
でもアクセスしたら「あれ?片方のデバイスと一致してねーや!」って気づかないのだろうか?cat /proc/mdstatで故障扱いになっていないか見てみる。
# cat /proc/mdstat
Personalities : [raid1]
md7 : active raid1 loop1[1] loop0[0]
1046528 blocks super 1.2 [2/2] [UU]
正常扱いでした。ではチェックを流してみましょう。
# echo check > /sys/block/md7/md/sync_action
# cat /proc/mdstat
Personalities : [raid1]
md7 : active raid1 loop1[1] loop0[0]
1046528 blocks super 1.2 [2/2] [UU]
[===========>.........] check = 57.4% (601984/1046528) finish=0.0min speed=300992K/sec
# cat /proc/mdstat
Personalities : [raid1]
md7 : active raid1 loop1[1] loop0[0]
1046528 blocks super 1.2 [2/2] [UU]
うーん、問題なし。何をチェックしたのやら。
ということで、この時点で、
ここまでやってRAIDはデータを守る仕組みじゃないんだね、ってことを体で学びました(愚者は経験から学ぶ)
読み取りエラーを起こさせる
よくある故障として「データ、読み取れませんでした」という状態を作ってみましょう。
ChatGPTと会話してみると、Device mapperを利用すると簡単に一部を読み取れなくすることができるらしい。やってみましょう。
# dmsetupでいじる場合、デバイスを使っていない状態にする必要がある # アンマウント umount ./mnt # MDアレイの停止 mdadm --stop /dev/md7 # device mapperでloop0のデータがある付近の故障をエミュレート BLOCK_SIZE=$(blockdev --getsz /dev/loop0) dmsetup create loop0 << __EOF__ 0 $((OFFSET / 512)) linear /dev/loop0 0 $((OFFSET / 512)) 1 error $((OFFSET / 512 + 1)) $((BLOCK_SIZE - (OFFSET / 512 + 1) )) linear /dev/loop0 $((OFFSET / 512 + 1)) __EOF__ # dmsetup remove loop0 で削除できる # MDアレイの再開 (dmを経由したloop0のデバイスを使う) mdadm --assemble /dev/md7 /dev/loop1 /dev/mapper/loop0 # 同期確認 cat /proc/mdstat # マウント mount /dev/md7 ./mnt
ファイルが読めるか確認
cat ./mnt/hello.txt
読めちゃった。
# cat ./mnt/hello.txt hello world 12345
なんでだろうなーと思ったら、気になるログがdmesgに出てた。
[8314675.848584] EXT4-fs (md7): mounted filesystem with ordered data mode. Opts: (null) [8314685.926359] md/raid1:md7: dm-5: rescheduling sector 263176 [8314685.926531] md/raid1:md7: redirecting sector 263176 to other mirror: loop1
なんてことはなく、いたずらで書き換えたデバイス(loop0)で故障をエミュレートしたので、正常なデバイス(loop1)から読み直した、ってだけらしい。
しかし、cat /proc/mdstat を見ても変化はなかった。これだけでは故障としてマークされないんか…
# cat /proc/mdstat
Personalities : [raid1]
md7 : active raid1 loop1[1] dm-5[0]
1046528 blocks super 1.2 [2/2] [UU]
チェックするしかあるまい。
# echo check > /sys/block/md7/md/sync_action
# cat /proc/mdstat
Personalities : [raid1]
md7 : active raid1 loop1[1] dm-5[0]
1046528 blocks super 1.2 [2/2] [UU]
[===============>.....] check = 77.4% (811136/1046528) finish=0.0min speed=202783K/sec
# cat /proc/mdstat
Personalities : [raid1]
md7 : active raid1 loop1[1] dm-5[0]
1046528 blocks super 1.2 [2/2] [UU]
# dmesg
...
[8315865.812910] md: data-check of RAID array md7
[8315871.205671] md: md7: data-check done.
故障扱いにならんのか…まぁ1発アウトはさすがに厳しすぎるからだろうか…閾値でもあるんだろうか。SMART読めないとダメとか?よくわからない
mdadmで故障とマークする
何としてでも壊れたデータを持つデバイス(loop0)を壊れたデバイスと認識させたい。
一旦、正常なデバイスである loop1 をあえて切り離してみる。
# loop0を故障としてマーク mdadm --manage /dev/md7 --fail /dev/loop1
# mdadm --manage /dev/md7 --fail /dev/loop1
mdadm: set /dev/loop1 faulty in /dev/md7
# cat /proc/mdstat
Personalities : [raid1]
md7 : active raid1 loop1[1](F) dm-5[0]
1046528 blocks super 1.2 [2/1] [U_]
縮退モードになりましたね。読んでみましょう。
# cat ./mnt/hello.txt hello world 12345
ディスクキャッシュが残ってた。
# echo 3 > /proc/sys/vm/drop_caches # cat ./mnt/hello.txt cat: ./mnt/hello.txt: Input/output error
エミュレート通り読みとりできなくなってる!期待通りですね。
でもこれでもloop0(dm-5)は故障扱いにならない。
# cat /proc/mdstat
Personalities : [raid1]
md7 : active raid1 loop1[1](F) dm-5[0]
1046528 blocks super 1.2 [2/1] [U_]
うーん。どうやったら故障になるんだろう?
ここまできて、データの破損に対してはRAID1は何もしてくれないので、別のアプローチをすべきだと思ったので、ここまでとする。判断が遅い。
後始末
後始末は以下の感じでできるでしょう。
# dmデバイスを削除 dmsetup remove loop0 # アンマウント umount ./mnt # MDアレイの停止 mdadm --stop /dev/md7 # ループバックデバイスの削除 losetup -d /dev/loop0 losetup -d /dev/loop1 # イメージファイルを削除 rm -f ./vd1.img rm -f ./vd2.img
データの破損に備えるには?
チェックサムなどでデータの完全性を検証する仕組みを持つファイルシステム(Btrfs, ZFS)を使うのがよいのではないか、という気持ちになっています。NASならZFSという気さえしている…
また、dm-integrityでデータの整合性をチェックするレイヤを追加することもできるようです。チェックサム計算とストレージ消費量といったオーバヘッドはありますが、ext4やxfsなどのデータの完全性に関して保証がないファイルシステムなどにも導入できるのはよさそうです。(というか、今となってはこれが一般的ではない理由がわからない)
そういえば、RAID5,6のパリティチェックはデータの破損に一役買ってくれるんでしょうか。どうなんでしょうか。
多分、同じ要領でできそうな気がします。気が向いたらやります。
*1:ただしRAID0を除く