技術

ZFSの頑固なSLOGを消す

削除できないZFSのZIL(slog)を強制的に削除するお話です。
(このブログでZFSの話題を取り上げるのは3年ぶり…!)
ZIL(ZFS Intent Log)というかSLOG(Separate Intent Log)なデバイスをzpoolコマンドで取り外そうとしても取り外せないという現象が長年使ってるプールで起きることに気が付きました。
詳しい現象は以下のとおり。

※作業中に記録とってなかったのでコマンドの実行結果は実際のものではないです。そのため辻褄が合わない部分があるかもしれませんが、重要な部分はなるべく再現するようにしました。

  • zpool removeコマンドが特に失敗を報告しないけど処理も実行されない
    root@localhost:~ # zpool status tank
     pool: tank
    state: ONLINE
     scan: resilvered 0 in 0h32m with 0 errors on Sun Nov 29 02:32:14 2015
    config:
    
           NAME        STATE     READ WRITE CKSUM
           tank        ONLINE       0     0     0
             raidz2-0  ONLINE       0     0     0
               ada1    ONLINE       0     0     0
               ada2    ONLINE       0     0     0
               ada5    ONLINE       0     0     0
               ada6    ONLINE       0     0     0
               ada3    ONLINE       0     0     0
               ada4    ONLINE       0     0     0
            logs
              ada0s3   ONLINE       0     0     0
    
    errors: No known data errors
    root@localhost:~ # zpool remove tank ada0s3
    root@localhost:~ # echo $?
    0
    root@localhost:~ # zpool status tank
     pool: tank
    state: ONLINE
     scan: resilvered 0 in 0h32m with 0 errors on Sun Nov 29 02:32:14 2015
    config:
    
           NAME        STATE     READ WRITE CKSUM
           tank        ONLINE       0     0     0
             raidz2-0  ONLINE       0     0     0
               ada1    ONLINE       0     0     0
               ada2    ONLINE       0     0     0
               ada5    ONLINE       0     0     0
               ada6    ONLINE       0     0     0
               ada3    ONLINE       0     0     0
               ada4    ONLINE       0     0     0
            logs
              ada0s3   ONLINE       0     0     0  ← 頑固
    
    errors: No known data errors
    
  • zdbで確認するとslogデバイスに削除中フラグが立っている
    root@localhost:~ # zdb
    tank:
       version: 5000
       name: 'tank'
       state: 0
       txg: 5771422
       pool_guid: xxxxxxxxxxxxxxxxxxxx
       hostname: 'localhost'
       vdev_children: 1
       vdev_tree:
           type: 'root'
           id: 0
           guid: xxxxxxxxxxxxxxxxxxxx
           children[0]:
               type: 'raidz'
               id: 0
               guid: xxxxxxxxxxxxxxxxxxxx
               nparity: 2
               metaslab_array: 23
               metaslab_shift: 36
               ashift: 12
               asize: xxxxxxxxxxxxxx
               is_log: 0
               children[0]:
                   type: 'disk'
                   id: 0
                   guid: xxxxxxxxxxxxxxxxxxxx
                   path: '/dev/ada1'
                   phys_path: '/dev/ada1'
                   whole_disk: 0
                   DTL: 106
               ### 途中省略 ###
               children[5]:
                   type: 'disk'
                   id: 5
                   guid: xxxxxxxxxxxxxxxxxxxx
                   path: '/dev/ada4'
                   phys_path: '/dev/ada4'
                   whole_disk: 1
                   DTL: 51
          children[1]:
              type: 'disk'
              id: 0
              guid: xxxxxxxxxxxxxxxxxxxx
              path: '/dev/ada0s3'
              metaslab_array: 38
              metaslab_shift: 24
              ashift: 9
              asize: xxxxxxxxxx
              is_log: 1
              removing: 1 ← 削除中
              create_txg: 428
       features_for_read:
           com.delphix:hole_birth
           com.delphix:embedded_data
    
  • zpool iostatで確認するとslogのallocに数値が表示される
    root@localhost:~ # zpool iostat -v tank
                    capacity     operations    bandwidth
    pool          alloc   free   read  write   read  write
    ------------  -----  -----  -----  -----  -----  -----
    tank          10.1T   805G      0      0  1.35K  1.10K
      raidz2      10.1T   805G      0      0  1.35K  1.10K
       ada1          -      -       0      0  1.01K  1.13K
       ada2          -      -       0      0    995  1.09K
       ada5          -      -       0      0  1.24K  1.08K
       ada6          -      -       0      0    939  1.06K
       ada3          -      -       0      0   1012  1.11K
       ada4          -      -       0      0    953  1.09K
    logs
       ada0s3        4K    21G      0      0    251  4.28K
    ------------  -----  -----  -----  -----  -----  -----
                     ↑ココ(これ自体は特に珍しい現象ではない?)
    
  • zpool replaceはできる
    root@localhost:~ # zpool replace tank ada0s3 ada7s1
    ### 自動的にresilverが開始されるのでしばらく経ったのち ###
    root@localhost:~ # zpool status tank
     pool: tank
    state: ONLINE
     scan: resilvered 0 in 0h32m with 0 errors on Sun Nov 29 06:55:21 2015
    config:
    
           NAME        STATE     READ WRITE CKSUM
           tank        ONLINE       0     0     0
             raidz2-0  ONLINE       0     0     0
               ada1    ONLINE       0     0     0
               ada2    ONLINE       0     0     0
               ada5    ONLINE       0     0     0
               ada6    ONLINE       0     0     0
               ada3    ONLINE       0     0     0
               ada4    ONLINE       0     0     0
            logs
              ada7s1   ONLINE       0     0     0  ← replaceできた
    
    errors: No known data errors
    

