最近の変更サイトマップ

SambaとZFSで大量のファイルを扱う時はcase-sensitiveの組み合わせを最適化する

同一フォルダに大量のファイルがあるとSambaが超遅くなる問題、自分なりの知見が得られたのでメモ。結果だけ知りたい人は最後までスクロールしてくだしあ。

まずはおさらい。

ことの発端は、数万個のファイルがあるフォルダをSambaのファイルサーバにコピーすると、速度が10kB/s前後まで低下する現象に見舞われた。速度はコピーが進むごとに低下し、比例してsmbdのCPU占有率が上がるというのが特徴。サーバ上で直接コピーすると何の問題もない。

調査を進めると、ファイル作成時Sambaはファイル名の重複チェックのため、作成先フォルダ内の全ファイル名を検査することが分かった。この時に行われるWindowsのファイル名規則(FS上は大文字/小文字を区別するが、一般的なファイルアクセス上は区別しないナンチャッテcase-preserving。この差はOSが吸収する。)と合わせるための、ファイル名を大文字ないし小文字に変換する処理がボトルネックのようだ。

プロファイルしたわけでもソースコードを見たわけでもないので確証はないが、公式ドキュメントに「ディレクトリに大量のファイルがある特殊なケースでは、case sensitiveをyesにせよ」(抄訳)と書かれており、ファイルコピーの進捗にあわせ指数関数的に速度が落ちる(CPU負荷が上がる)という挙動から、たぶん当たってると思う。

ではcase sensitive = yesにすれば万事解決かといえば、そうじゃない罠。

前述の通りWindowsはFS的にはcase-sensitiveだが、歴史的理由で表面的にはcase-insensitiveとして振る舞う。この仕様に胡坐をかき、ファイルパスを内部的に大文字or小文字に変換して扱うアプリケーションが少なくない。例えば、本来のファイルパスはC:\Data\FILE.datにもかかわらず、アプリケーション内部では c:\data\file.datとしてアクセスすることが往々にある(自分もそういう処理をつい書いちゃうんだけど(;'∀'))。ローカルのファイルに対してなら、これでも問題はない。

しかし、サーバの共有フォルダに対しては、アプリから渡されたファイルパスをそのまま渡しているようだ。故にサーバ側がその辺を考慮した処理になってないと、ファイルが見つからないという事になる。Sambaのcase sensitiveオプションは、まさにその辺の制御を設定するオプションなのだ。これをyesにするということは、\\Server\Data\FILE.datと\\Server\data\file.datは別のファイルと見なされる、ということだ。アプリからすれば、\\Server\Data\FILE.datが見つからないということになる。これでは使い物にならない。

ではどうするか。ZFSのcasesensitivityプロパティの登場だ。

お察しの通りその名の通りのプロパティで、デフォルト値はcasesensitiveである。これをcaseinsensitiveにしてやればいい。insensitiveという名付けではあるが、挙動としてはpreservingのようだ。ちなみに、mixedというのもあるが、使いどころがよくわからない挙動なので指定しない方が無難(一応、Windowsを想定した挙動らしいんだけど…)。

本プロパティはFS作成時にしか指定できないため、Windows共有用のFSを新規に作り、Sambaで共有フォルダに仕立て上げるのがよいだろう。

Sambaがファイル名をキャッシュせずファイル作成毎に全舐めしてしているのは、恐らく対象フォルダに対する変更をSamba側で検知する仕組みがないからだと思う。Sambaがファイルを作成しようとしたまさにその時、同名のファイルがサーバのローカルで作られたり、別のファイルがリネームされたりする可能性があるため、キャッシュではファイル名のユニーク性を担保できないのだろう。

一方、ファイルシステムレベルなら、そのような変更の検出とアトミック性・ユニーク性が保証された素敵な仕組みがあるハズだから、アプリ側でファイル名の全比較を行うより効率的なんじゃねっていう目論見。

結論としてはSambaとZFSで以下の設定を行うと幸せになれる。ZFSじゃない人はスマン…

  • ZFS
    • ファイル共有用にcasesensitivity=caseinsensitiveなFSを作る
      zfs create -o casesnsitivity=caseinsensitive ztank/path/to/cifs

  • Samba
    • ファイル名の扱いをcase-sensitiveにする

      case sensitive = yes
      case preserve = no
      short preserve case = no

