負荷試験#検証と最終診断

負荷試験#採取と整理 の続きを1年越しに再開です。

今回は負荷試験の結果を見て、何をどう判断して進めていけば良いのか、どのような判断事例があるのかという話です。



正常系と異常系の結果

負荷試験の大きな目的の1つである【スループットはどの程度を出せるのか】を判断するには、あえて異常な状態までトラフィックを流し込む必要もあります。

スループットに対するグラフの特徴としては、キレイな正常系と途中から不安定になる異常系があるとして、Locustでグラフの例を示してみます。

正常系

この例はユーザーを 30万 まで 500spawn/s で徐々に増やし、12000 RPS まで流し込んだグラフです。

エラーはゼロで、レスポンス速度もユーザー生成完了後は 50%tile で 70ms弱 と、良好な結果となっています。

嬉しい結果ではありますが、負荷試験の診断という意味ではこれだけでは不足です。正常な結果だけでは、その先のトラフィック量が来た時に、そのリソース量で捌ける限界値が不明なままだからです。

正常な結果を必要とするポイントは2つあります。
1つは最初の負荷試験処理としての正常な動作を確認する時です。軽量なリソース量とトラフィックで、クライアントにもサーバーにもキャパシティオーバーが起きていなく、エラーや遅延の類が出ないことを最初にキッチリ確認しないと、アプリケーションとして不安定なままその先を続けても良好な結果を得られないからです。

もう1つは最終結果です。用意したリソース量で、目標とするスループットがエラーや遅延なく捌けることを証明するデータとします。目標値は捌けましたよ、でもさらにトラフィックがこのくらい増えると次のボトルネックに当たりますよ、という結果にすることで、それで十分なのか、より余裕をもたせるのか、という協議をできるようにします。

異常系

次に正常に終了しない例を出してみます。

これは 15万ユーザー・17000 RPS あたりから ①レスポンス悪化 が出始め、25万ユーザーを超えたあたりから ②エラーが出現 し始めた結果になります。

正常ならば、キレイに比例するか、横一直線になるはずのところが、グワッと急上昇したり、グワングワンに乱れる波形になった場合、それが次のボトルネックを示すサインです。

もし、既に目標スループットを達成している場合は、それを発見することでその先の限界値を見極めることができます。目標値 12000 RPS で正常を確認していたならば、
(17000 / 12000) = 1.41倍 を超えると不健康になるということを認識できます。

まだ目標値に達していない場合、そのボトルネックを取り除いてあげる必要があります。リソース量の問題なのか、設定値の問題なのか、原因を特定して解消し、目標値を達成できるように試行と改善を繰り返していきます。


慢性的な不健康

システムの性能には必ず上限が存在するので、その炙り出しを目的とする実施が基本ですが、そうではなく全体的に性能がイマイチであることもままあります。最初から最後までエラーは出ないし、グラフも綺麗っちゃ綺麗なんだけど、全体の平均レスポンスタイムが遅い、特定の処理が遅い、といった症状です。

システムによってレスポンスタイムの許容範囲はまちまちですが、通常のHTTP(S)の場合、1秒以内ならばユーザーは快適で不満を感じなく、3秒以上かかるとユーザー離れを引き起こすと言われています。

そのため、一部の処理に平均1秒以上かかっている場合は、その処理を深掘りして改善する必要がありますし、全体の平均に1秒以上かかっている場合は慢性的に品質が悪いと判断して、根本的に改善する必要が出てきます。

一部が遅いならば、たいていはわかりやすいボトルネックがあるものですが、全体となるのは結構よっぽどのことなので、各システムパーツやインフラ構成から見直さないと劇的な改善は見込めないかもしれません。


原因の特定と改善

ここからは、採取した異常系グラフから、どのように原因を特定していくかを考えていきます。

グラフ形状に異常が発生したタイムスタンプを基準に、同タイミング近くで通常とは異なる動きを見せた数値を発見することが基本になります。また、見るべき数値は色々あるため、そもそもその数値群をグラフで見る場所を知っておく必要がありますし、もしグラフ化されていない重要項目があればグラフ化しておく必要があります。

ということは、それを知識として持たなければ原因を特定できない可能性があるということで、どのような項目が重要なのかを知識として蓄えたり、知るたびに自動的にグラフ化されるように仕組みを改善したり、という積み重ねが重要になってきます。

負荷試験はただ負荷をかければ良いというものではなく、システム全体からミドルウェア等の細かい仕様まで理解している、または都度深掘りしてフィードバックできる技術力が必要であり、わりとエンジニアとしての総決算的な色合いがある上級タスクと言えます。

エラーログ

