たごもりすメモ

コードとかその他の話とか。

Webアプリケーションのベンチマークをとるときに気をつけている10のこと

10もないかも、と思いながら項目を書き出してみたら10以上余裕であってキリがないので10で収めた。いやあ、あるなあ。

仕事柄よくベンチマークを実行したりしてて色々と思うところが溜まっていたところ、以下のような記事を見掛けたのでなんか書こうと思った。ところでこの記事はベンチマークを実行するための準備作業がループを回して2時間かかるところの待ち時間に書かれている。

sfujiwara.hatenablog.com

ISUCONといえば多少縁があるコンテストで、文中でISUCON5のことについても言及されているので、それも含めて。
自分が業務でいじっているのは "Webアプリケーション" というとちょっと違うんじゃないのというものばかりだが、いやー、最近なんでもHTTPで外部APIを作るからベンチマークのコツとしては大体変わんなかったりするよね。

なおこの記事でベンチマークはどのようなものかとか、どう作るのかとか、そういう基礎的なことについては言及しない。
またベンチマークの対象となるシステムはいわゆるWebアプリケーション、HTTPでリクエストを送りレスポンスを返すシステムで、かつ1リクエストの処理時間は通常1秒未満と期待され、そのようなリクエストを並列でできるだけ大量に処理するシステムであり、計測するのはスループット(req/sec)、並列性、レイテンシ(sec/req)などであるとする。*1

1. 走行中に動的に負荷を変えない

ベンチマークを実行するとき、事前に設定した走行時間(1分なり5分なり10分なり)がある場合、その中で負荷を変えるようなことをしてはいけない。具体的にはベンチマークツールの並列度などを動的に変更するようなことはしてはいけない*2

システムの性能というのは一瞬だけ走らせて確定するものではなく、ある程度の走行時間を必要とする。その中で全体の結果をサマリしてシステムの性能というものは推定されるのであって、一瞬だけのピーク性能は基本的にはほとんど参考にならない。このため、まずある程度の走行時間を確保する必要がある。
その中で動的に負荷を変えるようなことをすると、ベンチマーク走行のタイムラインにおいて、いつどの程度の負荷だったのかということが極めてわかりにくくなり、システムがいつどのような状況でどのような性能を発揮していたのかが確定できなくなってしまう。性能を明らかにするためのベンチマークにおいて、これは実行の意義を自ら捨てるといっても過言ではない行為である。

もちろんシステムのウォームアップ時などを考慮して走行初期に少しずつ負荷を増やしていくといった意図的な負荷の変更は考えられる。6時間の走行において1/3ずつ異なる負荷で走行させる、などのスケジューリングもありうるだろう。こういった負荷の変更は「静的に」「意図的に」行い、また結果からこの期間を容易に除外して考えられるようにすることが重要だ。

負荷の動的な変更は意図せずして発生することもある。例えば測定対象のアプリケーションが500 (Internal Server Error)を返したときなどに、ベンチマークツールがそれを意図しておらず内部的に例外を起こし、該当スレッドが停止することで並列度が下がってしまう、などの状況などはよく起きてしまう。ベンチマークだからといってクライアントコードを適当に書いているとドツボにはまる。

2. 各走行におけるベンチ対象・ベンチツールのパラメータおよびメトリクスを全部保存しておく

ベンチマークにあたっては、後に述べるように様々な要素を変更しつつ数え切れない回数を実行することになる。このため後から総合的に結果を見るときに必要なのが、いつどのようなパラメータでベンチマークを実行し、どのような結果になったのかを全て明確に参照できるようにしておくことだ。これを忘れると、いつ何を実行したかが簡単に分からなくなり、過去のベンチマーク実行のための膨大な時間が闇に消える。

ベンチマークを行うにあたっては管理しておく要素が極めて多数存在する。代表的には以下のようなものがある。

「起動時パラメータ」とまとめたが、ここにはもちろん極めて多数のものが入ってくる。代表的には対象システム・ベンチマークツールそれぞれにおいて並列性(起動プロセス数やスレッド数など)があるだろう。他にも割り当てメモリ容量上限、バッファのメモリ・ディスク、各種キャッシュの使用可否、などなどなどなど。送信するリクエストの内容(GET/POST/PUTメソッド、ペイロードのフォーマット、圧縮の有無……)なども変更の対象になる可能性がある。