という感じです。
実はこの現象だいぶ前から気づいていて、色んな操作をやってみたのですが全く解消されず、諦めて放置してました…
slogデバイスを物理的に取り外したいときは、面倒ですが身代わりのデバイス(※)を用意してreplaceすればよいですし…

※物理デバイスを用意するのは大変なので、実際はよくスパースファイルを割り当てたりしてました。

ただこんなこといつまでもやってられないし気持ち悪かったので、今回思い切って対策してみました。

対策内容としては、slogを強制削除するようZFSのカーネルモジュールのソースを書き換えコンパイルして実行する、という非常に危険な方法です。

ただ、ZFSのソースや後で紹介するパッチを見てもらうと分かると思うのですが、ファイルシステムそのものを直接弄るような変更ではないですし、またパッチはすごく短くそれ自体で削除を実行するようなものではないですし、zpool importしてzpool removeしてzpool exportするだけの一回限りの環境で使用するだけなのでまぁ大丈夫かなと。
もちろん一切保証はしませんが。

パッチ

b333z/zfs_slog_zero_vsalloc_inline.patch

作業内容

※長くなるので逐一コマンドを表記したりはしませんが、ほとんど全て一般的な操作なので調べれば色々情報は出てきますしさほど難しくはないでしょう。

パッチはLinux用ですが、私が使用してるのはNAS4Free 10.2とFreeBSD 10.2なので、そのまま適用はできません。
しかし重要な部分はそのまま使えました。

以下作業内容(箇条書き)

まずは対象のPCに新しいストレージを追加して、そこにFreeBSD 10.2をインストールして起動。

そしたら、
/usr/src/sys/cddl/contrib/opensolaris/uts/common/fs/zfs/spa.c
の spa_vdev_remove_from_namespace 関数を以下のように変更。
※3行追加しただけ。追加部分をサンドイッチするようなコメントの書き方は嫌いですが、この記事を書くにあたって分かりやすくするために書きました。

/*
 * Complete the removal by cleaning up the namespace.
 */
static void
spa_vdev_remove_from_namespace(spa_t *spa, vdev_t *vd)
{
	vdev_t *rvd = spa->spa_root_vdev;
	uint64_t id = vd->vdev_id;
	boolean_t last_vdev = (id == (rvd->vdev_children - 1));

	ASSERT(MUTEX_HELD(&spa_namespace_lock));
	ASSERT(spa_config_held(spa, SCL_ALL, RW_WRITER) == SCL_ALL);
	ASSERT(vd == vd->vdev_top);

	// 追加ここから
	if (vd->vdev_islog == 1 && vd->vdev_removing == 1 && vd->vdev_state == VDEV_STATE_OFFLINE && vd->vdev_stat.vs_alloc > 0) {
		vd->vdev_stat.vs_alloc = 0;
	}
	// 追加ここまで

	/*
	 * Only remove any devices which are empty.
	 */
	if (vd->vdev_stat.vs_alloc != 0)
		return;

	(void) vdev_label_init(vd, 0, VDEV_LABEL_REMOVE);

	if (list_link_active(&vd->vdev_state_dirty_node))
		vdev_state_clean(vd);
	if (list_link_active(&vd->vdev_config_dirty_node))
		vdev_config_clean(vd);

	vdev_free(vd);

	if (last_vdev) {
		vdev_compact_children(rvd);
	} else {
		vd = vdev_alloc_common(spa, id, 0, &vdev_hole_ops);
		vdev_add_child(rvd, vd);
	}
	vdev_config_dirty(rvd);

	/*
	 * Reassess the health of our root vdev.
	 */
	vdev_reopen(rvd);
}

そしたらカーネルの再構築。
以下のページを参考にしました。
最近はもっと良いやり方があるらしいですが、古いやり方でも今回は特に問題ありませんでした。
FreeBSD – How to build kernel

新しいカーネルで再起動したら、普通に対象のプールをzpool importし(状況によってはslogを無視するために-mオプションが必要かも)、zpool offlineでslogをオフラインにし(※)、zpool removeでslogを削除しました。
この時点でzpool statusするとslogが消えてました。

※パッチのifでデバイスがオフラインになってるかどうかもチェックしてるのでzpool offlineが必要になります。

あとはzpool exportしてから、本来の環境で再起動。

これでプールに余計なslogが無い状態で今までどおり使えているようです。

余談

最初、VMwareの仮想マシン上にslogの強制削除環境を構築し、ddで実ストレージにイメージコピーし、ターゲットのPCで起動しようとしたのですが、なんか起動しませんでした。
起動プロセス的には結構最後に近い方まで進むんですがkernel panicで落ちる感じです(※)。

※実のところ表示が一瞬過ぎてよく分からなかったのです…

Linuxだとハードウェアが変わるとinitramを再作成しないと起動しなかったりとかはあるんですが、FreeBSDのことはよく知らないですし、そのとき眠くて頭が動かなかったので、FreeBSDのインストールからカーネルの再構築まで実機でやり直しました。

ident以外はGENERICそのままのconfigでコンパイルしたんですが、コンパイル環境のHWに応じて自動的にモジュールが取捨されるのだろうか…

あと今回取り上げた現象、検索してみると結構同じような報告があって、もしかしたらslogの削除に未対応だった時代からzpool upgradeしながら使い続けているプールのかなりの割合で同じ問題が起きているのではなかろうかと思いました。

コメントを残す

メールアドレスが公開されることはありません。



※画像をクリックして別の画像を表示

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください