2009年8月20日木曜日

正しいベンチマークをするための10のポイント

 世の中ではたくさんの人が独自にベンチマークを行ない、独自に情報発信がされています。そのベンチマークの中には、非常に参考になるものもあれば、現実性に大きく欠けるものもあります。競合他社が、ライバル社の製品にとって不利な条件でベンチマークを行い、それを発信することも日常的に行われています。ベンチマークの結果を鵜呑みにすることは危険で、結果の意味を判断するスキルを持つことが重要です。これはプロジェクトにおいて負荷テストを行う場合にも重要です。負荷テストの条件設定が正しいかどうかを判断できるようになるためです。
 ここでは、私がDBサーバのベンチマーク/負荷テストを行ったり結果を読んだりする上で、心がけているポイントを10個ほど紹介したいと思います。

■ハードウェアに関する4つのポイント

1. ハードウェアのスペックと設定を注視する
 ハードウェア構成によってベンチマーク結果は劇的に変わるので、言わずもがなでしょう。以下のような点、特にDBサーバであればメモリ容量やストレージ構成に注意を払いたいところです。

・CPU型番、クロック、コア数
・メモリ容量
・ストレージ本数、RAID設定、HDDの回転数、SSDの製品名、ライトキャッシュの設定など
・ネットワーク

 RDBMSにおいては、ライトキャッシュの設定(バッテリーバックアップつきライトキャッシュが有効になっているかどうか)が非常に大切だということも付け加えておきます。


2. 大まかなアクセス性能を知っておく
 CPU、メモリ、HDD、ネットワークなどの各ハードウェアに対して、1回アクセスする際にどの程度の時間がかかるのか、あるいは1秒間に何回程度のアクセスができるのか、(概算値を)即答できるでしょうか。何度もアプリケーション運用を経験していると、このあたりの性能指標は嫌でも意識させられることでしょう。
 ざっくりとした感覚では、現在Linux-DB サーバとして主に用いられているハードウェア環境では、単一接続という条件では、CPUへのアクセス時間が10ns、メモリへのアクセス時間が60ns、HDD へのアクセス時間が5ms、Intel SSDが0.2ms、ネットワーク(Gigabit Ethernet)が0.1ms程度といったところではないでしょうか。データ転送量が多ければそれだけ時間はかかりますし、CPUクロック数やキャッシュの存在によっても値は全然違ってくるので、「正確性」という意味では突っ込みどころがあります。しかし、桁の感覚としてはおおむね正しいでしょう。特に、HDDがメモリどころかネットワークに比べても大きく性能が落ちるというのは、パフォーマンスチューニング上で非常に大切なポイントです。HDD へのアクセス時間が5ms ということは、1秒あたり200回くらいしかアクセスできないことを意味しています。
 こういった性能指標を叩き込んでおけば、ベンチマーク結果を見たときに不自然かどうかを見分けられるようになります。


3. 並列性を考慮する
 上で挙げた性能指標は単一接続時のものですが、アプリケーションでは複数の接続が張られるため、並列性についても考慮する必要があります。同時何接続から処理が行われた状態でのベンチマークなのか、ということです。CPUやメモリは桁違いのアクセス性能なので置いておくとしても、HDD/SSD/ネットワークの並列性に関する理解は重要です。ストレージの場合は、RAIDやコマンドキューイングによって並列性を高めることができます。コマンドキューイングと一口で言っても、HDDはSASとSATAによっても違いますし、HDDとSSDでも大きく変わってきます。SAS HDDでは単一接続でのランダムアクセス性能が200IOPSくらいだったのが、大量接続になると500IOPSくらいを得ることができるでしょう。Intel SSDではこれがさらに跳ね上がって、1接続では毎秒のランダムリード性能が5,000くらいだったのが、大量接続では25,000近くを得ることも不可能ではありません。
 ネットワーク(TCP/IP)も並列性が大変優れています。接続数が数十になった場合、ネットワークだけで見ればトータルで秒間20万回クラスのリモートアクセスも可能で、これはSSDをも大きく上回る性能です。
 RDBMSから見ると、同時接続数が増えた場合、内部的なmutex競合などでスループットが落ちるケースも頻発します。現実問題としては、無尽蔵に接続を張るのではなく、コネクションプーリングやMaxClientsなどで接続数を限定することも必要になってきます。期待されるアクセス性能に対して、どの程度落ちた値になるか、という観点での性能分析も面白いところです。


