ホーム » 技術 » Linux KVM » KVMを使う(ディスク性能編)

SAKURA Internet Inc.

アーカイブ

KVMを使う(ディスク性能編)

KVMをとりあえず動かそうというときは、10GB程度の小さなイメージファイルから始めると思います。当ブログのインストール編でも例示した通り、また他のインストールガイドでも取り上げられている通り、qcow2フォーマットで作成したイメージファイルを使用している方が多いと思われます。ところが本格的にテストをしようと思って100GBぐらい割り当ててインストールしてみると、途方もない時間がかかってびっくりすることがあります。

たとえば当方で計測してみたCentOSのインストールの時間は、qcow2を使い100GBのディスクを割り当てた場合に、フォーマットの所要時間が47分、パッケージのコピーで13分、合計で1時間もかかってしまいます。実サーバにインストールすればせいぜい10分ぐらいですから、恐ろしいほどのパフォーマンスの低下です。

まっさきに疑うのは、qcow2というフォーマットそのものです。試しにrawフォーマットでインストールしてみると、フォーマットは21分、パッケージコピーは7分、合計28分間とある程度改善します。この数字だけみると「qcow2なんてダメだ、rawフォーマットで決まりだな」と思いますが、本当にその結論でいいのか、ちょっと調べてみました。

フォーマット所要時間の比較

まずは実験環境で計測してみます。インストールの経過時間を測る実験は、ストップウォッチを持って画面を眺めていないといけないので面倒です。そこでmkfsにかかる時間を測ることにして、自動化を図ります。ゲストOSはあらかじめインストールを終わらせておき、テスト環境として利用します。パフォーマンスを測るためのディスクイメージはそれぞれ別に作っておき、ゲストOSの第2、第3のパーティションとしてアタッチして、これをmkfsでフォーマットするのです。

rawとqcow2のイメージファイルを作ります。

host# qemu-img create -f raw test-raw.img 100G
Formatting 'test-raw.img', fmt=raw size=107374182400
host# qemu-img create -f qcow2 test-qcow2.img 100G
Formatting 'foo', fmt=qcow2 size=107374182400 encryption=off cluster_size=0

ゲストOSは別のイメージファイルとして、インストールを終わらせておきます。比較のため、ホストOSと同じFedra 12を用意しておきました。コマンドラインはこんな感じです。モニタのポートやVNC設定等は省略していますので、必要ならば補ってください。

qemu-system-x86_64 -hda fedora.img -hdb test-raw.img -hdc test-qcow2.img ...

これを起動すると、ゲストOSからは/dev/sda→fedora.img、/dev/sdb→test-raw.img、/dev/sdc→test-qcow2.imgとなって参照できるようになります。

フォーマットを行う前に、fdiskを使って/dev/sdbと/dev/sdcにパーティションを割り当てます。特別な指定は不要なので、領域全体を1つのパーティションにしてしまいます。

guest# fdisk /dev/sdc                    ←qcow2パーティションの例
Device contains neither a valid DOS partition table, nor Sun, SGI or OSF disklabel
Building a new DOS disklabel with disk identifier 0x6d828872.
Changes will remain in memory only, until you decide to write them.
After that, of course, the previous content won't be recoverable.

The number of cylinders for this disk is set to 13054.
There is nothing wrong with that, but this is larger than 1024,
and could in certain setups cause problems with:
1) software that runs at boot time (e.g., old versions of LILO)
2) booting and partitioning software from other OSs
 (e.g., DOS FDISK, OS/2 FDISK)
Warning: invalid flag 0x0000 of partition table 4 will be corrected by w(rite)

Command (m for help): p                     ←パーティションが空であることをチェック

Disk /dev/sdb: 107.4 GB, 107374182400 bytes
255 heads, 63 sectors/track, 13054 cylinders
Units = cylinders of 16065 * 512 = 8225280 bytes
Disk identifier: 0x6d828872

 Device Boot      Start         End      Blocks   Id  System

Command (m for help): n                     ←割り当て開始
Command action
 e   extended
 p   primary partition (1-4)
p                                           ←とりあえずpでOK
Partition number (1-4): 1                   ←1を指定
First cylinder (1-13054, default 1):        ←デフォルトでいいので空リターン
Using default value 1
Last cylinder, +cylinders or +size{K,M,G} (1-13054, default 13054):
Using default value 13054                   ↑同じくデフォルトで