ZFSが自動マウントされない時はzfs_enable="YES"になっているか確認する

FreeBSDで起動時にルートプール以外のZFSファイルシステムが自動マウントされない時は、/etc/rc.confzfs_enable=“YES”しているか確認する。この指定がなくとも、ルートプール=システムが入っているプールは自動マウントされるので、なかなか問題に気づきにくい。自動マウントされないFSのcanmountmountpointプロパティを見ても問題ないし、原因が判るまで苦労したんだぜ★

インストーラに頼らず、手動でFreeBSDをインストールした場合なんかは特に注意が必要だ。

今更ながらZFSはキャッシュのヒット率が超重要

この記事には技術的裏付けがない、個人の感想、雑感、推測がふんだんに含まれています。ご利用の際は用法・用量を守り正しくお使いください。

知人のNAS4Freeなファイルサーバが重い問題、Sambaが原因で一件落着かと思いきや解決してなかった。(こっちはこっちで別の問題が発生してたりするので別途書く予定。)

知人とやり取りしつつCPU, ネットワーク, ディスクI/O, その他諸々を継続的にモニタリングしてみると、どーにもディスクアクセスがボトルネックになっている事があるようで…。ファイルサーバのターミナルで直接cpしても、全然速度が上がらないのだ。対象のファイルは、50~200kBの数十万個のPNGを含む膨大なファイル群で、ファイルシステム的に結構厳しい条件ではあるものの、ストレージは仮にもHDD 2ペア×3セットからなるRAID10である。十数MB/sは出るだろうと思ってたが、実際には1MB/s以下になることさえある。

いくらなんでもオカシイだろうとzpool iostatしてみた結果がこちら。

               capacity     operations    bandwidth
pool        alloc   free   read  write   read  write
----------  -----  -----  -----  -----  -----  -----
zdata       4.56T  6.31T  5.65K    435  25.5M  4.94M
zdata       4.56T  6.31T  7.43K      0  32.7M      0
zdata       4.56T  6.31T  5.44K      0  24.1M      0
zdata       4.56T  6.31T  6.20K      0  27.3M      0
zdata       4.56T  6.31T  6.44K      0  28.4M      0
zdata       4.56T  6.31T  5.81K    398  26.6M  3.89M
zdata       4.56T  6.31T  4.36K    215  34.8M  10.8M
zdata       4.56T  6.31T  2.76K    391  12.5M  3.47M
zdata       4.56T  6.31T  3.58K      0  19.7M      0
zdata       4.56T  6.31T  3.65K      0  20.1M      0
zdata       4.56T  6.31T  3.15K      0  17.7M      0
zdata       4.56T  6.31T  4.05K      0  19.0M      0
zdata       4.56T  6.31T  2.59K    343  14.6M  3.15M
zdata       4.56T  6.31T  3.66K      0  19.6M      0
zdata       4.56T  6.31T  4.99K      0  32.5M      0
zdata       4.56T  6.31T  2.93K      0  19.1M      0
zdata       4.56T  6.31T  6.38K      0  28.4M      0
zdata       4.56T  6.31T     3K    344  13.6M  2.99M
zdata       4.56T  6.31T  3.86K      0  17.9M      0
zdata       4.56T  6.31T  3.77K      0  16.9M      0
zdata       4.56T  6.31T  3.72K      0  16.8M      0
zdata       4.56T  6.31T  2.91K    226  13.3M  2.39M

読み込み操作数(operations)と読み込み量(bandwidth)の割に、書き込み量が著しく少ない。コピーと並行してSambaへ断続的なアクセスがあるってことを差し引いても全然スループットが出てない。つーか、書き込み量とネットワークに出ていく量を合わせても全然読み込み量に足らん罠。

大量の読み込みoperationsが走ってても、いい感じに処理できてる時は↓こんな感じで順当にbandwidthが上がる。CIFSによるリクエスト分がそのまま処理されてネットワークに流れていっている。

               capacity     operations    bandwidth
