はじめに
私が所属するチームでは、主にMicrosoftの.NET環境で開発を行っています。
その中でも、ASP.NET MVCモデルを用いたWebアプリ開発が主流となっています。
開発工程の一つとして"パフォーマンス試験"を実施しており、そこで得た知見を紹介します。
試験手順の紹介
パフォーマンス試験の中で、"WebAPIの同時実行"という観点を確認しています。
我々が実装したWebAPIの同時実行性能を計測する目的で、オープンソースの負荷検証ツールであるApache JMeterを使用して試験を実施しています。
同時実行性を決めるパラメーターとして、以下3つの値を調整します。
スレッド数
アクセスするユーザー数
Ramp-Up
期間スレッド数を何秒かけて実行するか
ループ回数
1スレッドが試験シナリオを何回繰り返すか
現在行っている試験手順では、Ramp-Up期間、ループ回数は1で固定し、スレッド数(ユーザー数)のみを調整して、同時実行性を決定しています。
つまり、1秒間にどれくらいの同時アクセスに耐えうるかを計測する試験になっています。
例として、ログインを試行するWebAPIの試験で、以下のような結果が得られたとします。
スレッド数 | 平均応答時間[s] | 最大応答時間[s] |
---|---|---|
200 | 1.1 | 1.6 |
400 | 1.7 | 2 |
600 | 2.4 | 3.5 |
800 | 2.9 | 4.3 |
1000 | 3.1 | 4.9 |
この結果から、
- 1秒間に1000件のログインが集中しても、平均して3秒程度、一番遅くても5秒で応答が返る
- 1秒間に400件のログインであれば、2秒以内にはすべての応答が返る
よって、「ユースケースとして許容できるね」などと試験結果を分析します。
これらの試験により、WebAPI単位で瞬間最大性能を担保できます。
複数のWebAPIが連続して実行されるようなケースを考えたときにも、個々の性能が担保されたものの集まりとして見ることができるので、意義のある試験だと考えています。
現在の試験で担保しきれないケース
そんな中、現在の試験では担保しきれないケースに遭遇しました。
多くのアイテムを一覧表示する画面など、1ユーザーが一斉に大量にWebAPIを呼び出すケースです。
現在の試験では、「1000ユーザーが同時に1アクセスする操作を行う」ケースを担保していますが、仮に1ユーザーあたり20件のアイテムを表示する画面を同時に開くとしたら・・・
「1000ユーザーが同時に20アクセスする操作を行う」となり、想定の20倍の件数が実行されることになります。
ところが、ASP.NET MVCの規定の動作では、同一セッションからの要求を並列で処理できないようになっています。
これは、HttpContextのSessionStateBehaviorというセッション取得時の動作定義によるものです。
#本来の目的であるセッションの排他に加え、同時処理件数を抑制する効果もあるのではと思います。
これにより、想定の20倍といった同時実行が要求されることはありません。めでたしめでたし・・・
とはいきませんでした。
同一セッションからのWebAPI同時実行による問題点と対策
同一セッションから大量に放たれたWebAPI呼び出しは、サーバー側で実行待ちとなります。
#動作を見る限り、後続のWebAPIが呼び出される順番は不定のようです。
待ちとなっている間も画面操作は継続し、後続のWebAPIが際限なく呼び出されます。
実行待ちのWebAPIは、処理が追い付かずに徐々に待機時間が蓄積し、いずれはゲートウェイタイムアウト(規定値20秒)に到達してしまいます。
#動作を見る限り、実行待ちとなった後続のWebAPIが処理開始されるまでに、最大で0.5s程の隙間時間があり、大なり小なりオーバーヘッドが発生しているようでした。
そこで対策として、該当のWebAPIにSessionStateBehavior.ReadOnlyの指定を行いました。
先述の通り、ASP.NET MVCの規定の動作では、同一セッションからの要求を並列で処理できないようになっていますが、これにより、セッションへの書き込みがないことを前提に、同一セッションからの要求を複数並列で処理できるようになります。
結果として、該当のWebAPIを含め応答時間が全体的に改善し、ゲートウェイタイムアウトが発生することはなくなりました。
#修正により、該当処理の同時実行件数は飛躍的に増加しますが、今回の想定ケースにおいては、CPU使用率やWebAPIの応答時間から見ても、他処理への影響はほとんどないという結果だったのが意外でした。
まとめ
これまでの試験手順からは、同時実行数=ユーザー数という先入観がありましたが、実際にはWebAPIごとのユースケースで同時実行性が大きく変わるので、それらを考慮した試験設計を行うべきと考え方が変わりました。
ポーリングにて一定間隔で呼び出されるものや、Push通知などで一時的にアクセスが集中するもの、今回のように1ユーザーが一斉に大量のアクセスを仕掛けてくるなど、製品の特性によりさまざまな考慮すべき観点があると思います。
柔軟な試験設計をできるようにすることが、今後見直していくべき課題の一つだと考えています。