NFSのブロックサイズ

先日(8月24日)、ビックカメラ.comにて注文済みであったI-O DATA製の1000BASE-T対応スイッチングハブ ETG4-SH8Nが到着した。同時に注文したLogicool製のレーザーマウス MX-400BKは既に8月20日には到着していたのだが、在庫の関係上このように別送となったようだ。価格はETG4-SH8Nが8380円がMX-400BKが4980円で併せて2236ポイント還元であった。
MX-400BKは単に入学祝いとしてマウスを購入するためにもらっていた5000円があったために購入しただけなのだが、ETG4-SH8Nは1000BASE-Tのネットワークを構築するために購入した。別に100BASE-TXの速度に不満があるわけではないのだが、オンボードLANがギガビット対応を果たしているのにそれを利用しないのはもったいないということで導入に踏み切った次第だ。なお、到着当日にケーブルのつなぎ替えは終了しているが、ジャンボフレームの設定はまだ行っていない。
本当はこの記事中にて導入の成果を数字を以って示す予定であったのだが、まず最初にNFSの最適なブロックサイズを計測してみたくなったのでそこから行うことにした。よって、導入の効果についてはまた日を改めて書こうと思う。
まず最適なブロックサイズの決め方だが、Linux NFS-HOWTO5.1. ブロックサイズを設定して転送速度を最適化するを参考にした。まず、指示通りに最大ブロックサイズを確認する。Fedora 7では以下のようにコマンドを実行する。

$ cat /usr/src/kernels/`uname -r`-`uname -m`/include/linux/nfsd/const.h | grep NFSSVC_MAXBLKSIZE
#define NFSSVC_MAXBLKSIZE       RPCSVC_MAXPAYLOAD
#define NFSSVC_MAXBLKSIZE_V2    (8*1024)
#define NFSD_BUFSIZE            ((RPC_MAX_HEADER_WITH_AUTH+26)*XDR_UNIT + NFSSVC_MAXBLKSIZE)

出力から定数NFSSVC_MAXBLKSIZEは定数RPCSVC_MAXPAYLOADと同値であることが分かる。そこでGoogleで検索すると定数RPCSVC_MAXPAYLOADはカーネルソース中の./include/linux/sunrpc/svc.hにて宣言されているので次のようにコマンドを実行する。

$ cat /usr/src/kernels/`uname -r`-`uname -m`/include/linux/sunrpc/svc.h | grep RPCSVC_MAXPAYLOAD
#define RPCSVC_MAXPAYLOAD       (1*1024*1024u)
#define RPCSVC_MAXPAYLOAD_TCP   RPCSVC_MAXPAYLOAD
#define RPCSVC_MAXPAYLOAD_UDP   (32*1024u)
#define RPCSVC_MAXPAGES         ((RPCSVC_MAXPAYLOAD+PAGE_SIZE-1)/PAGE_SIZE \

出力から定数RPCSVC_MAXPAYLOADすなわち定数NFSSVC_MAXBLKSIZEの値は1024*1024=1048576*1である。つまり1024KBが最大ブロックサイズとなる。
最大ブロックサイズが分かったところで実際の計測に入る。NFS(5)のmanページを見るとブロックサイズのデフォルトは1KB(=1024Bytes)となっているが、なにしろ1993年当時のデフォルト値であるから14年も経過した現在においてはあまり意味はない。ということで同manページにある8KB(=8192Bytes)を最小とすることにした。*2
ベンチマークを行うソフトウェアを用いる方法もあるが、導入するのが面倒なので自分でシェルスクリプトを作成して使用した。*3計測の仕方はNFS-HOWTOで示されているものと同一だ。以下にそれを示す。コメントを参考に変数の値を書き換えれば誰でも使用できるようになっている。

ファイル名: nfsBlockSizeTest.sh

#!/bin/sh

# Specify the block sizes you want to test
nfsBlockSize="8192 16384 32768 65536 131072 262144 524288 1048576"

# Specify the directory where you mount NFS
mountDir=/mnt/nfs

