はじめに
こんにちは、Checkout Reliabilityチームでバックエンドエンジニアをしているかがの(@ykagano)です!
Checkout Reliabilityチームはカートの信頼性を向上させるためのチームです。
今回、BASEのカート機能を安定的に提供するために、継続的な負荷テスト環境を構築しましたので第1回として、本記事では全体像を紹介します。
全3回の記事を予定していますので、よろしくお願いします。
BASEの負荷テスト
BASEではこれまで負荷テストは必要に応じて都度実施していました。
k6 というオープンソースソフトウェア(以降OSS)の負荷テストツールを使って、BASEのカートへの商品追加から購入完了までのシナリオの負荷テストを行い、負荷が高まっても機能的な問題がないことを確認していました。
しかし、この都度実施の負荷テストには以下の課題がありました。
負荷テストの課題
1. 以前作成したテストシナリオがシステムの仕様変更で動かない状態になっている
k6のテストシナリオはJavaScriptで書けるのですが、例えば購入可能な状態にするために必要なAPIやパラメータが一つ増えていたりすると修正が必要になります。
2. 外部APIの仕様変更で、外部APIに見立てたモックのコード修正が必要になっている
商品を購入する際には、BASEから社外の決済APIを呼び出しているのですが、開発環境とはいえ、社外の決済APIに負荷をかけるわけにはいかないため、決済APIの応答に見立てたモックを用意しています。そのため、この社外APIの仕様が変更になるとモックの方もコード修正が必要となります。
3. 新機能を作成した後、改修の度に負荷テストを行っておらず、現在のキャパシティ(負荷に対する許容量)が分からない
当時行った負荷テストの記録は残っているのですが、仕様変更があった今も同じキャパシティを維持できているのかは負荷テストをしてみないと分かりません。しかし、再度負荷テストをするためには、前述のハードルがあります。
これらの課題を解決して、継続的に負荷テストを行えるようにしよう、というのが今回の取り組みです。
負荷テスト環境の要件
継続的な負荷テスト環境として求めた要件は以下となります。
1. 負荷テストを自動で定期実行し、結果をレポートできる
定期的に負荷テストを行うことで、仕様変更によるエラー等を早期に検知できることを求めました。
2. 負荷生成ツールもモックもスケールアウトできる
k6のようなシステムに負荷を与えるためのツールを負荷生成ツールと呼びます。この負荷生成ツールとモックの両方を今後システム規模が大きくなっても追従して高負荷を与えられるように、負荷生成ツールやモックの台数を増やす機能を求めました。
3. モックがノーコードで更新できる
これまで使っていたモックはPHPによる独自実装をしていました。これだと仕様が変わるとコードの変更が必要です。今回はOSSのモックツールを使用することで、コードの変更をせずにモックの仕様を更新できる機能を求めました。
ベストプラクティスの調査
これらの要件に加え、負荷テスト環境としてのベストプラクティスを調査したところ、下記の記事を見つけました。
ベストプラクティスの部分を抜粋します。
- 実際のワークロードを使用して負荷テストを行い、自身のワークロードが本番環境でどう動作するのかを確認すること
- 本番環境同等のサイズの環境を利用して試験を行うこと。また、本番環境同等の量・バリエーションのデータを、本番環境データを匿名化したり模擬的に作成したりして用意すること
- ユーザーの実際の行動をリプレイ、もしくはプログラム的に再現することで、大規模にアーキテクチャ全体に対して負荷をかけること
- デリバリーパイプラインの一環として負荷テストを自動的に実行し、事前に定義した性能目標と比較して評価し、要求性能を満たせるかどうかを継続的に保証すること
ベストプラクティスに書かれていることは、私が負荷テスト環境に求めた要件とほとんど同じですが、負荷テストの考え方が間違っていない裏付けとして非常に参考になりました。
負荷テストツールの選定
こうした考え方を元に、継続的な負荷テスト環境の要件を満たすための負荷テストツールを選定しました。
負荷生成ツールには Locust を選定しています。
また外部APIのリクエストを受けるために WireMock を選定しました。
それぞれの詳細については第2回以降の記事で説明したいと思います。
負荷テスト環境の構成
最終的に負荷テスト環境の構成は下図となっています。
左が負荷生成ツールとモックツールを使用する負荷テストツール。 右が既存のBASEシステムと同等の負荷検証システム(簡略)となります。