兎にも角にも、まずは目をつけたタイムスタンプあたりにエラーログが残っていないかを確認します。

どこにログがあるかはシステム次第ですが、トラフィックの通り道を上から考えた時、(LoadBalancer | Proxy) → WEB → Application → (DB | KVS) などが考えられ、それぞれの箇所での保存場所を把握しておき、実際に見に行くことになります。

致命的な内容ならば、原因となる箇所の1つ上に残っていることが多いです。DBが原因なら Application に、Application が原因なら WEB に、といった具合です。これはクライアント側としてサーバーを利用すらできずに終えてレスポンスを返すことになるからですね。

サーバー側として上限値などに引っかかった場合は、サーバーの方にログが残り、その原因はたいていわかりやすい内容でログに記録されるので、検索して解決方法を探ることになります。

エラーによっては、そもそもエラーログとして出現してくれるのか、その内容がそのものを指してくれているのか、漠然としたエラーなのか。徐々に性能として悪化するタイプで証拠となるログは存在しないのか、といった様々な判断を迫られることになります。

初手としては必ずエラーログの確認から入るので、ログを検索や抜き出しコマンドを駆使して手早くチェックできるようにしておくことも、試験を円滑に進めるための重要な要素です。

ハードウェア・キャパシティ

グラフとしての初手は、ITインフラとして基本となる、各ハードウェアのメトリクスを診て、キャパシティに到達していないかを確認していきます。

過去記事『ミドルウェア性能検証の手引き | 外道父の匠』があるので、ここでは割愛していきます。

CPUひとつとっても、全体100% として診るのか、16vCPUs = 1600% として診るのか、はたまた詳細に %user %system %iowait %steal なのかで判断できる内容は異なってきます。コンピューターがどのようなパーツで構成されていて、それぞれどういう状態になったら困るのか、というのを理解せずして正しい負荷試験はできません。

ミドルウェア・ステータス

クラウドの提供するメトリクスや、監視サービス・ソフトウェア等が提供する基本データには様々なモノがありますが、それらで全ての異常変化を捉えられるかというと、そうではありません。

わかりやすく MySQL を例に上げると、稼働ステータス値(SHOW STATUS)のうち例えば、突然 tmp tables が増えたとか、Read I/O が増えた、といった捉えづらい要因が様々あります。メモリ容量やデータ量といった状態が流動的な部分を起因として、性能が悪化するような現象が多いです。

データベースは特にそういう不測な判断の塊ですが、Proxy, Web, App, KVS など他のミドルウェアも少なからずそういう類の内部ステータスを管理提供しているので、まずは見方を把握しておくこと。そして、それをメトリクス・グラフとして残す手段を用意し、少しずつ重要項目を残すようにしていくことが大切です。

ステータスは基本、その瞬間限りの数値なので、意図的にグラフとして残さないと、問題点となるタイムスタンプに何かが起きていないかを後からチェックすることができなくなるため、何かしらの自動採取する仕組み作りをすることになり、負荷試験以前の課題となります。

ソフトウェア・リミット

OSやミドルウェアには、メモリリークやメモリの過剰使用を阻止するような、システムの暴走によるシステム障害を止めるための仕組みが多々あります。

OSでよく見る上限といえば『systemd時代に困らないためのlimits設定 | 外道父の匠』ですし、

MySQLで言えば、パラメータ設定値(SHOW VARIABLES)の最大接続数(max_connections)、.irb を開く最大数(innodb_open_files)あたりが引っかかりやすいところでしょうか。どの部位でも接続数はかなり重要な要素として扱われますし、モノによっては並列処理数・リソース量・データ量などなんでも上限の管理があり得ます。

これらの上限値は、あくまで安全に稼働させるための仮設定値であることが多く、システムリソースを正しく理解して調整するならば、より高い上限で稼働させることが可能であることが多いです。

グラフからだけでは、どのような項目が原因であるかはわからないため、設定値の知識を蓄えておくことが望ましいです。それでもいくらでも初体験の項目に引っかかったりするので、処理を上から丁寧に追って、どの部位が原因なのかを特定し、その部位の怪しい設定項目と値を探し当てる嗅覚が試されるところです。

原因不明のエラー

ここまでチェックをしたとしても、それらを原因と断定できない状態なのに、まだエラーになるということもあります。ログや数値で目に見ることができない部分を探り当てるというのは、知識も経験も必要で非常に困難です。

1つの例として、アプリケーション・サーバーの処理限界というものがあります。

CPUやNetworkなどに余裕があっても、Workerプロセス(またはスレッド)数に対して、スループットと平均レスポンスタイムが一定以上になることで、空きWorkerが無くなり処理を受け入れられなくなる、という現象です。