# Specify the host name of the NFS server
hostName=example.com

# Specify the directory on the NFS server you want to mount
hostDir=/home/foo

# Specify the option mount command uses
mountOptions=defaults,hard,intr,tcp

# Specify the block size and count for dd
# $ddBlockSize * $ddCount / 1024 / 1024 >= the amount of RAM on the NFS Server
ddBlockSize=32768
ddCount=65536

function blockSizeTest {
        for i in 1 2 3 4 5 ; do
                echo Test No.$i - Block Size: $1
                umount $mountDir
                mount -t nfs $hostName:$hostDir $mountDir -o $mountOptions,rsize=$1,wsize=$1
                time dd if=/dev/zero of=$mountDir/testFile bs=$ddBlockSize count=$ddCount
                echo
                time dd if=$mountDir/testFile of=/dev/null bs=$ddBlockSize
                echo
                rm -f $mountDir/testFile
                echo ---
                echo
        done
}

echo NFS Block Size Test
echo

for i in $nfsBlockSize; do
        blockSizeTest $i
done

echo
echo Test was complete!

このスクリプトを以下のようにrootで*4実行する。なお、スクリプトNFSボリューム上に置いて実行してしまうとumount/mount出来なくなり全く意味をなさないので注意する必要がある。*5

bash nfsBlockSizeTest.sh &> result.txt

単純に"&>"で標準出力及び標準エラーをリダイレクトしているだけである。進捗状況が分かるのでscriptコマンドを使うのも良いだろうが、ハブのLEDの点滅の有無で試験中か否かは判断出来るので、今回はこうした。なお、各ブロックサイズについて5回ずつ計測を行うので終了までには結構時間がかかる。だいたい2時間以上はかかると思っておいた方が良い。
まず結果を示す前に計測に使用したマシンのスペックを記しておく。なお、全てのNICは32bit 33MHz PCI接続であり、NICの名称はlspciの実行結果をそのまま使用した。

NFSサーバー: IBM Intellistation M Pro 6849-34J
CPU: Pentium 4 1.7GHz
メモリ: RDRAM PC800 ECC 1024MB
NIC: Intel Corporation 82541PI Gigabit Ethernet Controller (rev 05)
OS: Cent OS 5 (2.6.18-8.1.8.el5)

クライアントa: DELL Latitude D400
CPU: Pentium M 1.40GHz
メモリ: PC2100 DDR SDRAM 512MB
NIC: Broadcom Corporation NetXtreme BCM5705M Gigabit Ethernet (rev 01)
OS: Fedora 7 (2.6.22.4-65.fc7)

クライアントb: IBM Intellistation M Pro 6219-4D6
CPU: Intel Pentium 4 3.06GHz (HT対応)
メモリ: PC2100 DDR SDRAM 2048MB
NIC: Broadcom Corporation NetXtreme BCM5702X Gigabit Ethernet (rev 02)
OS: Fedora 7 (2.6.22.1-41.fc7)

計測結果をそのまま載せてもあまり意味が無いので5回の転送時間の平均について各ブロックサイズ毎に示した棒グラフを以下に示す。なお、Readはサーバーからの読み込み、Writeはサーバーへの書き込みをそれぞれ表す。

このグラフから、多少の測定誤差はあるにしても、ブロックサイズが32KB以上になってもファイルの転送にかかる時間はほとんど変化しないことが分かる。したがって、ひとまずはブロックサイズを32KBとして使用することにした。今後は1000BASE-T環境の導入効果について検証していこうと思う。

*1:1024uのuは符号なしint型を表す。

*2:最大値は先述のように1024KBとする。

*3:結局時間的なコストがベンチマークを利用した場合のそれを上回っていることは気にしない。

*4:キャッシュの効果を無効化するために毎回mount/umountを実行する必要があるため。

*5:普通はこのような過ちは犯さないはずであるが、自分が犯してしまった以上ここに書き留めておくことは、未来の自分にとって意味があるかもしれない。