はじめに
こんにちは、大手飲食チェーン向けの eGift System のエンジニアをしている中妻です。
普段はクライアントが実施するプロモーションのための開発をしたり、保守や業務効率化のための運用改善をしたりしています。
私が運用しているプロダクトでは、1 年間の中でクリスマスシーズンがもっとも多く売れ、ユーザのアクセス数も急激に増えます。そのため、毎年その時期になると負荷試験を必ず実施し、劣化している機能がないか、サービスダウンせずに乗り切れるかを検証し改善しています。
今回は、その負荷試験について実施方法やポイントについてご紹介いたします。
想定読者
- 負荷試験の実施方法に悩んでいる方
- 負荷がかかるプロダクトの運用に興味がある方
背景
毎年負荷試験を実施しているのですが、以下の理由により安定運用の実現ハードルが年々上がってきています。
- ユーザのアクセス数が見積もりづらい
- ユーザへの認知が進み、平均のアクセス数自体が年々伸びていっているため
- クリスマスプロモーションの内容が毎年微妙に変わるため
- 世の中に流通したeギフトの数が多くなるほど、それらのギフト券面が同時に参照されて瞬間的にアクセス数が伸びる可能性が上がるため
- パフォーマンスチューニングがしづらい
- DB のレコード量が増えてきているため
- テーブル定義変更のリスクや影響範囲が大きいため
こうした背景から、当プロダクトでは負荷試験を通じて実際に検証することが重要になっています。
ポイント
負荷試験を実施する上で最も重要なポイントは「負荷試験の目的とゴールを定義すること」です。(全ての物事に言えることかもしれませんが)
一口に負荷試験と言っても、以下のような目的に分かれると思います。それぞれの目的に合わせた準備や手順があるので、それを定義することが最も重要だと思います。
- 高負荷時にパフォーマンスが著しく劣化する機能を発見する
- アプリケーションが稼働するための適切なサーバースペックやサーバー 1 台あたりの worker 数などを知る
- ある程度の負荷を長時間かけ続けても十分なレスポンス時間を維持できるかを検証する
今回の負荷試験の目的は「クリスマスシーズン中に想定される最大負荷に対して十分な性能を維持できるサーバー台数を見積もる」ことです。こちらを目的とした実施手順をご紹介いたします。
実施方法
前提
前提として、当プロダクトの構成について記載します。
- アプリケーションは Rails
- DB は AWS Aurora (MySQL)
- インフラは AWS を利用 (ALB/EC2 with Auto Scaling Group)
- 構成管理ツールとして ansible を利用
準備
見るべき指標とゴールを定義する
目的を定量化して見るべき指標とゴールを定義します。今回は、
- ゴール:
- 想定される負荷に対し、エラー率 1% 未満かつ平均レスポンス時間 4 秒以内を維持できるサーバー台数がわかる
- 見るべき指標:
- エラー率 (全リクエスト中のステータスコードが 5XX で返ってきたものの割合)
- レスポンス時間 (リクエストを投げてからレスポンスが返ってくるまでの時間)
- サーバー台数 (EC2 インスタンスの台数)
とします。エラー率 1% 未満
や平均レスポンス時間 4 秒以内
などの具体的な数値は、プロダクトオーナーとすり合わせをしておく必要があります。
なお、厳密なパフォーマンスの定義と計測が求められる場合は percentile 値を指標にする方が効果的です。
※ percentile 値についてはこちらの記事の "What is a percentile and why would you look at percentile response times when you already have average response times?" という部分が参考になります。
クリスマスシーズン中に想定される最大負荷を見積もる
過去の負荷傾向から今回のシーズンの最大負荷を見積もります。
昨年の実績と直近数ヶ月のアクセス数の傾向を参考に、今年の瞬間的なアクセス数を参考に見積もってその数だけ実際にリクエストを投げるようにします。
今回は CloudWatch Logs Insight を使って特定のエンドポイントにおける過去及び直近数ヶ月の瞬間的なアクセス数 (今回は 1 秒あたりのリクエスト数) を導出します。
※ ALB のリクエスト数から導出しても大丈夫ですが、toC 向けのプロダクトを運用している場合は悪意のあるユーザからの DDoS による瞬間リクエスト数が最大値とならないように注意する必要があります。
本番と同等スペックのインフラを用意
本番と同じ条件で検証するために本番と同等のスペックのインフラを用意します。
DB には本番と同等量のレコードを事前に入れておきます。
試験実施時にインフラが壊れることもあり、その際スムーズに交換できた方が実施効率が良いため、構成管理ツールの導入やインフラのコード化を事前にしておくことをおすすめします。
負荷試験用の資源を用意
外部サービスの API (当プロダクトでは決済代行の API がこれに該当します) をスタブにします。internet 経由での通信は不安定かつ外部サービスの検証環境は本番同等の性能を有していない可能性もあるため、実際に API をコールしての試験はしません。外部サービスが通常通りのレスポンス時間で返してくれることを前提とし、そのレスポンス時間だけ sleep する処理を入れた資源で試験を実施します。
また、slack のアラート通知やメール送信等の処理を削除した資源で試験を実施します。
手順
実施した負荷試験の具体的な手順です。以下の PDCA を繰り返します。
Plan
負荷試験を実施する条件を決めます。
負荷をかける (リクエストを投げる) 側と負荷をかけられる (検証するアプリケーション) 側の両方を決めます。
今回の試験では「想定される最大負荷に対する適切なサーバー台数を検証する」ため、
- 変えない条件: 投げるリクエストのシナリオ (どのエンドポイントに対しどの程度のリクエストを投げるか)
- 変える条件: サーバー台数
となります。
今回は、あるエンドポイントに対して秒間 100 リクエストを 10 分間をサーバー台数 2 台 (普段の台数) から少しづつ台数を増やしてく形で実施しました。
負荷をかける側のツールとしては JMeter を使用しました。比較的メジャーな負荷試験ツールの一つで、XML 形式の設定ファイルを編集することでリクエストのシナリオを定義できます。 (GUI 操作での編集も可能です)
Do
負荷試験を実施します。その際 flood.io というサービスを使用しました。flood.io は負荷をかける側のインフラを提供してくれる saas サービスで、JMeter や Gatling などの負荷試験ツールの設定ファイルをアップロードすると負荷をかけてくれます。
※ 個人情報や本番の認証情報が含まれたファイルをアップロードしないように注意してください。
また、インフラの負荷のモニタリングとして CloudWatch Metrics を使用しました。
アプリケーション側のモニタリングには Newrelic という APM を使用しました。
Check
負荷試験の結果を評価します。最初に定義した見るべき指標が期待通りの値かどうかを確認します。 flood.io の結果画面から、エラー率と平均レスポンス時間を確認します。
また、今回の目的とは異なりますが Newrelic からパフォーマンスが著しく劣化している機能がないかもついでに確認します。
Action
エラー率 1% 未満、平均レスポンス時間 4 秒以内という目標値を満たせているかを確認します。
満たせている場合は、その時の台数が今回の負荷試験で得られた結論になります。
満たせていない場合は、サーバー台数を増やして再度試験を実施します。(満たせるまで繰り返します。)
また、パフォーマンスが著しく劣化している機能が確認できた場合は、そのチューニングを別途検討します。(チューニングがすぐにできそうであれば、その対応を行ってから再度同じ条件で試験を実施します。)
今回試験を実施した時は、プロモーション用に追加したテーブルのカラムに index を貼り忘れていたためパフォーマンスが著しく劣化していたので、index を貼った上で再度試験を実施し直しました。
まとめ
負荷試験の実施方法やポイントについてご紹介してみましたが、いかがでしたでしょうか?
毎年必ず負荷試験をしているものの「今年も無事に乗り切れるかな。。」と不安になることもあります。しかし、負荷がかかる = ユーザが増えているということなので、きちんと対策して乗り切れたあとは充実感が得られ大きなモチベーションになります。
そして、負荷試験をしていていつも思うことが "開発だけがエンジニアリングじゃない" ということです。負荷試験をする場合、ユーザがどういう文脈で使うとどれくらい負荷になるかな?という視点でのインプットや調査をする時間も必要ですし、負荷試験の実施のための環境を作るという作業も必要です。
きちんとしたユーザ体験を作り続けるためには、このようなコードを書くこととは全く別の作業が必要であることもしばしばあるので、そのような視点でプロダクトや普段の運用を見直してみてはいかがでしょうか?
引き続きプロダクトの安定運用とユーザ体験の向上に取り組んでいきたいと思います。