これらの項目の値がどの程度になればスループットの限界に到達するかは、簡単に計算することは可能ですが、エラーとしては status:5xx が返るだけで、なぜスループットが上がらないかの原因にたどり着くことが難しいところです。


もう1つ例を Autoscaling Group で出します。CPU Average としては 80% 程度でまだ若干の余裕があるのに異常が起きるとします。この時、実は CPU Maximum としては 95% くらいを記録していたとしたら、そこが怪しいです。

AZ格差によってCPU使用率の差が発生すること、そして1分平均の記録値が 95% ならば、1秒単位で見れば 100% に到達している瞬間があるかもしれない。そして、負荷試験ツールによる複数ユーザーでのリクエスト群は、完全に平均的なRPSになるのではなく、若干の偏りが発生しているかもしれない。

といったことから、記録では見えないけど CPU キャパシティオーバーが発生して遅延するサーバーが一部にあるかもしれないので、もう少し安全圏と言える平均CPU使用率を低く見積もろう、とか、ツールからのリクエストをもっと平均化するよう改善しよう、となります。


ここら辺まで潜ることができれば、負荷試験としてはやり切りかけているというか、負荷試験に自身が試されているというか、楽しさの最終局面的な状況になっていると思います。


試行サイクル

最後に簡単に負荷試験の実施手順をまとめておきます。

正常性の確認 軽量なリソース量とトラフィックで試験を実行した時に、クライアントにもサーバーにも、エラーや遅延が起きないことを確認します
ボトルネック洗い出し トラフィックを徐々に大きくし、グラフの異常変化を出現させ、その原因を特定します
改善 ボトルネックの原因を解決し、リソース量の変更やデプロイを行います
再試行 同条件で試行し、同タイミングでのグラフの異常が解消されることを確認します
再調整 目標値または用意できる最大リソースに向かって徐々にトラフィックを上げていき、ボトルネック洗い出しから繰り返すことで、サーバーリソース量に対する性能値を算出します
目標達成 目標とするユーザー数やトラフィックがあれば、その流量とサーバー条件で最初から最後まで異常が起きないことを確認します。できればさらに、現実的に用意できるサーバーリソースに対して、最初のボトルネックとその時のトラフィックを記録します


結果報告

あとは結果を何かしらのフォーマットでまとめるだけですが、読み手が理解できる必要がありますし、あまり過剰な情報を載せすぎないことにも気をつけていきます。

負荷試験は収集するデータの意味や、それぞれの値の判断理由などは、エンジニアでも難しいことがあるので、特に非エンジニアが関わる場合はまとめの粒度をあまり細かくしすぎないよう、わかりやすさに努めることになります。

ログやデータを過剰に載せる方が一見、懇切丁寧な報告書になると思いがちですが、途中経過をいくら載せても閲覧者がそれらを精査することはそう多くありません。

最終判断の9割方は、負荷試験者が自信を持って行うべきで、基本的にはその最終判断に使用した要所のデータを記載すれば十分です。それでもし、より詳細な説明を求められた場合は、途中や細部のデータとともに理由を再提示すればよく、よほど最初からフォーマットが決められていない限りは、1発でなんでもかんでも見せて説明しようとするよりは、簡潔さを重視する方向性から始めるとよいでしょう。

そのためにも、大事なのは1つ1つの試験結果を丁寧に整理し、いつでも再確認できるようにしておくことです。場合によっては再試験することもありますが、基本的には再試験をすることなく、必要に応じて全ての結果を検めることをできるようにしておくことが、負荷試験を円滑に支えてくれます。



さて、1年ぶりに再開してみたものの、実はこれでいったんの落着まで書ききれた気がします。

どこまで検証してどういう判断をするか、というこの最終局面が最も重要ではあるのですが、一連の流れを考えると、準備と試験実行そのものにかかる手間と時間が、最も尽力して創意工夫すべきポイントであると体感しています。可能な限りを自動化・ワンコマンド化をして、ストレスと時間の削減をすることで、試験日程に余裕ができるため、結果の精査にも時間をかけることができるようになるからです。

その上で、必要ならアプリケーションのリファクタリングを指示したり、時には自分で改修したりもするので、負荷試験=トラフィックを流しこんで、ただ無事を確認するだけの簡単なお仕事、ではないですよということは強調しておきたいです。

ただ、一度仕組み作りと作業工程を仕上げてしまえば、その後はメチャクチャ役に立つ負荷試験システムとして活躍しまくれるので、自分にも周りにも楽にわかりやすく、を心がけて取り掛かると、皆が幸せになれる良いタスクになると思われます:-)