pool        alloc   free   read  write   read  write
----------  -----  -----  -----  -----  -----  -----
zdata       4.70T  6.17T  15.3K    638  64.8M  7.06M
zdata       4.70T  6.17T  23.8K      0   101M      0
zdata       4.70T  6.17T  20.4K      0  85.9M      0
zdata       4.70T  6.17T  23.6K      0  98.7M      0
zdata       4.70T  6.17T  18.4K      0  76.5M      0
zdata       4.70T  6.17T  7.06K    720  30.1M  8.41M
zdata       4.70T  6.17T  16.6K      0  70.0M      0
zdata       4.70T  6.17T  13.7K      0  57.7M      0
zdata       4.70T  6.17T  18.1K      0  77.3M      0
zdata       4.70T  6.17T  16.7K    536  72.3M  6.52M
zdata       4.70T  6.17T  7.63K     80  32.5M   376K
zdata       4.70T  6.17T  12.3K      0  52.7M      0
zdata       4.70T  6.17T  9.78K      0  42.4M      0
zdata       4.70T  6.17T  11.8K      0  51.0M      0
zdata       4.70T  6.17T  9.32K    586  40.4M  6.41M
zdata       4.70T  6.17T  9.47K      0  40.6M      0
zdata       4.70T  6.17T  11.6K      0  49.2M      0
zdata       4.70T  6.17T  12.1K      0  51.8M      0
zdata       4.70T  6.17T  5.05K      0  22.2M      0
zdata       4.70T  6.17T  4.88K    579  22.1M  6.61M

rsyncでディスク内コピーを行うと更に悲惨で、びっくりするほど速度が出ない。ネットワーク(1000BASE-T)越しの別マシンにzfs sendし、そいつとrsyncで同期した方が早いっていうね…。すわ、断片化か!?と思ったけど、プールは半分以上空いてるしちょっと考えにくい。

あれこれ考えてるうちに、ZFSはキャッシュのヒット率が重要って事を思い出した。1フォルダに大量の細かなファイルがあるって事は、その分メタデータ処理が重いと考えられる。とすれば、メタデータを使いまくってそうなrsyncで速度が出ないってのも説明が付く………気がしなくもない。

zfs-statsを入れてキャッシュのヒット率を見てみたら、なんと2割を切ってるじゃないの。キャッシュに乗り切らなかったメタデータに毎度アクセスしに行くために、read operationsの割にbandwidthが上がらなかったのかしら…?

とりあえずarc関連のカーネル変数を調整したところ、いい感じでキャッシュヒットするようになった。問題のプール内のディレクトリ間でrsyncした時の結果が↓これ。

キャッシュヒット率が90%ほどに改善し、書き込みも12MB/s程出ている(zdata iostatは1秒毎、vfs.zfs.txg.timeout=5である)。

zpool detachしようとしたらno valid replicasと言われたでござる

ZFSプールが手狭になったため、HDDを順繰り大容量のものにzpool replaceしてた。

$ zpool status zdata
  pool: zdata
 state: ONLINE
status: One or more devices has experienced an error resulting in data
    corruption.  Applications may be affected.
action: Restore the file in question if possible.  Otherwise restore the
    entire pool from backup.
   see: http://illumos.org/msg/ZFS-8000-8A
  scan: resilvered 8.82T in 25h57m with 2 errors on Tue Jan 23 22:47:38 2018
config:

    NAME             STATE     READ WRITE CKSUM
    zdata            ONLINE       0     0     2
      raidz1-0       ONLINE       0     0     4
        ada3p1       ONLINE       0     0     0
        ada2p1       ONLINE       0     0     0
        replacing-2  ONLINE       0     0     0
          ada1p1     ONLINE       0     0     0
          da0p1      ONLINE       0     0     0
        ada5p1       ONLINE       0     0     0
    logs
      mirror-1       ONLINE       0     0     0
        ada6p5       ONLINE       0     0     0
        ada7p5       ONLINE       0     0     0

errors: 2 data errors, use '-v' for a list

replace-2でada1p1をda0p1にreplaceしてた感じ。置き換え中のデバイスには(resilvering)表記が付くのだが、ご覧の通り上記のda0p1には付いてない。てなもんで、処理が終わったと判断し、ada1p1を切り離そうとしたら…

# zpool detach zdata ada1p1
cannot detach ada1p1: no valid replicas

