前回は線を描いて特異点を捉える話を書きました。今回は、その”線”について考えていきます。
比例とは実際にはどのような変化なのか、はたまた例外にはどのようなものがあるのか、地味に楽しい所です。
比例を期待する
負荷試験の測定において、基本的にはX軸を増やすと、Y軸の結果値もそれに比例していくことを期待して取り組みます。X軸はvCPUs、台数、同時接続数など項目は様々です。実際には、ピタリ綺麗に比例するわけではありません。そもそも試験条件を全く同じにして何度も実行しても、ハードウェアやOSは常に稼働していて状態は微妙に変わるので、試験結果にも微妙な違いが必ず出るからです。
ただ、その程度は誤差とし、結果としては比例であると扱いますし、誤差の中でも平均値や中央値を採用することで、最終的な結果にできるだけブレの影響がないよう十分な計測と記録をします。
そのようにして、X軸を倍にすればY軸も倍になることを期待して試験を進めます。比例し続けるということは、どういうことかというと、リソース事情的にX軸の増加を実現できる限りは、Y軸性能もそこまで伸ばし続けられるということです。
現実的にはX軸はハード/ソフトどちらかの事情で有限なので、その限界までY軸が伸びることによって目標性能に到達するならば当面の成果としては成功です。
が、そうは問屋が卸さないのが楽しいところ。
上限は解決する
気持ちよく比例しているところに、突然Y軸の伸びが停止して、グラフが天井を描くわかりやすい現象があります。この原因は大きく分けて3つあります。
ハードウェア制限
CPUやストレージ性能など、処理量が多くなりすぎてその限界値に達すると、直にエラーが発生するというよりは待機時間が発生し、性能が伸びなくなります。よくあるのはアプリケーション・サーバーのCPU、DBサーバーのCPUやストレージIOPSです。これらは基本的なメトリクスを採取してあれば、グラフを見ればすぐに気づくものです。
アプリケーションのチューニングなど、軽量化する方法がなければスケールアップやスケールアウトを検討します。
もしくは、その負荷試験条件における限界性能である、ということを認識できます。あえて少ないリソースでそれを知ることで、目標に対してどのくらいのリソース量を用意すれば達成できるかを推測するのに役立ちます。
ソフトウェア制限
ミドルウェアの設定値や、OSの設定値によるものです。例えば最大接続数の上限に到達すると、同時接続数の増加が停止して計測値が伸びなくなりますし、もっと大きな接続数になると、OSの最大プロセスIDやスレッド数の設定に引っかかって伸びなくなることもあります。
この原因を見つけて調整して解決しても、いつかは次の原因にブチ当たるわけですが、ソフトによる上限がある状態よりは、その枷を外してハードによる上限にした方が、リソースを限界まで使えるという意味では効率的かもしれません。し、モノによっては大事故につながらないよう安全管理という意味で、ソフト制限が適している場合もあるでしょう。
どちらにせよ、設定の存在を認識して調整できるようになることが大事です。
クラウド制限
クラウドでは安全管理や従量課金のために、様々なリソースの消費量が記録されています。慣れないユーザーが間違って大容量のリソースを使って大課金になってしまったり、外部からの攻撃で大きな使用量になってしまうことを防ぐ目的で上限値があります。また、インスタンスタイプの大きさごとに各種制限値が変動したりもします。
固定値や、利用条件ごとの変動値での上限が、ハード/ソフトどちらにも制限があり、OS内での出来事からはその存在に気づくことが難しい場合が多々あります。
そういう時は、関連リソースのドキュメントや管理画面で上限値について学ぶ──か、サポート問い合わせになり、この辺がクラウドの理解力が問われる大きな要因の1つだったりもします。
オーバーヘッド
CPU数や台数を増やすことで比例強化を期待するわけですが、それが上手くいくアーキテクチャとそうでないモノがあります。リソースを増やすということは、それに対してより多くのトラフィックを流し込むということです。それはつまり、同時接続処理や、並列処理が増えるということになります。
1台の中での並列処理はコスト無料というわけではなく、処理の切り替えコストや、僅かな待機時間が発生している場合があります。また、CPUスレッドや複数台ホストにおいて、それらが共通して利用するストレージやサーバーがあれば、少しずつ待機時間が増える可能性もあります。
ロードバランサ → WEBサーバー のようなシンプルな分散転送程度ならば、WEBサーバー増加がほぼそのまま性能強化になりえますが、AppやDBサーバーといった複雑な処理で、その並列度が増えるような箇所では、リソースに対して比例よりは数%から1%未満の性能劣化があると想定しておくとよいでしょう。
そしてそれを観測した時に、許容できるかどうか、比例と言えるかどうか、を考察して判断していくことになります。
ベースとメインのリソース
この、リソース増加に対してちょっと損をするかもしれない、という話の逆の話もしておきます。サーバーでミドルウェアを動かす時、OSやミドルウェアを動かすために必要とする最低限のリソースというものがあります。それを仮にベースリソースとします。
メインとなる処理は、全体からベースを引いた残リソースを可能な限り活用して稼働させることになりますが、サーバーリソースを増やしてもベースリソースが変わらないとするならば、メインリソースの割合は増加することになります。
サーバー | ベース | メイン | メイン割合 |
100 | 10 | 90 | 90.0% |
200 | 10 | 190 | 95.0% |
400 | 10 | 390 | 97.5% |
サーバーリソースを増やすほど、ベースリソースの影響が減少し、よりメイン処理を活性化できる側面もあるということですが、その他の要素も含めて性能がどう変化するかはモノ次第です。
とはいえ、あまり小さいサーバーリソースだとメインに活用しきれないとも言えるので、一定以上大きなサーバーリソースを選択する、というのはコスパ的に十分アリな判断材料の1つです。
経年劣化
負荷試験において経年劣化を炙り出すことはあまりありませんが、『その先』を見据えておいて損はありません。時間が経過することで変化のあるパーツといえば『データ容量』です。と、言ってもディスクフルのような障害に属するものではなく、あくまで性能劣化に関する話です。データの蓄積と言えばデータベースなので、2つほどMySQLでの劣化例を出してみます。
1つはバッファプール・メモリに関するものです。
- MySQL 8.0 リファレンスマニュアル :: 15.5.1 バッファプール
- MySQL 5.1.41リリース – SH2の日記(※私が初めてこれを学んだ所)
データ容量<メモリ なら、常にデータがメモリに載ることが保証されるので、速い処理を期待することができます。しかし、データ容量>メモリになった時点以降は、読み書きが頻繁に行われるデータの割合と量によって、ストレージからの読み込みが発生し、その分の iowait で全体のレスポンス劣化に繋がっていきます。
最近はストレージ性能が高いため、その影響度が少なくなっていますが、メモリに対して頻繁利用されるデータ割合が大きくなるほど、徐々に悪化していくことに変わりはありません。
もう1つはデータ割合の変化によるものです。
試験時のテストデータはたいてい、数値やBooleanのバランスが均一だったり偏っていたりします。例えばカテゴリIDが 1~20 まであるのに、1 のデータしかなかったり、1~20まで平均的なデータ数だったり、テストユーザーの所有データ数が均一だったり、などです。
それに対し本番のデータは、ヘビーユーザーが飛び抜けたデータ量になったり、選択されやすいIDのデータ割合が大きくなったりと変化していきます。
出だしの平均的なデータでの試験で問題なかったインデックスの調整も、数ヶ月経過するとデータ割合が変化し、インデックスの効果が小さくなったり、一部の偏ったデータ条件でのみ効かなくなることがあります。それらが平均レスポンスタイムに悪影響したり、スロークエリとして登場してきます。
実際の運用ではアプリも機能追加など変化するので、悪化の原因は総合的にチェックしていきますし、負荷試験としてもこの未来の状況再現を狙って行うのは難易度が高く、またその正確性と手間を考えると非常にコスパの悪い計画とも言えます。
なので実践的には、やはり平均的なデータや、せいぜいピンポイント特化したデータで試験し、それでできる限りの調整を行った上で本番へ臨みます。『本番に勝る試験環境はない』という前提を踏まえつつ、本番では悪化をできるだけ早く捉え対処する、監視体制を整えることが負荷試験よりも重要になってきます。
負荷試験はタイミング的に時間が限られるし、時間をかけたとしても本番相当の問題を全て炙り出すのはほぼ不可能です。ゆえに、大半を負荷試験で炙り出し、残りは本番運用中に早期の対応をしていく、というバランス感と、ある意味では開き直りに似た区切りをつけることは必要になってきます。
比例しない事例
ここからは3つほど、少し変わった比例しない例を出してお終いにします。こういうのにブチ当たるのが楽しいものです。アプリケーション・サーバーのインスタンスサイズ
基本的にはアプリケーション・サーバーは、インスタンスサイズを倍にすると、並列数増加による劣化を考慮しても、捌けるRPSもほぼ倍近くになります。そして、負荷分散するための複数台サーバーは、少なすぎるのはリスクが上がりますが、多すぎるのも管理が面倒だったり監視費用が上がるので、それなりに少ないほうが良い場合があります。
という2点を踏まえた時、8 や 16 vCPUs じゃなくて 96 vCPUs で台数を減らしてしまえと思うかもしれません。
もしかしたら上手くいくかもしれませんが、普通にログ書き込みなどストレージを使っている場合、並列処理数が多くなりすぎるとたかがログでも iowait が上昇し、全体の性能としては十分に発揮できない場合もあります。
AWSだとストレージ容量を上げれば性能も上がりますが、それがベターな解決案かは考えどころ。比例に頼ってやりすぎも良くないよって話でした。
AuroraのCPU
データベースも概ね処理するRPSに対して比例したCPU使用率になるのですが、Aurora はそうではなく、常時高めのCPU使用率と、終盤の粘り強さを公表しています。その理由は、並列性能を高めるため──とからしいのですが、そのせい?で、Auroraは負荷試験において比例による先の推測をしづらい特徴となっています。例えばこのようなCPU変化を観測できます。
負荷試験において、まず小さい量での結果を得て、では比例すると最大ではこのくらい捌ける、と計算するわけですが、Auroraで序盤の数値でそれをやると、全然見当違いの結果になってしまいます。
CPU 10% で 100 RPS だったから、CPU 50% で 500 RPS、CPU 100% で 1000 RPS だろう、とはなりません。これは、まず何もしていなくても数3~8%程度のCPU使用率があること、中盤からはRPS増加に対してCPU使用率は比例より少し低い値になること、終盤は謎の粘りを見せること。が要因となっています。
だんだんCPU使用率の伸びが少なくなっていくので、誤った試算だとしても死にづらい方向性なので悪い方向ではないのですが、ちゃんとAuroraに注視した中盤~終盤の性能を計測しないと、無駄に大きなインスタンスタイプを用意する可能性があるので、取り扱い要注意です。
WriteDBの分割
Writeって書いてますがMaster|Primaryと同義で書いてます(めんどくさい)。ストレージがまだ弱かった時代、特にDBの書き込み分散には苦労していました。その時の選択肢の1つとして、正攻法である(分散)XAトランザクションを採用したシステムを検討したことがあります。
複数台に書き込みデータを分割しつつ、クエリとしてはほぼそのまま扱えるという理想形に見えるのですが、台数の増加がそのまま性能の増加に繋がることは、当時はありませんでした。
XAトランザクションは、通常のそれとは異なり、全サーバーにトランザクションを発行するので、台数が増えるほど全台分のトランザクション完了待機時間が増加し、数台ならまだしも十数台以上では使い物になりませんでした。
全台に並列で同時に発行するならよかったのでしょうが、当時のそれは1台ずつ順に発行していたので、20台なら20トランザクションの完了待ちになり、台数増加に対する性能劣化が激しく、断念しました。
とはいえ、その経験は非常に勉強になったと思っており、通信内容や原因をしっかり追う、という大切さが身にしみた検証であったと記憶しています。
こういった経験を積んでいくと、新しいシステムでもアーキテクチャを理解すれば、どのように拡張するべきで、どのような性能変化が起きるだろうか、を予測できるようになってきます。
予測しつつも負荷試験でその確証をとる── または想定外なら調整でなんとかするのか、別のアーキテクチャを考えるのか。
負荷試験ツールはシステムの本性を暴く武器の1つに過ぎず、その先の考察が醍醐味であり腕の見せ所です。それゆえに、負荷試験の仕組みがそれを妨げないよう、取り扱いやすく柔軟なものに仕上げていくことが求められるのではないでしょうか:-)