失敗から学ぶパフォーマンステスト

# 背景
システムの処理速度やリソース効率化を確認するパフォーマンステストですが、大きめ新機能開発や、システムのリプレース時など、やる機会が限られていることが多いと思います。

経験する機会が少なく、実際にやってみると結構ハマることが多いので、自分が経験した失敗事例からの学びを共有します。

# 書かないこと
パフォーマンステストの一般的なやり方や、手法については書きません。
また、用語の説明もありませんので、パフォーマンステスト自体を知識として知っている方を前提としています。
パフォーマンステストのやり方とかは、他の記事を参考にした上で、読んでいただけたらと思います。

# 失敗事例： 本番想定の負荷をかけられてなかった
パフォーマンステストしてけど、ちゃんとできてなかったで、一番辛いケースです。
運用開始後に、システムが動かないぞ！全然応答しないぞ！で、ユーザーからめちゃくちゃクレームがきます。
テストでの再現ができていなければ、本番環境だけで発生する状況になるので、原因調査も難しくなります。

### 原因： パフォーマンステストが単純すぎた
想定ユーザー数や想定シナリオをベースにパフォーマンステストを作成した場合、この画面はこんな操作でこのぐらいの処理量で、作成することになりますが、実際の本番ではユーザー数分、別々の操作がされるため、それだと再現が不十分だったというケースです。
テストシナリオが単純な場合、WebサーバやDB等を含め、どこかでキャッシュされてしまい、それによってテスト時は良いパフォーマンスが出やすいです。本番ではいろんな操作がされるため、キャッシュヒット率が下がり、パフォーマンスが出ないという状況が発生します。

### 対策： 本番の実行ログを使い、テストする ※現行システムがある場合
現行システムがある場合、そのシステムの実際の実行ログとかをとっておき、同じような処理を新システムでテストできるとベストだと思います。
また、カナリアリリースなどをして徐々に新システムに切り替えられるとより安心です。

### 対策： スケールアウト・アップできるようにしておく ※現行システムがない場合
現行システムがない場合は、スケールアウト・アップをできる準備をしておくと良いかと思います。
また、高負荷が予想される場合は、スケールアウトがしやすいアーキテクチャをあらかじめ設計しておく必要があります。例えば、ACID特性を担保していないNoSQLの利用や、RDBを利用する場合は、読み込みと書き込みのテーブルを分けるなどが考えられます。
特にRDBはACID特性を担保するため、スケールアウトにどうしても制約があるので、注意が必要です。
（複数サーバでデータを同期するためにロックする必要があり、スケールアウトするとそこの処理負担がどうしても増えてしまう）


# 失敗事例： テスト環境で想定負荷がかけられない
これはパフォーマンステストを実行するぞ！となった時に発生する問題です。
100rpsを超えたり、大きなファイルのアップロード/ダウンロードがあるようシステムの場合、負荷テストツール/端末の問題で想定の負荷がかけられない、ということがあります。
負荷テストはテスト終盤でやることが多いので、「ようやく負荷テストできた！やるぞ！」「あれ？パフォーマンスが出ない？」「なぜ？システムは問題ないぞ・・・？」「負荷テストツール/端末の問題だった・・・どうしよう・・・」で結構泣きそうになります。
（早めにやっておくのが理想だけど、そうできる人は多分パフォーマンステストの経験が豊富な人たち）

### 原因： 1台のコンピュータで負荷テストする限界
普段の開発、テストだと、開発用のPCや端末からやるので、負荷テストも同じ感じでやろう！でこの失敗にあたることが多いと思います。
100rpsを超えたり、大きなファイルのアップロード/ダウンロードを並列実行しようとすると1台の端末ではハードウェアやネットワークの都合で想定負荷がかけられないことがあります。

### 対策： 事前に負荷テストツールのテストをする
想定の負荷をかけられるか、事前に簡単なテストシナリオを作って、使う予定の負荷テストツールをテストしておくとよいです。
実際の負荷テストシナリオの設計や構築作業は結構大変ですが、1画面で特定のリクエストするとか、単純なものならすぐできるので、それで想定負荷をかけられるか確認できます。
想定負荷に満たない場合、複数台で分散して負荷をかけるような環境や設定を作れる負荷テストツールが多いので、事前にその設定や環境を準備することで対応できます。


# 失敗事例： 想定負荷が高すぎる
期待の新機能とかを開発するとき、期待のアクセスが来た時大丈夫かな・・・で心配になってしまい、テスト設計時の想定負荷が高くなりすぎて、それをクリアできない、どうしよう・・・みたいなケースです。

### 原因: アーキテクチャ or テスト設計の問題
スケールアウト可能なアークテクチャになっていない、もしくは、テスト設計が非現実的になってしまっている可能性があります。

### 対策: 現状どこまで捌けるか把握する ※アークテクチャの問題の場合
スケールアウト可能なアークテクチャになっていないについては、構築してからだともう手遅れかもしません。。
その場合、どのくらいなら処理を捌けて、どのくらいで捌けなくなってくるを確認し、捌けなくなった場合はどうするか、をあらかじめ検討しておく、とかぐらいかな・・・と思います。