4. どこのスループットやレスポンスタイムかを確認する(ローカルアクセスかリモートアクセスかを確認する)
 「ライブラリBはライブラリAよりも処理速度が100倍速い」という状況はよくあります。これを見て、ライブラリAをBに入れ替えたら、アプリケーションのレスポンスタイムやスループットが100倍速くなると考える人はいないでしょう。スループットやレスポンスタイムという観点では、Webブラウザ→Web/APサーバ→DBサーバというアクセス経路全体を見る必要があり、特定のサーバの特定のロジックだけを改善しても、それが全体から見て無視できる範囲であれば大きな効果にはなりません。経験的には、DBサーバがボトルネックになることが多いため、DBサーバだけをベンチマークしてX倍速くなった、と言うことが多いですが、RDBMSと比較して別の製品(キャッシュサーバ等)を評価するような場合は、単体だけではなく、全体的なスループットの面でどれだけ効果があるのかも確認したいところです。
 多くのベンチマークでは、負荷をかけるクライアント、DBサーバともに同一マシン上に同居させた状態で行われています。しかし、現実的な用途ではアプリケーションサーバとDBサーバは別のマシン上に置かれるので、リモートアクセスになります。
 これは、「同一プロセス空間内でアクセスできる製品」のベンチマークに顕著な影響を及ぼします。同一プロセス内でローカルアクセスする場合と、リモートアクセスになる場合とでは、ネットワークのオーバーヘッドだけでなく、オブジェクト・シリアライゼーションのオーバーヘッドも加わるので極端な差(条件次第では100倍を超える差)が生まれます。逆の言い方をすれば、可能ならローカルアクセスで完結させるのは良い設計だと言えます(EHCacheやTimesTenなどをAPサーバ上でローカルで使うというアプローチ)。
 「秒間100万アクセスができました」などと宣伝している製品も見かけますが、リモートアクセスをさせた場合は、GbEのネットワークアクセス性能に引きずられるので、1台あたりではせいぜい10数万かそれ以下のスループットに落ち着くのではないでしょうか。


■ベンチマーク用アプリケーションに関する4つのポイント

5. 処理の内容に気を配る
 ベンチマークプログラムが処理する内容と、実際のアプリケーションで処理する内容が大きくかけ離れているケースがあります。例えばDBサーバではI/Oが主なボトルネックになりますが、そこでI/O性能を測定するベンチマークをしようとしても簡単ではありません。以下のように性能に影響を与える要因が数多く存在し、どれを変えても結果が変わるためです。

・扱うデータ量
・メモリの搭載量
・ディスク本数、ディスクの種類(SSD/HDDなど)
・RAID構成
・ランダムI/OかシーケンシャルI/Oか
・ダイレクトI/Oかbuffered I/Oか
・ライトキャッシュの有無
・I/O単位(1KBか10MBか)
・同時I/Oスレッド本数
・扱うファイル数
・上書きか追記か
・ファイルシステムおよびマウントオプションの違い

 ddコマンドなどによってシーケンシャルなファイル書き込みを行ない、毎秒100MBの転送量が出たとします。いざDB サーバを動かすと毎秒数MB程度しか転送量が出ず、「このデータベースはおかしい」などと言い出す人がたまにいますが、それは行なっている処理が違うためです。
 多くのパフォーマンスの変動要因があるので、簡易なツールを用いてI/O の負荷測定を行なうのは簡単ではありません。やはり、DB サーバに対して直接負荷をかけたほうが確実性が高いでしょう。


