性能試験に真面目に向き合う
サービス運用において切っても切り離せない性能試験ですが、
実際には「感覚的に」行われているケースも多く、試験内容の不足、考慮漏れが発生している事例があります。
性能試験は本来、安全にリリースするための重要な検証です。
雑な試験であっても、たまたまサービスイン後に問題が起きなければ「結果よければすべて良し」と片付けられがちですが、
私たちは ユーザー体験(UX)とビジネスの継続性 を担保する立場にあり、そのような「ギャンブル」は避けるべきでしょう。
本記事では、性能試験を実施する際に何を考慮すべきかを整理し、サービスを安全にリリースするための方法を解説します。
そもそもいつ実施すべきなのか?
開発段階(サービスイン前)
性能試験は、すべての機能を作り終えてから行うものではありません。
よくある失敗例として「全機能完成後に性能試験をしたら全然性能が出なかった」というケースがあります。
特にクリティカルになるのは次のような場合です。
- 基幹ロジックが原因で性能劣化を起こしている
- そもそも言語仕様と相性が悪い
- (例:巨大な JSON を扱うのに Node.js を利用、計算処理が多いのに GC の影響を受けやすいランタイムを選択)
これらは改修に膨大な時間がかかり、場合によっては作り直しが必要になります。
しかし、多くの場合 リリースを遅延できないため「一旦リリースして後で直す」という判断になりがちです。
ところがリリース後は機能追加が優先され、結果としてチューニングが後回しになり、札束で性能を買う という事態に陥り、利益率を圧迫します。
こうしたリスクを避けるためには、開発段階で性能試験を 定期的に 実施しておくことが重要です。
完全な試験である必要はなく、開発と並行して徐々に整備していけば十分です。
大切なのは、致命的な問題を早期に発見してリスクを潰すことです。
リリース後
性能試験は一度やって終わりではありません。
システムは改修や機能追加が繰り返される中で性能を悪化させる改修が入ってしまうことは避けられません。
ここで重要なのは どれだけ早く劣化に気づけるか です。
例えば、数か月〜1年ぶりに試験をしたら劣化が見つかったとしましょう。
原因はその間の膨大なコミットのどれかになり、特定は非常に困難です。
オブザーバビリティを使えば当たりをつけることは可能ですが、コード量が増えるほど難しくなります。
だからこそ、定期的に性能試験を行い、早期に劣化を検知することが大切です。
理想は CI/CD に性能試験を組み込むことですが、現実には難しいケースが多いでしょう。
そのため、まずは「2週間に 1回、簡易負荷を自動で流す」といった形でよいので、定期的に実行できる仕組みを作ることが望ましいです。
その際に リクエストパラメータを固定しすぎないよう注意してください。
ユーザー属性やクエリ内容は時間とともに変化するため、同じパラメータを使い回すと実際のリクエストと乖離するリスクがあります。
試験を常に「最新の利用状況を反映したもの」に保つ工夫が必要です。
また、性能試験はリリース前の一回限りのイベントではなく、サービスライフサイクル全体に組み込む習慣 として運用することが重要です。
試験設計の準備ステップ
システム構成を把握する
まずはシステム全体の構成を把握しましょう。
依存しているデータベースや外部 API、メッセージキューなどを洗い出すと同時に、データの処理フローを整理することが重要です。
リクエストがどの順番で処理され、どこに保存されるのかといった流れを明確にしてください。
この作業には大きく 2つの意味があります。
ボトルネック候補の発見
DB アクセスの集中、外部 API コールの遅延、同期処理の存在など、性能上のリスクをあらかじめ見つけられます。
試験の副作用の回避
一見「安全そう」に見える API でも、内部で更新処理が走っていることがあります。
例えば GET リクエストなのに裏でカウンタ更新をしていた場合、負荷試験によって意図しないデータが生成されるリスクがあります。
処理フローを明確にしておけば、どのような負荷をかける必要があるかを正しく評価できます。
ワークロードを把握する
次に重要なのは、システムにどのようなリクエストが、いつ、誰によって発生するのかを把握することです。
リクエストがかかる条件を明確にすれば、負荷の傾向を予測できます。特に次の観点を整理しましょう。
- ユーザー操作:どのような動作(ログイン、検索、購入など)でリクエストが発生するか
- スパイクの有無:急激にアクセスが増える場面はあるか(例:セール開始、イベント配信)
- ユーザー属性ごとの違い:属性ごとに処理フローが異なるか、割合はどの程度か
- リクエスト内容の重さ:軽い処理と重い処理の比率(例:キャッシュヒット、特殊処理 など)
これらは単なる推測ではなく、実際のログやモニタリングから傾向を確認することが重要です。
アクセスログや APM のトレースを活用すれば、現実に即したシナリオを作成できます。
また、負荷には時系列パターンがあります。
時間帯・曜日・イベントの違いを意識すると、より現実的な試験設計が可能です。
さらに、現状だけでなく将来の利用増加も考慮することで、サービスの成長に耐えられるシステムかを見極められます。
ただし、サービスイン前の場合にはこれらのデータが存在しないため、ある程度は予測ベースで用意する必要があります。
パフォーマンス目標設定
性能試験は、想定するユーザー数に対して適切にサービスの価値を提供できるかを確認する作業です。
ここで重要なのは「想定ユーザー数」と「適切な価値」の 2点です。
「適切な価値」とは、ユーザー体験(UX)の観点で定義されます。
UI であれば Core Web Vitals のような指標がありますが、負荷試験においては 応答速度(レイテンシ) と 正常応答性(エラー率の低さ) が代表的です。
通常、こうした目標は SLO(Service Level Objective) や SLA(Service Level Agreement) として定められます。
もし自分のシステムに明確な基準が存在しない場合は、ステークホルダーと相談して合意形成を行う必要があります(SLA は外部公開が前提となるため、社内的には SLO だけを定めるケースもあります)。
SLO/SLA の詳細は別記事で扱えるほど広いテーマですが、少なくとも次の観点で目標を設定するのが望ましいでしょう。
- Volume(最大処理件数):同時リクエスト数や処理スループット
- Availability(可用性):リクエストに正常応答できる割合
- Latency(レイテンシ):p95 や p99 の応答時間
- Errors(エラー率):全リクエストに占める失敗の割合
-
Tickets(要オペレーション件数):人手によるリカバリが必要になった回数
※ Tickets はオプションとして扱うケースが多いです。
これらを計測できるよう計装し、ダッシュボードやアラートとして可視化しておくことが重要です。
性能試験はこの可視化された指標を基準に評価を行います。
さらに忘れてはならないのが 費用対効果 です。
リソースを大量に追加すれば一時的に指標を達成できますが、それは「札束で性能を買う」行為に過ぎず、サービスの利益率を圧迫します。
一方で、信頼性を犠牲にしてコストを削減しすぎるのも本末転倒です。
したがって、目標指標と運用コストのバランスをどう取るかを、ステークホルダーと協議することが欠かせません。
どの検証をするのか?
一口に性能試験といっても、実施目的によっていくつかの種類があります。
どの試験を採用するかによって、準備コストや試験期間が大きく変わるため、実施計画の段階で優先度を整理しておくことが重要です。
代表的な試験は次のとおりです。
種別 | 与える負荷の傾向 | 検証内容 |
---|---|---|
ロードテスト | 想定される通常の負荷 | 想定ユーザー数・リクエスト数で安定稼働できるか |
ストレステスト | 想定以上の過負荷を段階的に与える | 過負荷時に障害やデータ不整合が起きないか |
性能限界試験 | 限界まで負荷を増加させる | 処理能力の上限やボトルネックを特定する |
スパイクテスト | 短時間に急激な負荷を与える | 瞬間的なトラフィック急増に耐えられるか |
耐久テスト | 長時間にわたり一定または変動する負荷を与える | 長期稼働での安定性、リソースリークの有無を確認する |
いずれの試験についても、基本的には すべて実施することが前提 です。
ただし、実施コストを抑えるために、システムの特性や重要度によっては取捨選択することも可能です。
- サービスが安定した流量しか扱わない → スパイクテストは不要
- 多少の停止が許容されるシステム → ロードテストだけでも十分
- ミッションクリティカルなシステム → 全種類を実施するのが望ましい
ここで重要なのは、試験を省略する=潜在的リスクを明らかにしない という判断である点です。
つまり「実施コストを削る代わりに、リスクを許容する」という意思決定になります。
一度リリースしたシステムを引っ込めるのは容易ではありません。
例えばリリース後にメモリリークなどが発覚した場合、ユーザーからのクレームに追われながら対応することになりかねません。
こうしたリスクを理解したうえで、覚悟を持って選択することが必要です。
試験設計時の考慮ポイント
負荷パラメータ(攻撃パラメータ)
性能試験で課題になりやすいのが、どのようなパラメータで負荷をかけるか です。
理想的には実ユーザーのリクエストを再現することですが、現実的にはかなり難しいアプローチです。
そもそもリクエストを再現するには、現状どのようなリクエストが流れているかを把握する必要があります。
しかし一口にリクエストといっても、考慮すべき内容は多岐にわたります。
- クエリパラメータ
- リクエストボディ
- Cookie
- ユーザー属性
これらを収集する場合、多くはログベースで取得することになりますが、ログコストの増大 や セキュリティ上の制約 が課題になります。
特にユーザー属性や Cookie は機微情報を含むため、ログに残せないケースもあります。
実用的なアプローチ
どこまで厳密に再現するかによって選択肢は変わりますが、次のような方法が考えられます。
- 試験専用ログを設ける:通常ログとは別に、特定期間だけ試験用パラメータを収集できる仕組みを用意する
- 安価なストレージへ転送:試験用パラメータを分析基盤に送らず、S3 などの安価なストレージに保存する
- サンプリングの導入:全リクエストを保存せず、一定割合でサンプリングしてデータ量を抑える
いずれも実装コストはかかりますが、実ユーザーのリクエストを再現できる基盤は性能試験だけでなく機能試験にも活用できる ため、投資する価値はあります。
ミラーリングという選択肢
条件が合えば、トラフィックミラーリング を使って実際のユーザーリクエストを試験環境に流すことも可能です。
この方法であれば、追加のログ収集コストなしで「本番に近い負荷」を再現できます。
ただし、更新系のリクエスト(書き込み処理など)が含まれる場合は、データ破壊や不整合を招くため利用できません。
採用可能かどうかは、自分のサービスの特性を踏まえて検討する必要があります。
何を使ってどこから投げるか?
性能試験で負荷をかけるためのツールは、基本的に OSS を採用するのが無難 です。
大抵のユースケースは OSS でカバーできるため、自作するのは特殊な事情があるときだけにしましょう。
代表的な OSS ツール
ツール | シナリオ対応 | コーディング | 特徴 | 注意点 |
---|---|---|---|---|
JMeter | 可能 | 不要 | GUI でシナリオ作成可能、プラグイン多数 | メンテナンス性はやや高コスト |
k6 | 可能 | 必要(JavaScript) | コード管理しやすく、CI/CD パイプラインに組み込みやすい | スクリプト実装の学習が必要 |
Locust | 可能 | 必要(Python) | 学習コストが低く、Python エコシステムと相性が良い | 大規模試験には調整が必要 |
vegeta | 可能 | 必要(Go) | 軽量でシンプル、スクリプト化しやすい | 高度なシナリオ作成には工夫が必要 |
正直なところ、どの OSS でも大抵のシナリオは実現できるため、チームが得意とする言語で選べば問題ありません。
負荷送出の実行環境
ツールを決めたら、次は どこから負荷を送出するか を考えましょう。
考慮すべきポイントは次の 3 つです。
-
送出側の性能
-
負荷ツールを動かすサーバー自体に十分なリソースが必要。
-
送出側の性能不足で「システムが強いのか、負荷が弱いのか」が判別できなくなります。
-
ネットワーク距離
- 近い場所(同一 AZ や同一リージョン)から実行すると通信遅延が少なく、実際より性能が良く見えることがあります。
- 最低でも 別リージョンから、可能であれば 複数インスタンスを分散配置 して送出してください。
-
分散実行
- 単一インスタンスでは送出側がボトルネックになることが多いです。
- 複数インスタンスに分散させることで、より現実的な試験が可能になります。
⚠️注意:絶対に手元の PC から実行しないでください。規模にもよりますが、ほとんどの場合はリソース不足で正しい性能が測定できません。
実行結果とチューニング
リソースをいつ調整するのか?
性能試験を実行して期待した性能が出なかったときに、リソース(CPU・メモリ)を調整すべきかどうかは難しい判断です。
まだ一度も公開していないシステムであれば、比較的安易にリソースを変更しても構いません。
ただし、CPU が極端に不足している場合を除けば、性能不足の原因は多くの場合アプリケーションコードにある ことを忘れてはいけません。
既に本番運用しているサービスの場合は、リソース割り当て変更には慎重であるべきです。
なぜなら リソースを変更しなければならない=過去よりも性能が劣化している ことを意味するからです。
多くの場合、機能追加や改修によるコード変更が原因なので、まずはその箇所を特定して修正することが基本方針です。
しかし、原因調査や修正に時間がかかり、ビジネス的に待てないケースもあります。
そうした場合には スケールアップではなくスケールアウトを優先 し、1 インスタンスあたりの負荷を下げて回避することを推奨します。
なぜリソース変更を避けるのか?
リソースを変更すると、過去に蓄積した運用実績やメトリクスの基準が失われます。
今まで「CPU ○コア、メモリ ○GB」で集めてきたデータが、設定変更によって比較できなくなるため、長期的な分析や傾向把握に支障が出る のです。
そのため、基本的にはリソース割り当ては安易に変えず、まずコードやアーキテクチャを見直す ことが推奨されます。
どのような場合にスケールアップを許容できるか?
基本的にはスケールアウトが優先されますが、分散よりも 1 インスタンスで処理するほうが効率的なケース ではスケールアップが選択肢になります。
具体例としては次のようなケースです。
-
ワークロード的に分散させにくい構成
- スケールアウトでの一貫性コストが高い(分散ロックやトランザクションのオーバーヘッドが支配的)、大容量のメモリキャッシュを 1 インスタンスに保持する前提になっている
-
ライセンス上の制約
- 商用ライセンスが「インストール台数」で課金される場合は、少数台を強化するほうがコスト効率が良い
-
起動コストが高い
- モデルロードや事前キャッシュロードなどに数分以上かかり、頻繁に Pod を増減させるのが非現実的な場合
CPU 利用率の適正値
では実際に CPU をどの程度割り当てるべきかというと、設定した SLO を守れる利用率を基準 に考えるのが適切です。
一般的には CPU 使用率が 60〜70% 程度 で収まるように設定するのが目安です。
多くのアプリケーションは CPU 利用率が 80〜90% に達すると性能劣化が起こるため、ある程度の猶予を持たせる必要があります。
requests と limits の設定について
Kubernetes では requests
と limits
を同じ値にすると CPU スロットリング が発生し、レイテンシが悪化するケースがあります。
そのため、一般的には次のような設定が無難です。
- requests = 実測ピーク × 1.2〜1.5
- limits = 無制限、または requests の 2〜3 倍
CPU の場合、メモリのように「超えたら OOMKill」という挙動にはならず、瞬間的なバーストを許容することが多いです。
そのため、理想的には limits を無制限にする のが望ましいとされます。
limits を設定すべきケース
ただし、常に無制限が正解というわけではありません。状況によっては limits の設定が有効です。
-
マルチテナント環境
- クラスタを複数チームで共有している場合、CPU を無制限に使うと ノイジーネイバー を引き起こす可能性があります。
- 適度に上限を設けて、クラスタ全体の公平性を保ちましょう。
-
コスト制御が必要な環境
- CPU 使用量に応じて課金が発生する場合、limits で上限を設定することでコスト抑制につながります。
- ただし、性能劣化とのトレードオフになるため慎重な意思決定が必要です。
サービスイン戦略
性能試験はあくまでもユーザーのリクエストを擬似的に再現した結果に過ぎず、実際のトラフィックにさらすと思ったより性能が出ないケースは多々あります。
そのため、性能検証の結果を過信せず、慎重なリリースを行うことが重要です。
まずは可能な限りカナリアリリースを採用しましょう。
実際のユーザーのアクセスを調整後のインスタンスにごく一部だけ流すことで、影響範囲を最小限に抑えつつ挙動を確認できます。
新規サービスで 100% リリースしかできない場合は、アクセスを AB テストのような仕組みで限定するのが望ましいです。
それも難しければ、リリース直後はあえて過剰気味にプロビジョニングしておき、性能不足を吸収できる余裕を持たせましょう。
オートスケーリングは負荷変動にはある程度対応できますが、瞬間的なスパイクには無力です。
過信せず、余裕を持ってリソースを確保しておくことで、想定外の負荷にも耐えられるようにします。
最後に、実際のトラフィックとリソース状態をメトリクスで確認しながら、徐々にスケール調整を行うことで、最終的に適切なインスタンス数を見極めていくことが大切です。
まとめ
性能試験は単に「負荷をかけてシステムを壊す」ことが目的ではありません。
ユーザー体験を守り、サービスを安定的に成長させるための重要なプロセスです。
この記事で取り上げたように、性能試験は次のステップを意識して進めると効果的です。
- システム構成やワークロードを把握し、実際の利用を再現する
- SLO を基準にパフォーマンス目標を定め、正しい指標を計測する
- 試験内容(ロード、ストレス、スパイクなど)を選び、必要なものに集中する
- 負荷ツールや実行環境を適切に選び、再現性ある試験を行う
- 実行結果を過信せず、リソース調整は慎重に進める
- サービスイン後も定期的に試験を行い、劣化を早期に検知する
重要なのは「一度やって終わり」ではなく、サービスライフサイクル全体で性能を見続ける文化を持つこと です。
過信せず、継続的に、そして現実的に運用することが、結果としてユーザーの信頼を守り、サービスの成長につながります。
Discussion