5. NFS の性能を最適化する

ネットワーク設定を正しくすると、 NFS の性能は何倍も向上することがあります (転送速度 10 倍、というのも聞いたことがあります)。 そのうち最も重要なのは、 mountrsize オプションと wsize オプションです。 以降に示す他の要素も、 特定のハードウェアを使っている人には 効果があるかもしれません。

5.1. ブロックサイズ設定で転送速度を最適化する

mountrsize オプションと wsize オプションは、 クライアントとサーバがデータをやりとりするときの データの転送単位を指定するものです。 それぞれのオプションが指定されないときのデフォルト値は、 使っている NFS のバージョンによって異なります。 ほとんどの場合のデフォルトは 4096 バイトですが、 2.2 カーネルにおける TCP ベースでのマウントや、 2.4 カーネル以降でのあらゆるマウントでは、 サーバがデフォルトのブロックサイズを指定します。

デフォルトの値は大きすぎ/小さすぎかもしれません。 全ての、あるいは大抵の設定に有効なサイズ、というものはありません。 例えば Linux カーネルとネットワークカードの組み合わせによっては (ほとんどは古いマシンでの話)、あまり大きなブロックは扱えません。 一方大きなブロックが扱えれば、大きなサイズの転送は高速になります。

そこでここでは、実験を行って、最速になるような rsize や wsize を決定するやり方を述べることにします。 ある設定にしたときの転送速度は、 いくつかの簡単なコマンドで調べられます。

最初に実行すべきコマンドは、16k のブロック 16384 個を、 特殊ファイル /dev/zero (読み込むと 0 を「非常に」高速に吐き出してきます) からマウントしたパーティションに転送するものです。 どのくらい時間がかかるかを time で測りましょう。 よって、クライアントマシンから次のように入力します。
    # time dd if=/dev/zero of=/mnt/home/testfile bs=16k count=16384
 

こうすると (バイトデータの) 0 で埋めつくされた、 大きさが 256Mb のファイルができます。 一般には、サーバに積んである RAM のサイズの 少なくとも 2 倍の大きさのファイルを作るべきです。 (ただしディスクに空きがあるか、確認を忘れないこと!)。 次にそのファイルを、クライアントのブラックホール (/dev/null) に読み出します。 次のように入力してください。
    # time dd if=/mnt/home/testfile of=/dev/null bs=16k
  

これを何回か繰り返して、かかった時間を平均してください。 対象のファイルシステムを毎回アンマウント→再マウントして (クライアントと、こだわる方はサーバでも)、 キャッシュの効果をすべてクリアするのを忘れずに。

終わったらアンマウントし、ブロックサイズを大きくしたり 小さくしたりしてもう一度マウントしてください。 NFS version 2 の最大サイズは 8192 バイトなので、 これよりも大きくしないほうが良いでしょう (しかし Version 3 なら 32768 まで試してみましょう)。 値は 2 の冪乗で変えるのが賢いと思います。 転送に関連するパラメータ (ファイルのシステムブロックサイズや ネットワークのパケットサイズなど) も、 たいていは 2 倍ずつ変わるからです。 ただし、ブロックサイズを 2 の冪乗以外の値にして、 より良い結果を得たユーザもいます。 しかしその場合でも、 システムのブロックサイズやネットワークパケットサイズの 整数倍にはなっていました。

大きなサイズでマウントしたら、 そのファイルシステムに cd し、ls するなどして、 ファイルシステムの中味が正しく見えるか調べてみて下さい。 rsize や wsize が大き過ぎると、妙な兆候が現われ、 ファイルの信頼性が 100% でなくなります。 よくある例としては 「ls してもすべてが表示されない、エラーメッセージも出ない」とか 「エラーメッセージは出ないのにファイルの読み込みになぜか失敗する」 などがあります。 さて、与えた rsize/wsize でシステムが正しく動作していることがわかったら、 もう一度速度のテストをしてみましょう。 サーバの OS が違うと最適なサイズも異なる場合が多いです。 評判によると、 SunOS や Solaris の場合は 4096 が他に比べてずっと速かったりするそうです。

最後に /etc/fstab を編集して、 決まった rsize/wsize の値を反映させるのを忘れないように。

5.2. パケットサイズとネットワークドライバ

Linux にはあまり出来のよくないネットワークドライバーが (比較的有名なカードのものも含めて) 多く存在します。

2 台のマシンの間で ping をやり取りしてみましょう。 その際 ping-f オプションと -s オプションを用いて (詳細は man ping を見てください) 大きなパケットを使い、 パケットロスが起きていないか、 リプライに時間がかかっていないかを見てみましょう。 このような障害が起きている場合は、 ネットワークカードの性能に問題があるかと思われます。

このような問題を修正するには、ネットワークカードの用いている パケットサイズを再設定するといいでしょう。 2 台のマシンの間でやり取りできるパケットサイズの最大値は、 ほとんどの場合ネットワークのどこか (例えばルータ) において、 ネットワークカードのものより小さな値に制限されています。 TCP ではネットワークに対して適切なパケットサイズを 自動的に見つけるようになっていますが、 UDP では単にデフォルトの値を使うだけです。 従って、特に UDP 上で NFS を使っている場合には、 適切なパケットサイズを決めることは非常に重要です。

ネットワークパケットサイズのテストは、 tracepath コマンドによって行えます。 クライアントマシンから、単に tracepath [server] 2049 と入力すれば、一番下に path MTU が表示されます。 次に ifconfig の MTU オプションを使って、 ネットワークカードの MTU を path MTU の値と同じにします。 そしてパケット落ちが少なくなるか確認してください。 MTU の再設定方法の詳細は ifconfig の man ページを見てください。