6. データサイズに注意する
 バージョンも同じ、テーブル構成も同じ条件でベンチマークをしても、本番環境とかけ離れた結果になることはよくあります。最も大きな要因がデータサイズではないでしょうか。データサイズが実メモリに十分おさまる場合、すべてがメモリアクセスになるため高速になります。MySQL(InnoDB)でも、すべてがインメモリであれば、以下のように主キー検索がローカルアクセスで14万クエリ/秒、リモートアクセスで9万クエリ/秒などといった値をたたき出すことができます(Nehalem 2.8GHz x 16コアでmysqlslapを使用)。
ローカル50接続:
mysqlslap --concurrency=50 --iterations=1 --number-int-cols=2
--number-char-cols=3 --auto-generate-sql --engine=innodb
--auto-generate-sql-add-autoincrement --auto-generate-sql-load-type=key
--auto-generate-sql-write-number=10000 --number-of-queries=1000000
--host=127.0.0.1 --port=3306
Benchmark
Running for engine innodb
Average number of seconds to run all queries: 6.866 seconds
Minimum number of seconds to run all queries: 6.866 seconds
Maximum number of seconds to run all queries: 6.866 seconds
Number of clients running queries: 50
Average number of queries per client: 20000

リモート50接続:
mysqlslap --concurrency=50 --iterations=1 --number-int-cols=2
--number-char-cols=3 --auto-generate-sql --engine=innodb
--auto-generate-sql-add-autoincrement --auto-generate-sql-load-type=key
--auto-generate-sql-write-number=10000 --number-of-queries=1000000
--host=remote --port=3306
Benchmark
Running for engine innodb
Average number of seconds to run all queries: 11.200 seconds
Minimum number of seconds to run all queries: 11.200 seconds
Maximum number of seconds to run all queries: 11.200 seconds
Number of clients running queries: 50
Average number of queries per client: 20000

 しかし、データがメモリにおさまらなくなればディスクアクセスの割合が多くなるため、こんな値を出すことは不可能になります。範囲検索の場合はランダムアクセスの割合も増えるため、ディスクI/Oが多くなりがちで、性能はさらに落ちる傾向にあります。負荷テストをするときに、簡単のためデータ量は1GBで、などとやっていると現実的な結果を得ることは困難です。


7. データのアクセス範囲に注意する
 データサイズを十分に揃えたとしても、それだけでは十分ではありません。アクセス範囲をカバーすることも大切です。例えば、ユーザ数が10万人、同時にアクセスするユーザ数が100人と見積もったとします。ここでよくやってしまうのが、負荷テストに使うユーザ数をあらかじめ決めた100ユーザで固定してしまうことです。これは実際のアクセスパターンとは異なります。実際のアクセスパターンは100ユーザ固定ではなく、さまざまなユーザにまたがることでしょう。
 トータルで10万人のユーザがアクセスしてきた場合、DBのデータはキャッシュに収まりきらず、ディスクアクセスの頻発が予想されます。一方で100ユーザに固定した場合、100ユーザ分のデータしかアクセスされないので、すべてのアクセスがインメモリで行なわれることになるでしょう。つまり、実際に行なわれるであろう処理よりもずっと高速に処理されてしまい、実際よりも良い結果が得られてしまうのです。


8. データの内容に注意する
 多くのWeb アプリケーションでは、傾向の違いはあるとはいえ「頻繁にアクセスしてくるヘビーユーザ」と「ほどほどにアクセスするユーザ」「めったにアクセスしないライトユーザ」がいます。各ユーザによってDB サーバ内でのアクセス傾向は変わってきます。当然ながら、ライトユーザよりもヘビーユーザのほうがアクセス頻度は高くなります。このため、負荷テストを厳密に行なうなら、ユーザIDを均等に割り振っていくのではなく、ヘビーユーザのIDに対して重み付けをすることにも意味があります。
 ヘビーユーザはDBサーバに対する負荷の与え方も変わってきます。例えば、書いた日記の一覧を取り出すために「SELECT 日記ID FROM 日記テーブルWHERE ユーザID=?」というSQL 文を実行したとします。この場合、マッチするレコード数はヘビーユーザほど多くなるでしょう。ここで取得した日記ID を用いて日記の本文を取り出すためには、日記ID の数だけランダムアクセスが必要になります。このため、ヘビーユーザほどランダムアクセスの回数が増える、つまりSQL 文の実行負荷が増えることになります。一方でヘビーユーザに関しては、アクセス回数が多いことからよりキャッシュされやすく、ディスクアクセスの頻度は高くなりにくいというプラス材料も挙げられます。