と、言われてしまった所で日記タイトルなのである。

「有効なレプリカがない」とな…。ZFS暦7年目にして初めて目にするエラーでござる。errors: 2 data errors, use '-v' for a listで報告された壊れているファイルを消してもダメぽ。それどころか、、、

# zpool status -v zdata
  pool: zdata
 state: ONLINE
status: One or more devices has experienced an error resulting in data
    corruption.  Applications may be affected.
action: Restore the file in question if possible.  Otherwise restore the
    entire pool from backup.
   see: http://illumos.org/msg/ZFS-8000-8A
  scan: resilvered 8.82T in 25h57m with 2 errors on Tue Jan 23 22:47:38 2018
config:

    NAME             STATE     READ WRITE CKSUM
    zdata            ONLINE       0     0     0
      raidz1-0       ONLINE       0     0     0
        ada3p1       ONLINE       0     0     0
        ada2p1       ONLINE       0     0     0
        replacing-2  ONLINE       0     0     0
          ada1p1     ONLINE       0     0     0
          da0p1      ONLINE       0     0     0
        ada5p1       ONLINE       0     0     0
    logs
      mirror-1       ONLINE       0     0     0
        ada6p5       ONLINE       0     0     0
        ada7p5       ONLINE       0     0     0

errors: Permanent errors have been detected in the following files:

        zdata/NFC/data/Decomo:<0x196090>

なんやねん、zdata/NFC/data/Decomo:<0x196090>って。こうなったら神頼みのzpool scrubするしかない。

# zpool scrub zdata
$ zpool status zdata
  pool: zdata
 state: ONLINE
status: One or more devices has experienced an error resulting in data
    corruption.  Applications may be affected.
action: Restore the file in question if possible.  Otherwise restore the
    entire pool from backup.
   see: http://illumos.org/msg/ZFS-8000-8A
  scan: scrub in progress since Tue Jan 23 23:39:17 2018
        100G scanned out of 18.1T at 427M/s, 12h15m to go
        25.0G repaired, 0.54% done
config:

    NAME             STATE     READ WRITE CKSUM
    zdata            ONLINE       0     0     0
      raidz1-0       ONLINE       0     0     0
        ada3p1       ONLINE       0     0     0
        ada2p1       ONLINE       0     0     0
        replacing-2  ONLINE       0     0     0
          ada1p1     ONLINE       0     0     0
          da0p1      ONLINE       0     0     0  (repairing)
        ada5p1       ONLINE       0     0     0
    logs
      mirror-1       ONLINE       0     0     0
        ada6p5       ONLINE       0     0     0
        ada7p5       ONLINE       0     0     0

修復が始まった。replaceでのresilveringは一体なんだったのか…。しばらく放置してからプールの状態を見てみると、、、

$ zpool status zdata
  pool: zdata
 state: ONLINE
status: Some supported features are not enabled on the pool. The pool can
    still be used, but some features are unavailable.
action: Enable all features using 'zpool upgrade'. Once this is done,
    the pool may no longer be accessible by software that does not support
    the features. See zpool-features(7) for details.
  scan: scrub repaired 4.51T in 15h42m with 0 errors on Wed Jan 24 15:21:24 2018
config:

    NAME        STATE     READ WRITE CKSUM
    zdata       ONLINE       0     0     0
      raidz1-0  ONLINE       0     0     0
        ada3p1  ONLINE       0     0     0
        ada2p1  ONLINE       0     0     0
        da0p1   ONLINE       0     0     0
        ada5p1  ONLINE       0     0     0
    logs
      mirror-1  ONLINE       0     0     0
        ada6p5  ONLINE       0     0     0
        ada7p5  ONLINE       0     0     0

errors: No known data errors

修復が終わりデバイスの置き換えまで行われてる!

今回得た知見:

  • zpool replaceでは、新しいデバイスの再同期が終わると旧デバイスが自動でdetachされる
  • 可能ならzpool replace前にscrubでプールの状態を確認するのが吉

ZFSで使ったことがあるHDD/SSDを再利用する時はzpool labelclearすべき

一度でもZFSで使ったことがあるストレージを別のzpoolに使い回す際は、zpool labelclearコマンドで当該デバイスからZFSラベル情報を消した方が良さそうだ。