Command (m for help): w                     ←パーティション情報を書き込んで終了
The partition table has been altered!

Calling ioctl() to re-read partition table.
Syncing disks.

続いてmkfs.ext4を使ってそれぞれのディスクをフォーマットします。経過時間はtimeで測ればよいでしょう。

guest# time mkfs.ext4 /dev/sdb1
mke2fs 1.41.9 (22-Aug-2009)
Filesystem label=
OS type: Linux
Block size=4096 (log=2)
Fragment size=4096 (log=2)
6553600 inodes, 26214055 blocks
1310702 blocks (5.00%) reserved for the super user
First data block=0
Maximum filesystem blocks=4294967296
800 block groups
32768 blocks per group, 32768 fragments per group
8192 inodes per group
Superblock backups stored on blocks:
 32768, 98304, 163840, 229376, 294912, 819200, 884736, 1605632, 2654208,
 4096000, 7962624, 11239424, 20480000, 23887872

Writing inode tables: 386/800

さてこれを実行してみると、まず「Writing inode tables: xxx/800」のところで非常に大きな差がでることが分ります。最初、カウンタは勢いよく回り始めるのですが、80になったとたんがくっと遅くなります。inode tableは128MBごとに1ブロック割り当てられるので、10GBを超えたあたりから遅くなり始めるということです。この実験をやってみるとrawフォーマットでは20分、qcow2では39分かかりました。qcow2はrawのほぼ2倍、余分に時間がかかっていることになります。

試しにもう一度やってみる

歴然とした差が出たので結論を出してもいいような気がしますが、念のためもう一度やってみましょう。テスト環境はそのまま、リブートせずにもう一度同じコマンドを投入して測ってみます。するとrawフォーマットでは51秒、qcow2では1分8秒と、劇的に速くなりました。

2度目に試した結果がどうしてこんなに改善するのかというと、キャッシュが効いているからに違いありません。すぐに思い当たるのは、ホストOSのページキャッシュです。ゲストOSのディスクは、ホストOS上のファイルに過ぎませんから、ページキャッシュの影響度は大きいと予想できます。試しにホストOSのキャッシュをクリアしてみましょう。ゲストOSはそのまま、ホストOS側で次のコマンドを実行します。

host# echo 1 >/proc/sys/vm/drop_caches

ゲストOSに戻って、mkfsの経過時間を測ると、rawフォーマットでは1分59秒、qcow2フォーマットでは2分6秒となりました。グラフにまとめてみるとこうなります。

差が大きすぎるので、2度目(キャッシュが効いた状態)とキャッシュをクリアしたときの2つを切り出してみます。

キャッシュをクリアしても、パフォーマンスの悪化は2倍程度にとどまっており、1度目ほど悪くはありません。ということはどうやら、ページキャッシュ以外にもパフォーマンスに影響する別の要因があるようです。

qcow2とスパース

別の要因とはいったい何でしょう? まずqcow2フォーマットについておさらいしてみましょう。

qcow2のイメージファイルを作成すると、まず気付くのはファイルサイズが指定サイズよりもずっと小さいということです。qcow2は指定された通りのファイルサイズではなく、実際に使用している領域分のみ確保して使うためです。データをどんどん書き込んでいくと、qcow2のファイルはそれにつれて大きくなっていきます。これによりファイルサイズを大幅に節約できるわけです。このことは、ファイルサイズの拡張時にオーバーヘッドが発生し、それがデメリットにもなるだろうと予想できます。

しかし前述の実験では、rawフォーマットでも1度目は時間がかかっていました。rawフォーマットのファイルサイズは、ls -lで確認すると指定したとおり100GBありますから、qcow2のようにファイルサイズ拡張のオーバーヘッドはありえず、遅延の原因はないように思えます。

実はここに落とし穴がひとつあります。qemuのマニュアルを読んでみましょう。

‘raw’

Raw disk image format (default). This format has the advantage of being simple and easily exportable to all other emulators. If your file system supports holes (for example in ext2 or ext3 on Linux or NTFS on Windows), then only the written sectors will reserve space. Use qemu-img info to know the real size used by the image or ls -ls on Unix/Linux.

要点だけ訳すとこうなります。「もしファイルシステムがホールをサポートしていれば、利用容量の確保だけを行う。実際の容量を確認するならば、infoオプションを使うか、Unixのls -lsコマンドを使う」