最初はこんなパラメータ変更しないと思っていても、様々なベンチマークパラメータを考慮した結果、最終的に変更して試してみる、といったことも非常によく起きる。その後で過去のベンチマーク結果を眺めて「このときこのパラメータの値はなんだっけ?」となってしまうケースも容易に起きる。このため変更の可能性は無いと思っているパラメータについても、細大漏らさず記録しておくことが肝要である。

3. ベンチノードのクライアント・インスタンスのリソース使用状況と並列性に気をつかう

ベンチマークを実行するとき、ベンチマーク対象システムのメトリクス(リソース使用状況)には多くの人が気を遣うだろう。しかしうっかりするとベンチマークツール側のメトリクスには注意を払うのを忘れるといったことが起きてしまう。リクエストを受ける側はまだまだ性能に余裕があるのに、ベンチマークツール側がいっぱいいっぱいになってしまってベンチマークスコアが全く上がらなくなり、しかし実行している人は原因に気付けない、という状況もしばしば発生してしまう。
こういったことが起きないよう、ベンチマークツール側のノードのメトリクスにも常に注意を払おう。基本的には各インスタンスのCPUかネットワーク帯域を使い切る近くになるまで*3、1ノード内における並列度を上げるのが望ましい。その上でベンチマークノードのインスタンス数を、並列度の合計が望ましい値になるまで増やしていく。

ただし、HTTPコネクションの並列度がベンチマークのパラメータになるケースもある。この場合には1インスタンスにおけるリソースの使用状況にかかわらず並列度の調整が最優先になる……など、例外ケースはいくつかある。頑張って考えること。

4. 1セットのベンチマークはパラメータ1軸のみを変更しつつ何度もとる

ベンチマークは、基本的に、あるパラメータひとつのみを変更しながら実行する。そのパラメータに対してどのように性能が変化するかを確認するためには、他のパラメータはすべて固定しておかなければならない*4。性能に影響を与えうる複数のパラメータを同時に変更することは絶対にしてはいけない。ベンチマークの意味がなくなってしまう。

これは当たり前のようにも思えることだが、実際にベンチマークを実行していると意外にやってしまうことが多い。どんなに退屈だろうと、あるいは複数の変更が独立していることが自明に見えても、複数のパラメータの同時変更は鬼門だ。本当に思わぬところで影響が出たりするし、最終的に結果を考察する上でノイズになったりもする。

5. 1セットの結果から仮定を立て、次セットは別のパラメータ1軸を対象に実行する

ベンチマークにより性能計測を行いたいという場合、性能に影響するパラメータがひとつだけということはあまり無いと思う。ある1セットのベンチマークでパラメータAについてベストな設定がわかったら、次はパラメータAをその値に固定した上で別のパラメータBを軸として設定を変更しつつ別のベンチマーク走行セットを実行する。

これによりパラメータBの最適値が分かったとするが、このとき即座に次のパラメータCに行ってはいけない。パラメータBの最適値が当初の値と異なる場合、それがパラメータAの最適値に影響を与えないとは限らないからだ。ということで今度はパラメータBを発見した最適値に固定し、一度パラメータAに戻って再度ベンチマークを実行する。パラメータBの変更がパラメータAに影響を与えていなければ前と同じ最適値が得られるはず。影響を与えていれば異なる最適値が得られる……ので、その値を使って再度パラメータBに戻りベンチマークをまたまた実行する必要がある。

このようにしてA, Bそれぞれについて完了したら、ようやくパラメータCに移ることができる……。

6. 納得するまで繰り返す、走行時間も変えてみる

ということで、様々なパラメータについてあれこれ変更しつつ、納得するまで試行錯誤を繰り返すことになる。長く根気の必要な作業である。

またこのとき、ある程度結果が見えてきたらベンチマークの走行時間についても気にかけてみるとよい。短時間であれば良い性能を示しても、同じ負荷で長時間走行させるとある時点で突然性能が劣化する、ということもよくある。そのような性能特性では当然本番のトラフィックに投入するとトラブルを引き起こすので、事前に見付けておく必要がある。

7. 計測する性能における期待値と最大値は明確に分けて考える

ベンチマークにおいて計測する性能とは、実際にはいくつもの数値によって総合的に構成される。典型的には処理の並列度(同時コネクション数)、処理時間(sec/req)そしてスループット(req/sec)だろう。また並列度やスループットは時間平均*5の推移をとればよいとしても、処理時間については平均値とともに各パーセンタイル値(50, 90, 95, 98, 99 など)を取るべきだ。

