はじめに
最近始まったプロジェクトの中で、開発しているSaaSのアプリケーションの性能測定を行うことになりました。
自分の頭の中を整理するためにも性能測定でやることをまとめました。
前回は性能測定の「準備」についてまとめたので、
今回は性能測定の「計測結果と考察」についてです。
第一回目はこちら
https://qiita.com/waste_/items/03bce598f89cb88350fe
※間違いや不明点などありましたらご指摘いただけると幸いです。
性能測定を行う背景と目的
性能測定を行うに至った理由は、開発しているアプリケーションの機能要件を大きく変更することになったからです。この機能要件の変更により、大量データの作成、取得を行う必要があるため、非機能要件を満たすかを検証する必要がありました。
今回の性能測定の目的は、大量データが存在するときに既存の機能に大きな影響があるものが存在するかを調査することでした。
また、短期間で新機能をリリースするための調査であったため、できるだけ早く性能検証をする必要がありました。
そのため、今回は、広範囲の機能を大まかに測定することにしました。
見る性能指標と測定対象、その基準値は以下になります。
性能の指標 | 測定対象 | 測定方法 | 目標の基準値 |
---|---|---|---|
レスポンスタイム | apiのレスポンス速度 | JMeter、Chrome DevTools | 基本的には3s |
リソース使用量 | バッチ処理 | datadog、AWSのモニタリング | 処理が正常終了する(処理時間は個々による) |
また、この性能測定の目的は既存の機能に大きな影響があるかというところを調査することなので、大量データが存在することでユーザー体験に影響のある部分も調査しました。
計測結果と考察
計測結果
計測の結果です。
4つの点で、現状の仕様だと問題が起こることがわかりました。
apiのレスポンス速度
apiのレスポンス速度の目標値を基本的には3sと置いていました。
しかし、大量データが増えたテーブルのCRUD操作をした場合、1minを超えるものも多くありました。
システムリソースの枯渇
バッチ処理などで大量データが増えたテーブルに対してCRUD操作を行う場合には、処理が想定時間よりも長くなるものや、
処理が終わらなくなるものもありました。
その原因の多くは、メモリ枯渇によるものでした。
外部ツールの限界
今回のアプリケーションでは、csvファイルを用いた一括処理などを行っていました。
しかし、大量データの存在により、そのエクセルの仕様の限界を超えるということが起きました。
エクセルの行数の限界は、約104万行ですが、今回の大量データはその量を超えるものだったため、エクセルでは表示しきれないという問題が起こりました。
ユーザー体験の劣化
性能以外にもユーザー体験に関わるところで問題がありました。
つまり、性能が問題なくても、実際にユーザーが使ったときに問題が出てしまう機能がありました。
例えば、ページング機能です。今回の大量データ作成に関わる表示では、10000ページ二分割されている画面も存在しました。
しかし、そんなにページ数があったところでユーザーが利用できません。
また、csvファイルを編集する必要のある機能もありますが、編集する量が膨大すぎてユーザーが利用できないものもあります。
結果に対する考察
現状の仕様だと問題が起こることに関して、対応策などを考えていきます。
apiのレスポンス速度の劣化
apiのレスポンス速度の遅い原因は、アプリケーション層からDB層へのアクセス速度が遅いことが1つあります。
そのため、以下の対応策が取り得ます。
・インデックスを貼り、処理速度を早める
・クエリキャッシュを持たせる
https://qiita.com/ryurock/items/9f561e486bfba4221747
メモリ枯渇による処理停止
メモリ処理で落ちていた箇所は、アプリケーションの中でのトランザクションが貼られていたり、並列処理が走っているところでした。
・トランザクション処理
既存の処理では、元々大量データの処理を行う前提で作られているものもあったため、トランザクションを一括処理で行っていました。
そのためメモリが圧迫していました。
対応策としては、処理数を限定してコミットする中間コミットを用いる実装に変更することなどがあります。
参考:https://terasoluna-batch.github.io/guideline/5.0.4.RELEASE/ja/Ch05_Transaction.html
・並列処理
既存の処理では、処理速度向上を目的に複数プロセスを並列して処理が行われていました。
この場合、親のプロセスに利用するのと同様のメモリを子プロセスにおいてもあらかじめ確保されるため、1プロセスの2倍のメモリを利用することになり、メモリを圧迫していました。
現状のメモリでやりくりするには、処理速度を犠牲にして、並列処理をやめることが考えられます。
参考:https://qiita.com/Kohei909Otsuka/items/26be74de803d195b37bd
時間と空間のトレードオフの例として最も一般的なのはルックアップテーブルを利用したアルゴリズムである。テーブルの全てを最初から持つようにした実装では、処理時間は削減できるが、必要なメモリの量は増加する。また、テーブルの要素をその都度計算することもでき、これは計算時間は増加するが、必要なメモリの量を削減できる。
https://www.weblio.jp/wkpja/content/%E6%99%82%E9%96%93%E3%81%A8%E7%A9%BA%E9%96%93%E3%81%AE%E3%83%88%E3%83%AC%E3%83%BC%E3%83%89%E3%82%AA%E3%83%95_%E6%99%82%E9%96%93%E3%81%A8%E7%A9%BA%E9%96%93%E3%81%AE%E3%83%88%E3%83%AC%E3%83%BC%E3%83%89%E3%82%AA%E3%83%95%E3%81%AE%E6%A6%82%E8%A6%81
ここで話した処理速度とメモリ容量に関しては、抽象的には、時間と空間とトレードオフという概念に当てはまり、
それぞれの要求の中で最適値を取る必要があります。
例えば、そもそもコスト気にしてなくて良いなら、メモリ増加すれば良いという話になるので、それぞれの状況に応じてどこに性能のシワを寄せるかを考える必要があります。
外部ツールの限界とユーザー体験の劣化
外部ツールの限界とユーザー体験劣化の問題に関しては、アプリケーション側の仕様でどうにかするしかありません。
そのため、対応策としては以下が考えられます。
・既存の仕様を変更する
・大量データ作成をしない
まとめ
性能測定を行い、その結果と考察をまとめてみました。
今回の計測結果から、少なくとも既存仕様のままでは性能・ユーザー体験共に限界がきてしまうことがわかりました。
ここから、大元の仕様を検討するヒントになることができたので、機能要件を満たすために非機能要件がどうなるかを調査することの重要性がわかりました。
性能に関する理解を深められたことで、実装をしていく時に、この書き方したらリソース食いすぎるなとか、このクエリだと性能落ちてしまうなとかを考えるきっかけになったので、実装にも生かしていきたいと思います。