ホールとは何のことでしょうか。書かれている通り、コマンドを使って確かめてみましょう。

host# qemu-img create -f raw foo 100G
Formatting 'foo', fmt=raw size=107374182400
host# qemu-img info foo
image: foo
file format: raw
virtual size: 100G (107374182400 bytes)
disk size: 0
host# ls -ls foo
0 -rw-r--r--. 1 root root 107374182400 2010-03-08 16:35 foo

このように実際のファイルサイズを見てみるとゼロ、すなわちrawフォーマットのファイルは、生成時点では領域が割り当てられておらず、サイズ情報のみ100GBになっていることが分ります。

このようなホールを持つファイルをスパースファイルと呼びます。ファイルのスパース化はファイルシステムが提供する機能で、Linuxのextフォーマットはこれをサポートしています。そしてスパースファイルもqcow2と同様に、実際に必要が生じたときにディスク領域の確保を行います。つまりスパースファイルを使うと、新規に作成されたqcow2ファイルと同じく、ファイル拡張のためのオーバーヘッドがかかってしまうのです。

スパースファイルを作る方法はそれほど難しくありません。lseek()を使ってサイズだけ指定すればいいのです。しかしここではスパースファイルにしない方法を考えなければいけません。qemu-imgにはスパースファイルを回避するオプションが見当たらないため、このままでは使用することができません。困りましたね。

非スパースファイルを作る

すでに述べたとおり、rawフォーマットのイメージファイルは空っぽ、ゼロが並んだファイルに過ぎません。それならqemu-imgを使わなくとも、ddコマンドで作れそうです。100GBのイメージファイルはぴったり100GBのサイズですから、同じサイズのゼロから成るファイルを作ればよいのです。

host# dd if=/dev/zero of=test-real-raw.img bs=1M count=102400

こうして生成したファイルをKVMで実際に使ってみます。同じくフォーマットのテストを実施してみると、1度目は1分58秒、2度目は50秒となりました。2度目終了の後ホストOSでキャッシュをクリアすると1分59秒となりました。これらの結果を先ほどのグラフに追加してみます。

本来であれば、計時は何度か繰り返して平均をとるべきなのですが、色々面倒なのでデータは1発取りです。そのため推測よりもデータがよくなったり悪くなったりしていますが、とりあえず無視できる範囲ではないかと思います。

まとめ

今回はファイルフォーマットにかかる時間を例に、パフォーマンスの違いと理由について調べてみました。qcow2やスパースファイルのようなフォーマットは、ディスクスペースの節約にはアドバンテージがあるかもしれません。しかし領域拡張時のオーバーヘッドは非常に大きく、新規に作成した直後のファイルシステムは壊滅的に遅くなります。この欠点は、qcow2にせよスパースファイルにせよ、ファイルにあいた穴にデータが書き込まれ、容量が増えることでしか解消できません。しかし、そうして容量が増えた場合には、qcow2の省スペースというメリットは吹き飛んでしまい、使う意義がなくなってしまいます。ディスクのパフォーマンスが重要な場面では、ファイルサイズの拡張という成り行きに任せるよりも、最初からホールのないrawフォーマットを用意した方がよいでしょう。

どうしてもqcow2を使いたいのでしたら、ひとつTIPSがあります。ゲストOSのインストールだけはrawフォーマットで行い、完了したらqcow2にコンバートするのです。qemu-img convertを使えば可能です。これによりインストールで20倍もかかってしまう所要時間をある程度削減できます。また一度インストールしたイメージは保存しておき、コピーして使いまわすのもいいでしょう。

なお、インストール完了後のイメージファイルをcpコマンドでコピーするときは、cpがブロック単位でゼロの連なる部分を発見して勝手にスパース化してしまうことに注意 してください。cpでスパース化を避けるには–sparse=neverを指定しなければいけません。詳しいことはcpのmanpageをご覧ください。

KVMのディスクパフォーマンスにまつわる話題には他にもたくさんの要素があります。たとえば単純なリード・ライト性能の計測、kvmオプションの有無による違い、IDEインターフェースの他にvirtioという仮想IOインターフェースによる実装との比較などです。また実際的なアプリケーションによる性能比較も欠かせないでしょう。興味のある方はパフォーマンス測定にぜひトライしてみてください。

参考文献


4件のコメント

コメントは停止中です。