さてこういった性能指標をどう扱うか。システムのこれら全ての性能指標について最大値(最良値)を追求する、というのは実際には不可能といってよい。検証すべきパラメータは膨大なので、おそらくいくら時間があっても足りない。こういったときはベンチマーク全体の設計として、いくつかの性能指標は「この程度の性能を期待する」という値を設けておき、それが満たされてさえいるなら最大値は追求しない、という進め方をするほうが良いだろう。
そのシステムの実際のワークロードを考えたとき、例えば並列度はどう考えても10,000を超えない、というようなケースは多いはずである。またスループットは 1,000 req/sec 程度あればよいが処理時間は 100ms/req を切っている必要がある、などの要求の場合、最大スループットを計測することにはほとんど意味がない。ターゲットのスループット*6が問題なく処理できることを確認したら、あとは問題となる処理時間のメトリクスに集中して更なるベンチマークを実行するほうがよい。

8. 走行中の負荷はできるかぎり平準化する

ベンチマークツールの出来によるが、あまり適当にやると時間あたりに送るリクエスト数がひどく上下してしまう、というようなことが起きる。ある瞬間は500req/sec送っていたのが、次の瞬間には300req/secになってしまう、というように。これは測定対象の瞬間性能の上下*7に引きずられてリクエスト送出側の送信数も素直に上下する、という現象と区別がつきにくいが、区別するべきだ。
現実のワークロードはシステムの性能にあわせてリクエスト送信数を上下してくれることなどほとんど無い*8。このため可能なら、ベンチマークツールはベンチマーク対象の性能にかかわらず一定のスループットでリクエストを送信しつづけることができる構造のものを使用する*9べきだ。またその送出リクエスト数(負荷)は不用意に上下しないものがよい。

こういったベンチマークツールを作るのはわりと難しく、世の中にある単純なもの*10はこの機能をもっていないものも多い。
やむをえずこの機能のないベンチマークツールで負荷をかける場合は、測定対象のレスポンスタイム(sec/req)に注目するとよい。対象システムの性能上下によってスループットの上下が発生する場合、かならずレスポンスタイムの悪化*11が裏で起きるため、それをメトリクスで確認できるはずだ。

またこのような問題は、送出するリクエストの組み立てを事前にまとめて行う、といった構造のベンチマークツールを書いたときにも起きやすい。以下のようにして起きる。

リクエストを1,000,000件組み立て(無負荷) → 組み立てたリクエストを順次送出(負荷走行) → またリクエストを1,000,000件組み立て(無負荷) → ……

リクエストを事前に組み立てるのは負荷走行中のリクエスト送出毎のディレイを短くするためには有効だが、組み立てたリクエストを全て送出し切る前に完了する類のベンチマーク走行でしか使うべきではない。実際にベンチマークを実行する場合には数時間といったスパンの長時間走行も考えるべきだから、現実的には全てのベンチマークにおいて、あまり適切ではないといえる。
ベンチマークにおいては、全てのリクエストは送出ごとに組み立てから行うほうが負荷の平準化のためには良い。ベンチマークノード1ノードが送出できるリクエストのスループットは多少下がるが、それはノード数(並列数)の増加で補うべきだろう。

9. 異なる種類のリクエストは公平に混ぜて送る

現実のワークロードに近いベンチマークを行おうとすると、おそらくベンチマークツールから送るリクエストは複数の種類のものを混在させることになるだろう。複数のログインユーザを装うことになるかもしれないし、それぞれ異なるリソースを読み書きするリクエストを並列で発行させたい、ということもあるかもしれない。
このようなリクエストを送るときは、可能な限り綺麗に混在させるよう努力するべきだ。例えば時間によってアクセスされるリソースが偏っている場合は必要な情報がメモリキャッシュに乗りきってしまい、実際よりも高いパフォーマンスを示す、などということが起きうる。そのような状況から得た結果は考察を混乱させてしまい、悪影響が大きい。

実際にどう気をつけるかはベンチマークツールなどの実装に大きく依るが、自作する場合、リクエストタイプ毎にリクエストワーカースレッドを作る、などとするとこの偏りが発生しやすい。レスポンスタイムの短いリクエストを担当するスレッドだけが規定のリクエスト数を走行させきってしまい、あとはレスポンスタイムが長いリクエストだけが流れ続ける時間が生まれる、という状況が生まれやすいからだ。ベンチマークツールを自作する場合は、まず全種類のリクエストを綺麗に混ぜ、それを全ワーカースレッドに公平に割り当てるようにすればよい。

10. 結果は複数のスコアから総合的に解釈する