### 対策: 現実的なテスト設計をする ※テスト設計の問題の場合
実際の現行システムの類似機能の実行ログや利用ケースを見ると、ここまで高負荷にはならないよね、というのが見つかることがあります。
一度パフォーマンステストで失敗を経験すると、ビビってテスト設計するようになりますが、腹括って現実的なテスト設計をするというのが大事です。また、どういう前提でどう設計したかをドキュメントに残して、周りの人にチェックしてもらうとよいです。それってやりすぎじゃない？とそこまではないと思うよとか周りからアドバイスもらうことができます。

# 失敗事例： 本番環境が限界を迎えたけど、スケールアウトできない
これは、本番リリースしてしばらく経ったあとに発生します。
ユーザーや利用数が徐々に増えてきて、初期に想定した以上の負荷がかかるようになり、システムの応答速度が下がったり、応答できない場合が出てきて、とりあえずスケールアップで凌いだけど、マシンパワーにも限界があって、これ以上スケールアップできないな、で、スケールアウトしたけど、処理量が増えない・・・どうしよう。みたいな状況です。

# 原因： ボトルネック部分が分散できない仕組みになっている
例えばRDBを使っている場合、複数台のDBサーバを用意したとしても、書き込みが発生するシステムの場合は、それをそれぞれのDBサーバに同期する必要があるため、どうしてもスケールアウトに制約が発生してしまう、という場合があります。
もしくは、一つのNASにファイルを出力して、それを他の処理でも共有利用していて、そこのIOがボトルネックになってしまった場合、ファイルは物理的に一つのためスケールアウトしたくてもできない、ということが発生します。
（こちらは最近だとあまりないかも・・・？）

# 対策： スケールアウトで処理量増えること、また、スケールアウトの限界値をテストしておく
サーバー台数を最小構成でパフォーマンステストを実施して、そこから台数を増やした場合にスケールアウトしていくか徐々に負荷を増やしながら、想定される限界値までテストしておくと、どこが限界点なのかをあらかじめ知っておくことができます。
ありえない限界点までテストする必要はないかもですが、少なくとも最小構成からスケールアウトして、処理量が比例して増えることは確認したほうがよいと思います。

# 失敗事例： スパイクに対応できない
急激なアクセス増加が発生して、そのタイミングでシステムが処理しきれず、全体が応答不可になってしまう、というようなケースです。

## 原因： システムは急激にスケールアウト/アップすることはできない
自動的にスケールアウト/アップしていくような仕組みはありますが、急激なアクセス増には対応が難しいです。徐々にアクセスが増えていくみたい場合だと、少しずつとサーバー台数を増やしていくみたいな動作は可能なだと思いますが、例えばアクセスが急増した1秒後に、そのアクセスを全部対応処理できるだけのサーバー立ち上げるみたいな仕組みを構築することは技術的にもコスト的にも難しいかと思います。

## 対策: 事前にスケジュールしておく・余力をもっておく
この場合は、事前に把握しておき、そのタイミングや時間帯にスケールアウト/アップしておくぐらいしか思いつかないです・・・
もしくは、スパイクが不定期かつ頻繁に発生する場合は、余力がある構成してスパイクしても処理可能なようにしておくとかですかね。

即時応答が不要なシステムの場合は、MQを使ったりすると平準化して対応できるかもです。
あとは、スパイクが想定されるシステムの場合、発生時に全機能が停止したり、他システムに影響を与えないために、サーキットブレイカーの仕組みを検討する必要があります。

# 失敗事例： 高負荷時にDBの応答しなくなってしまう
RDBを使っている場合、大量のデータを読み取る処理と、そのデータを更新する処理がバッティングすると、応答しなくなってしまうケースがあります・・・
DBが止まってしまうと、システム全体も止まってしまうことがほとんだと思います。また、実際に本番で発生すると原因調査や対応に時間がかかることがあるため、結構大変なことになります。

## 原因： テーブルロック、共有ロックの発生
RDBのデフォルトの設定では、分離レベルがREAD COMMITTEDというやつを採用していることが多いです。
RDBによっては、この分離レベルの場合、読み取り処理中は共有ロックがかかり、データ更新ができない状態になってしまいます。
さらにSQLServerなど一部製品はロックエスカレーションという仕組みがあり、データ読み込み量が多い場合、テーブル自体をロックしてしまいます。

## 対策： 処理負荷が高いクエリはロックしない or 最小限になるようにする
そもそも処理負荷が高いクエリを実行しない、が一番ですが、どうしても設計や仕様上をやむを得ない場合もあります。
その場合、そのクエリの実行時だけ分離レベルを下げたり、ロックしない設定で実行する方法があります。ただし、その場合、ダーティリードが発生するため、それは許容する必要があります。

それが許容できないケースでは、ロックが最小限になるように、そのクエリの実行計画を確認して、インデックスが利用され、テーブルスキャンが発生していないことを確認しておくと良いかと多います。
（SQLServerではテーブルスキャンするとその間、ロックしてしまう挙動があります。）

ちなみにOracleはそのへんが優秀で、READ COMMITTEDでもロックせずに動作するため、上記のようなロックの問題が発生しにくいです。また、結構無茶苦茶なクエリでもそれなりに早く応答できたりします。
（Oracleは個人的に好きじゃないですが、DB自体は本当にすごいと思います）


# 最後に
最後まで読んでくださってありがとうございます！
原因や対策の部分は、自分の実際の経験によるものですが、別の原因で発生したり、別の良い対策もあると思います。
なにがご意見やアドバイスがあれば、コメントいただけますと幸いです。