再利用デバイスで新しいプールを作ろうとすると、殆どの場合は以下のようなエラーが出て作ることができない。

# zpool create ztank da0p3
invalid vdev specification
use '-f' to override the following errors:
/dev/da0p3 is part of potentially active pool 'zroot'

「有効なプールzrootの一部かもよ」とZFS先生は仰ってるものの、zrootは間違いなくdestroy済みで、僕は確信を以てda0p3でztankを作りたいのだよ。先生ったらお節介~。destroyしたんだからラベル情報も一緒に消しとけよ!と思わなくもないが、ZFSではdestroyの取り消しをサポートしてたりする関係で、こういう仕様なのだろう。削除済みプールの復元については、zpool importの-Dオプションを調べておくんなまし。

ところがである、条件は不明だが、このお節介が働かず以前のラベル情報を残したまま新プールが作れちゃうことがある。こうなるとマジで大惨事。新しいプールと昔のプールが管理情報の上では存在する事になり、見るからにヤバげな状態となる。その時の再現ログが↓これ。

# zpool status
  pool: newtank
 state: ONLINE
  scan: none requested
config:

        NAME        STATE     READ WRITE CKSUM
        newtank     ONLINE       0     0     0
          da0p3     ONLINE       0     0     0

errors: No known data errors

  pool: oldtank
 state: UNAVAIL
  scan: none requested
config:

        NAME                      STATE     READ WRITE CKSUM
        oldtank                   UNAVAIL      0     0     0
          1234567890123456789     UNAVAIL      0     0     0

errors: No known data errors

ログ上からは、別々のストレージで構成される2つのプール、newtankとoldtankがあるようにしか見えない。しかし実際のところ、oldtankはnewtank以前にda0p3で使っていたもので、既にdestroy済みのプールなのである。newtankとして実際にデータが書き込まれ、意味消失してるハズの削除済みプールoldtankが認識されているのだ…!

こうなると最早手遅れ。oldtankを再度destroyしようにも「そんなプールは存在しない」と言われるし、ならばとデバイス1234567890123456789をどうにかする方向で頑張ってもどうにもならず…。かと言って、この段階でlabelclearは絶対にしてはいけない。newtankもろとも消えてしまうので_:(´ཀ`」∠):_(実体験)。

というわけで、ZFSプールを作る時は忘れずにlabelclearで以前の情報を消すようにしたい。


(2017-11-14 追記)

タイミングよく本症状が発生したのでスクショをうp。

やった事はと言うと…

  1. 以前のzroot(赤い方)を削除しlabelclear
  2. 新しいzroot(緑の方)を作成
  3. FreeBSD 11.0-RELEASEをインストール
  4. freebsd-updateで11.1-RELEASEに更新するも、ブート時のTrying to mount root~で失敗して起動しなくなる
    • 今にして思えば、この時点で古いプールが認識されて旧zrootの方をマウントしようとしてたのかも…?
  5. kernel.oldの方から起動してfreebsd-updateで11.0の最新にしようとする
  6. 見事に失敗して起動しなくなる。起動スプラッシュのシェルでload kernelもload zfsも失敗する状態。
  7. インストーラメディアから起動してzpool importしたらこの状態

確かにlabelclearしたハズなんだけどなぁ…。変なzpool.cacheでも食ってんのかなぁ…?わからん。


(2017-11-16 追記)

あ、ありのまま起こったことを(ry。

症状が出たHDDの各パーティションを再度labelclearしてddでゼロフィルまでしたにもかかわらず、古い方のzrootが認識されやがったぜ……。最終的にzpool labelclear da0してようやく認識されなくなった。(が、当然GPTは壊れる。セカンダリテーブルは生きてるからパーティションの認識と復旧は可能だが…。)GPTの領域にZFSラベルが書き込まれそうな使い方─つまりディスク全体をzpoolにするような使い方はした記憶がないんだが、どうしてこうなった……?

結論としては、zpoolを作る時は対象デバイスの/dev/daX/dev/daXpYを全てlabelclearするのが安全、可能なら全体をゼロフィルするということで。

start.txt · 最終更新: 2016-05-07 17:46 by decomo
CC Attribution-Noncommercial-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0