ベンチマークの結果は複数の性能指標を見て総合的に解釈するべきだ。前述したように典型的なWebシステムであれば処理の並列度(同時コネクション数)、処理時間(sec/req)そしてスループット(req/sec)が考慮の対象になるだろう。たとえスループットを最大化することが目的であっても、そのためにレイテンシの95パーセンタイル値が大幅に悪化する、などということになれば現実的には使えないシステムになってしまう。またベンチマーク走行中に性能値が極端に上下するようなものも好ましくない。

CPUやメモリ、ストレージI/Oなどのメトリクスにも注意を払う必要がある。現実的に運用不可能なリソース消費量になっていないか、長時間走行において不自然なCPU・メモリ使用の増加が起きていないか、などはチェックしておくべきだ。

まとまらない

もうかなり書いたのでこのくらいにしようかと思うが、何か大事な項目を書き忘れているような気もする。細かいことを言うとこれ以上にもあれこれ多数あるだろう。「大事なこれを忘れてるだろ!」というのがあったら教えてください。

EX. ISUCONベンチマークの特殊性

発端がISUCONベンチマークの記事だったので、それについても書いておこう。

ISUCONのベンチマークは、通常業務で実施するベンチマークと較べると極めて特殊なものだ。上述したように通常ベンチマークといえばパラメータを変えながら何度も何セットも実行してようやく性能指標の最大値を求める。ところがISUCONのベンチマークはたった数分、一度きりの走行でスコアという名前の性能値を算出しなければならない。
また対象システムの性能は初期状態からコンテスト末期の状態でおよそ100倍の性能差があることも全く珍しくないが、この両方をひとつのベンチマークシナリオでカバーするのは、普通に考えると不可能に近い。

fujiwaraさんのエントリで記述されているので詳細は省くが、ISUCON 3, 4, 6, 7, 8 ではいくつかの方法*12で負荷が調整されるようになっている。これは実質的にはすべて並列度の操作だ。workloadを手動で指定する方法でも自動調節する方法でも様々な問題がある。特に自動調節はfujiwaraさんのエントリの「自動負荷増加の問題」に書かれている通り、本来(通常)のベンチマークからほど遠い思考を要求されることになる。コンテストだからといえば良いかというと、本来の「できるだけ本物の業務に近いことをやる」という主旨に外れること甚しく、個人的にはあまり好きではない。

ISUCON5のベンチマーク*13は何をやったかというと、並列度は固定してあった。走行中に並列度は上下しない。その上でリクエストに関する許容レスポンスタイムを大幅に引き上げ、非常に遅いリクエストであってもベンチマークツールが頑張って待つ、ということで初期状態でもなんとかスコアが出るように調節したように思う。その上でできるだけ高速に動作するベンチマークツールを作ることで、参加者がアプリケーションの高速化を行ってもそれに追随できるスループットが出ることを目指した……はず。
いま確認したら、それでも予選時には上位チームのスループットには追随できずサチったケースがあったらしい。本戦では並列度が高めに設定してあったから問題は出なかったようだ。

つまり実質的に、ISUCON 1, 2, 5 は固定の並列度に対してスループットを上げる(==レスポンスタイムの短縮を狙う)ゲームであり、それに対して他のISUCONは増加する並列度に対して追随することでスループットを上げるゲームである、と言える。
これが明示されていないことをどう思うかは、うーん、どうなんだろうなあ。まあいいのかなー、という気はする。

*1:同僚が詳しいのですが、たとえばRDBMSベンチマークなどを実行する場合、共通する考え方はあるにせよ細部は大きく異なります

*2:もちろんこれは、だんだん負荷が上昇するようなケースで起きる問題を特定したい、というようなケースでは当然守られない。が、それはおそらくデバッグのためのケース再現であってベンチマークではない。

*3:本当に使いきってしまうとコントロールなどに色々弊害が出るので、いくらか下に上限を設けること

*4:もちろん実質的に複数のパラメータがセットになっているものはあわせて変更する必要があるが

*5:短時間、たとえば1秒や10秒ごとの平均値

*6:の、安全を見てさらにある程度上のスループット

*7:レスポンスタイムの上下といった形で現れることが多い

*8:例外ケースとして、現実のワークロードにおけるリクエスト送信側がキュー&ワーカー構造になっていれば性能の上下に対して敏感にならず、平均のスループットだけを相手にすることができる

*9:あるいは自分でそのように作る

*10:abなど

*11:あるいは処理並列度の低下だが、処理並列度はベンチマークツール側で固定できることが多いので、実際には並列度の上下が動的に起きることは少ない

*12:workload, 自動調節およびSNSシェア

*13:実際にはISUCON 1, 2も根本は同じだが