■データベース設定に関する2つのポイント

9. OS/ミドルウェアのバージョンと設定に注意する
 OSの種類やバージョン、RDBMSのバージョン選定、パラメータ設定なども注意が必要です。これも説明は不要でしょう。
 RDBMSの場合、耐障害性の高い設定にするか、低い設定にするかによっても大きくスループットが変わるので注意が必要です。ベンチマーク結果を見るときは、自分がどういう設定にしようとしているかを踏まえた上で見るようにすべきでしょう。


10. 統計コマンドの使い方と見方を知る
 ボトルネックを特定する上で統計分析ツールは必須です。Linuxならvmstat、iostat、mpstatの読み方は最低限おさえておきたいところです。MySQLではスロークエリログ分析(mysqldumpslow等)、ステータス変数の推移分析(mysqladmin extended-status等)、実行中のクエリ解析(show full processlist等)の使い方と読み方くらいは知っておきたいところです。


 これらのポイントをおさえておくと、DBサーバのベンチマークを見たときに、その妥当性がある程度判断できるようになります。例題として「RDBMSは1レコードのアクセスに数ms程度かかる。一方でTimesTenのようなインメモリデータベースは数マイクロ秒でアクセスできる。だからインメモリデータベースはRDBMSより1000倍速い。これはインメモリデータベースの内部構造がメモリアクセスに最適化されているからだ」という説明があったときに、それをどう解釈するかを考えてみましょう。
 1レコードのアクセスに数msということは、1秒あたり数百回のアクセスしかできないことを意味します。数百回というのはHDD1本レベルの値なので、まったくキャッシュされていない条件での値だと判断できます。一方で数マイクロ秒という値は、リモートアクセス(GbE)では不可能なので、ローカルアクセスをした上での値だと考えられます。つまり、リモートアクセスな上にまったくキャッシュされていない状態のRDBMSと、ローカルアクセスでインメモリなデータベースの性能を比較して、1000倍速いと言っているのだということが分かります。ディスクアクセスとネットワークアクセスが発生する環境とまったく発生しない環境では、1000倍近い差は発生し得ます。決して、データベースの内部構造の違いだけで1000倍の差が発生しているわけではありません。上記のような説明を受けたときに、そのまま信じ込んでしまう人は多いのですが、DBスペシャリストが近くにいれば、説明を受けた瞬間に矛盾に気づくことができるでしょう。
 現実的なアプリケーションでは、それなりの量のメモリを搭載するはずなので、RDBMSはここまで不利な条件にはなりません。そこで、より正しい評価をするためには「メモリにキャッシュされている状態でのRDBMSとの性能比較」であったり、「同一メモリ容量でどれだけ多くのレコードをキャッシュできるか(T-TreeはB+-Treeよりも空間効率に優れる)」といった観点でのベンチマークをするのが妥当と考えられます。リモートアクセス前提であれば、前述のmysqlslapが示すように、InnoDBでも秒間9万クエリ級をさばくことが可能(すべてメモリにキャッシュされている状態での主キー検索の場合)で、これはリモートアクセスした状態のインメモリデータベースと比べても大きく見劣りしないでしょう。
 ベンチマークは悪意を持って行われることはむしろ少数派で、ほとんどの場合は得られた結果をそのまま発信しているだけなので、結果を発信した人を責めるのは良くないと考えています(私自身も間違えることは良くあります)。むしろ積極的な情報交換があった方が良いでしょう。ただ結果の変動要因が非常に多いので、それを読む側の人にもスキルが求められます。ベンチマークは簡単ではないのです。

0 件のコメント:

コメントを投稿