5.3. NFSD のインスタンスの数

Linux でも他の OS でも、ほとんどの起動スクリプトでは、 nfsd のインスタンスを 8 つ起動します。 NFS の最初のころに Sun はこの値を経験則から決め、 その後はみんなこの値をコピーしているのです。 どのくらいのプロセス数が最適かを決める良い基準はありませんが、 トラフィックの大きいサーバではより大きな値にするのが良いでしょう。 2.4 以降のカーネルを使っている人は、各 nfs スレッドが どのくらい使われているかを /proc/net/rpc/nfsd で見てみるといいでしょう。このファイルの th 行の最後の 10 個の数字は、 そのスレッドが、割り当て可能な最大値の、 そのパーセンテージの状態にあった秒数を示しています。 最初の 3 つの値が大きいときは、 nfsd のインスタンスを増やすほうが良いでしょう。 これを行うには、 nfsd を起動するときのコマンドラインオプションで インスタンスの数を与えます。詳細は nfsd の man ページを見てください。

5.4. 入力キューのメモリ制限

2.2 と 2.4 のカーネルでは、ソケットの入力キュー (処理中の要求が待つところ) のデフォルトの大きさは小さく、 64k に過ぎません。つまり、nfsd のインスタンスを 8 つ走らせているとすれば、 各々には処理対象の要求を保存する場所が 8k ずつしかないことになります。

nfsd に対しては、 このサイズを少なくとも 256k にまで増やすことを考えるべきです。 この上限値は proc ファイルシステムの /proc/sys/net/core/rmem_default/proc/sys/net/core/rmem_max を用いて設定します。増加させるには 3 つのステップを踏みます。 以降に示す方法はちょっとあらっぽいですが、 ちゃんと動作しますし、問題を起こすこともないはずです。

  1. これらのファイルに書かれているサイズを増加します:
       echo 262144 > /proc/sys/net/core/rmem_default
       echo 262144 > /proc/sys/net/core/rmem_max
        

  2. nfsd を再起動します。例えば RedHat なら /etc/rc.d/init.d/nfsd restart と入力します。

  3. サイズの上限値を通常の値に戻し、 他のカーネルシステムはこちらを使うようにします。
     
         echo 65536 > /proc/sys/net/core/rmem_default
         echo 65536 > /proc/sys/net/core/rmem_max
       

    この最後のステップを忘れないように。 この値を長い事変えたままにしておくと、 マシンがクラッシュするというレポートも受けています。

5.5. フラグメントされたパケットのオーバーフロー

NFS プロトコルはフラグメント化された UDP パケットを用います。 カーネルには、 不完全なパケットのフラグメントをいくつまで保存しておくかの上限値があり、 これを越えるとパケット (の断片) は捨てられ始めることになります。 /proc ファイルシステムをサポートする 2.2 カーネルでは、ファイル /proc/sys/net/ipv4/ipfrag_high_thresh/proc/sys/net/ipv4/ipfrag_low_thresh を編集すれば、この値を指定できます。

処理待ちのパケットの断片が ipfrag_high_thresh に指定した値 (バイト単位) を越えると、 カーネルは単にパケットの断片を捨てはじめ、 サイズの合計が ipfrag_low_thresh に指定した値 (2.2 カーネルでのデフォルトは 256K) になるまで捨て続けます。 これは外部からはパケットロスのように見え、 高い方の閾値に達すると、サーバの性能は大きく劣化します。

これをモニタする方法の一つは、 ファイル /proc/net/snmp の IP: ReasmFails フィールドに注目することです。 重いファイル操作の際にこの値があまりに急激に上昇する場合は、 おそらく問題が生じています。 ipfrag_high_threshipfrag_low_thresh に、(デフォルト以外の) どの様な値を指定すると良いのかは、 まだ報告がありません。もし特定の値で良い状態が得られた場合は、 本文書のメンテナや開発チームに知らせてください。

5.6. NIC とハブの自動ネゴシエーションを無効にする

ネットワークカードの中には、 ハブやスイッチとの自動ネゴシエーションがうまくできず、 妙な症状を起こすものがあります。 また、ハブで別々のポートを異なったスピードで動かしていると、 パケットをロスすることがあります。 ネットワークの速度と全二重の設定を適切にしてあげてください。

5.7. サーバの性能をあげる NFS 以外の方法

うまく機能するファイルサーバの設定方法に関する 一般的なガイドラインを説明することは、 本文書の目的からは外れています。 しかし、いくつかの方法は紹介しておく価値があると思います。 まず、RAID 5 を使うと読み出しは速くなりますが書き込みは遅くなります。 速度と冗長性の両方が必要な場合は RAID 1/0 の利用を考えましょう。 次に、ジャーナリングファイルシステムを用いると、 システムがクラッシュしたときの再起動時間が非常に減少します。 本文書の執筆時点では、NFS version 3 と一緒にちゃんと動作する ジャーナリングファイルシステムは ext3 (ftp://ftp.uk.linux.org/pub/linux/sct/fs/jfs/) だけですが、もちろんすぐに状況は変わるでしょう。 特に Reiserfs は、2.4 カーネルでは NFS version 3 といっしょに使えそうです (しかし 2.2 カーネルではだめです)。 最後に、オートマウンタ (autofs や amd) を使うと、 クロスマウントを (わざとでもうっかりでも) したマシンの どちらかが落ちたときでも、もう片方のハングアップを避けられます。 詳細は Automount Mini-HOWTO (日本語訳が JF にあります) を見てください。