実際のリクエストと同様にインターネットを跨いでリクエストを処理するようにしています。
ここからは負荷テストの構築にあたり進めた内容についてお話しします。
キャパシティプランニング
負荷テスト環境を構築するために、キャパシティプランニングを行いました。
まず以下の2点を確認しました。
- BASEのGMV(流通取引総額)がこの先どれぐらい増加する想定なのか
- 現時点での本番環境での最大負荷はいくつなのか
1に関しては事業計画を参照すれば分かりますが、2に関しては、どこの負荷を基準とするかを検討しました。
BASEのカート機能には注文APIがありますので、そのAPIに対する負荷を基準にすることにしました。
またBASEではこれまでrpm(requests per minute)を基準にしていましたが、今回は高負荷状態を基準にするため、rps(requests per second)を基準にすることにしました。
ここでは注文APIの過去の最大負荷を仮に1,000rpsとします。
また事業計画として2年後のGMVが仮に1.5倍になるとします。
そうすると、2年後にはrpsも1.5倍の1,500rpsが最大負荷として想定されます。
しかし、あくまで成り行きで増える見積もりとなるため、上ブレすることも考えて多めに見積もりを行いました。
増加分の500rpsを2倍にして上振れ含め1,000rpsになるため、2,000rpsを目標値とします。
このような考え方で、今回構築するシステムの負荷テスト可能なキャパシティとして設定しました。
負荷テスト環境の構築
キャパシティプランニングでは、注文APIの負荷を仮に2,000rpsと設定しましたが、2,000rpsはあくまで注文API単体の負荷となります。
今回購入のワークロードを対象としているため、そのワークロードでは20を超えるAPIを利用しており、全体としては数万rpsの負荷が想定されました。
こうした要素を加味して、負荷テスト環境のシステム要件を決めていきました。
Gold環境
本番と同等の負荷検証システムは、既に以前から使っているGoldという名前の環境があったため、それを今回も利用することにしました。
しかしこのGold環境は、本番と同等のDBを使用するため、本番相当のデータ量の準備に数時間かかるという課題がありました。
負荷テスト環境は最終的に負荷テストの定期実行までを見据えていました。
負荷テスト環境の起動後、負荷テストを実行し、負荷テスト環境の破棄まで行う想定です。
しかし、定期実行が準備だけで数時間かかるとなると、コンテンツのデリバリー途中でタイムアウトする可能性もあります(実際にこのデータ準備の処理が何度か止まっていることがありました)。
再実行が頻繁に必要になってくると定期実行に支障が出ます。
DBが巨大すぎるため、Gold環境での定期実行は難しいことが分かってきました。
巨大なDBは存在するだけで多大なコストがかかるため、定期的に数時間利用するだけでも多大なコストがかかります。
定期実行ではこの課題を解決する必要がありました。
Bronze環境
本番同等の環境で負荷テストを行うというベストプラクティスには反するのですが、Gold環境での定期実行の課題を解決するため、負荷テストを自動で定期実行し、結果をレポートするための環境として本番よりスペックの低い縮小環境も今回用意することにしました。
この縮小環境には Bronze と名付けました。
命名理由としてはすでにSilver環境を別用途で使っていたためです。
このBronze環境があれば、テストシナリオの修正と確認もBronze環境で容易に行えます。
最終確認としての負荷テストはあくまでGold環境を使用しますが、定常的に必要最小限のキャパシティを担保するには、Bronze環境でも十分要件を満たせると考えました。
Bronze環境では開発環境のデータベースをそのまま流用することにしました。システムの仕様としては、本番環境の構成を縮小した形で仕様を決めていきました。
Gold環境とBronze環境の比較
| 項目 | Gold環境 | Bronze環境 |
|---|---|---|
| 目的 | 本番想定の最終的な負荷検証 | 継続的な負荷テスト・定点観測 |
| スペック | 本番同等 | 本番より縮小 |
| データ量 | 本番同等(大規模) | 開発環境のデータを流用 |
| 起動時間 | 数時間(データ準備に時間がかかる) | 比較的短時間 |
| コスト | 高い | 低い |
| 実行頻度 | 必要に応じて実施 | 定期実行(週次) |
| 主な用途 | 最終確認・性能限界の検証 | 早期検知・継続的な品質担保 |
| メリット | 本番に近い精度の検証が可能 | 手軽に何度でも実行できる |
| デメリット | コスト・準備時間が大きい | 本番との差異がある |
Bronze環境で継続的に異常検知を行い、最終的な性能検証はGold環境で行う、という役割分担にしています。
チームでの構築
今回、チームで負荷テスト環境の構築を進めていきました。
草案は私の方で作成しましたが、設計は全員で行いました。
その後、Bronze環境の構築、AWSインフラとLocust環境の構築、WireMock環境の構築と3つに分かれて構築を行いました。
私は主にBronze環境の構築を行ったのですが、BASEのテスト環境を1セット作成する作業のため、BASEのシステムに対する解像度を上げることができ、非常に良い経験を得られました。
Bronze環境での継続的負荷テスト
毎週月曜に以下のスケジュールで負荷テストを自動実行しています。
| 時刻 | 内容 |
|---|---|
| AM3:00 | アプリケーションの自動デプロイ |
| AM7:00 | 負荷テストの自動実行 |
負荷テストの自動実行はGitHub Actionsのワークフローで行っています。
以下のジョブが40分程度で実行されます。
- 負荷テスト開始の通知
- Bronze環境の起動
- 負荷テストツールの起動
- 負荷テストの実行(メインシナリオ1)
- 負荷テスト結果の通知
- 負荷テストの実行(メインシナリオ2)
- 負荷テスト結果の通知
- 負荷テストツールの停止
- Bronze環境の停止
- 負荷テスト完了の通知
負荷テスト結果はSlackに投稿されます。
以下はレポート内容の例ですが、注文API(post_orders)に対するrpsとAPI全体(aggregated)のrpsを見れるようにしています。
post_orders: requests=2000 failures=0 avg_time=20ms avg_rps=8.0, aggregated: requests=80000 failures=400 avg_time=500ms avg_rps=300.0
これで毎週最新のアプリケーションでキャパシティの定点観測ができる状態になりました。
本番と同等の挙動ではないですが、もしパフォーマンスの低下があれば週次で検知できます。
また今後負荷テストを実施したい場合にも、すぐに使える環境があるため、負荷テスト実施のハードルも下がってくれるのではないかと期待しています。
Gold環境での本番想定の負荷テスト
Gold環境での負荷テストは当初パフォーマンスが想定したよりも出ませんでした。
本番と同等の環境ではありますが、本番と仕様や設定が一部異なっていたため、それらを本番と揃える作業が必要でした。
また本番と挙動が異なる部分もありました。
これは外部APIの通信時間を細かく本番と合わせたり、外部APIが本来持っているRate Limit(一定時間内にリクエスト回数の上限を設ける機能)の機能を再現するための仕組みを入れることで徐々に解決していきました。
負荷テストは実行してパフォーマンスが出ない場合にどこが原因か調査しボトルネックを見つけることも大事ですが、パフォーマンスをもっと出すためにはどうすればいいかは、仮説を立てる必要があります。
そのため、負荷テストの実施前に負荷改善の仮説DBをNotionに作成し、この辺りを改善すればより早くなるのではないか、といった仮説をチーム内で思い付いたものがあれば、確度が低くても良いのでどんどんDBに入れていくことにしました。
今後はこの負荷テストの結果と仮説を元に、BASEのカートの安定性をより高めるための施策を実施していく予定です。
おわりに
今回の記事では、継続的な負荷テスト環境の全体像と設計方針について紹介しました。
第2回では負荷生成ツールの構築と運用について、第3回ではモックツールの構築と運用についてお話しする予定です。
順次公開しますので、ご期待いただければと思います。
このようなシステム環境で働きたいエンジニアの方はぜひ採用情報をご覧ください!