【図解】Webシステムのパフォーマンス設計|性能要件・性能試験・チューニングを解説

  1. はじめに
  2. Performance Engineeringとは
    1. Webシステムは複数のレイヤで構成される
    2. 性能設計・性能試験・チューニングの役割
    3. 性能の良さは要件とコストで決まる
  3. Performanceを測る基本指標
    1. Response TimeとLatencyは測定点と一緒に定義する
    2. 平均だけでなくPercentileを見る
    3. RPS・TPS・同時ユーザー数を混同しない
  4. Throughput・Response Time・Saturation
    1. 負荷を増やしても処理量が増えなくなる点がある
    2. 最大スループットと運用できる処理量は違う
    3. リトルの法則(Little’s Law)で処理量・時間・同時数をつなぐ
  5. BottleneckとQueue
    1. Bottleneckは処理の流れの中で見つける
    2. Response Timeには処理時間と待ち時間がある
    3. M/M/1モデル
    4. Queueは一時的な変動を吸収するが、処理能力は増やさない
  6. Utilization・Saturation・Error
    1. Utilization:どれだけ使われているか
    2. Saturation:どれだけ待たされているか
    3. Error:失敗は起きているか
    4. CPUが低くても、システムは遅くなる
    5. メモリやストレージも使用率だけでは判断しない
  7. WebシステムをEnd-to-Endで考える
    1. 時間を足す前に、重複と並列処理を確認する
    2. メトリクス・ログ・トレースをつなげる
    3. 一部の高速化が全体に効く割合を考える
  8. Performance Requirementと性能試験
    1. 性能要件を測定可能な条件にする
    2. Workloadは件数だけでなく中身を再現する
    3. 試験の種類を目的で使い分ける
    4. Open ModelとClosed Modelを区別する
    5. 負荷生成側がBottleneckになっていないか確認する
    6. 結果は変更前後を同じ条件で比較する
  9. Performance Troubleshootingの基本フロー
    1. Step 1:症状と影響範囲を確認する
    2. Step 2:時系列・平常時・変更履歴を比較する
    3. Step 3:End-to-Endで問題区間を絞る
    4. Step 4:Resource・Queue・待ちを確認する
    5. Step 5:仮説を立て、観測で検証する
    6. Step 6:対策を実施し、同条件で再計測する
    7. Step 7:原因・限界・監視項目を設計へ戻す
    8. 影響が大きい場合は原因調査と並行して緩和策を実施する
  10. まとめ
  11. 参考資料

はじめに

Webシステムが遅くなったとき、「CPUを増やせばよい」「スレッド数を増やせばよい」「データベースをチューニングすればよい」と考えたことはないでしょうか。どれも有効になる場合はありますが、原因と条件を確認しなければ、費用だけが増えたり、かえって応答が遅くなったりすることがあります。

たとえば、データベースの応答が遅くなり、アプリケーションのスレッドが結果待ちで埋まっているとします。この状態でスレッドを増やすと、データベースへの同時アクセスが増え、さらに待ち時間が長くなるかもしれません。性能を改善するには、個々の設定だけでなく、システム全体でどの仕事が流れ、どこで時間を使い、何を待っているのかを捉える必要があります。

本記事は、Webシステムの性能設計・性能試験・チューニング・運用をつなぐPerformance Engineeringの考え方を整理して解説します。

対象は、Webシステムの設計・構築・運用に関わるエンジニアです。特定製品の設定値ではなく、性能を何で測り、どう判断し、どの順番で調べるかを理解することを目指します。本文の数値・構成・試験結果は説明用の仮定例であり、そのまま適用できる推奨値ではありません。

Performance Engineeringとは

Performance Engineeringとは、必要な性能を定義し、その性能を設計・実装・試験・運用の各段階で作り込み、継続的に確認・改善する取り組みです。ここでいう性能には、応答の速さだけでなく、処理できる量、混雑時の振る舞い、必要なリソースとコストも含まれます。

Webシステムは複数のレイヤで構成される

Webシステムの性能は、アプリケーションだけで決まるものではありません。Web・AP・DBなどのミドルウェア、OS、仮想化基盤、ハードウェア、ネットワークといった複数のレイヤが関係します。

たとえば、利用者から見れば「画面が遅い」という1つの現象でも、原因はアプリケーションの処理、DBの待ち、OSのリソース不足、仮想化基盤の競合、ストレージI/O、ネットワーク遅延など、さまざまな場所に存在します。そのため、最初から特定のレイヤを原因と決めつけず、システム全体を俯瞰してから問題区間を絞ることが重要です。

「1回の処理を最速にすること」と「多くの利用者に安定した応答を返すこと」は、同じ課題とは限りません。少数のRequestでは速い構成でも、同時アクセスが増えるとLockやConnection Poolの待ちが発生することがあります。どのWorkloadに対して、何を達成するかが出発点になります。

性能設計・性能試験・チューニングの役割

活動答えるべき問い成果物の例
性能要件の定義どの操作を、どの負荷条件で、どれだけ速く・多く処理するか対象業務、Response Time、処理量、Error Rate、測定条件
性能設計要件を満たすために、どの構成と制御が必要かリソース見積もり、処理方式、並列度、Queue・Timeout設計
性能試験設計した構成が、想定条件で要件を満たすか試験シナリオ、計測結果、限界点、合否判定
チューニング何を変えると、どの指標が改善するかSQL・コード・設定・構成の変更と比較結果
運用・改善本番で要件を維持できているか監視、容量予測、障害分析、次の設計へのフィードバック

Performance Engineeringの改善サイクルは以下の通りです。

性能試験は最後に一度だけ実施するものではありません。設計段階で重い処理を小さく試し、実装後に組み合わせて確かめ、本番稼働後の実測を次の設計へ戻します。問題が起きてから設定を調整するだけでは、処理方式やデータ構造に原因がある場合の改善が難しくなります。

性能の良さは要件とコストで決まる

商品検索と月次集計では、求められる性能が異なります。商品検索は利用者を待たせないResponse Timeが重要ですが、月次集計では決められた時間内に処理を終えることが主な要件になる場合があります。大量のリソースを投入すれば速くなるとしても、運用費用に見合わなければ適切な設計とはいえません。

このため、性能改善では「何ms短縮したか」だけでなく、「要件を満たしたか」「ピーク時にも維持できるか」「1件あたりの処理コストはどう変わったか」まで評価します。

Performanceを測る基本指標

性能を議論するときは、最初に指標の意味と測定範囲をそろえます。同じ「応答時間」でも、ブラウザで測る時間と、アプリケーション内部で測る時間では含まれる処理が違います。

指標意味単位・例
Response Time(応答時間)定義した開始点から終了点までの経過時間ms、s
Throughput(処理量)単位時間あたりに完了した仕事の量requests/s、TPS(transactions/s)、MB/s
Arrival Rate(到着率)単位時間あたりに観測境界へ到着する仕事の量requests/s
Concurrency(同時処理数)ある時点で対象範囲に存在する未完了の仕事の数処理中と待機中を含めて40件など
Utilization(利用率)リソースが使われている割合CPU使用率、Connection使用数 / 上限
Saturation(飽和・混雑の度合い)リソース不足により仕事が待たされている度合いQueue長、待ち時間、待機スレッド数
Error Rate(エラー率)定義した対象Requestのうち失敗した割合失敗件数 / 対象件数 × 100%

Response TimeとLatencyは測定点と一緒に定義する

本記事では、Response Timeを「クライアントがRequestの送信を開始してから、Responseの受信を完了するまでの時間」として説明します。ただし、実際の性能要件では接続確立や名前解決を含むかも明示してください。画面の描画完了までを要件にする場合は、HTML取得後の追加通信やブラウザ内の処理も対象になります。

Latencyという語は、通信の片道遅延、往復時間、Request全体の所要時間など、文脈によって異なる意味で使われます。「Latency 100ms」とだけ記録せず、「Load Balancer受付から応答完了まで100ms」のように測定境界を添えます。TTFB(Time To First Byte)は最初のByteを受信するまでの指標であり、Response全体の受信完了時間とは区別します。

平均だけでなくPercentileを見る

平均Response Timeは全体の傾向を把握するのに役立ちますが、一部の遅いRequestが見えにくくなります。たとえば、100件中95件が100ms、残り5件が2,000msだった場合、平均は195msです。「平均200ms未満」という条件だけなら達成していますが、5件のRequestでは2秒待っています。

指標読み方確認すること
平均Response Timeの合計を件数で割った値全体的な時間の水準
p50おおむね50%のRequestが、この時間以下で完了中央付近の利用体験
p95おおむね95%のRequestが、この時間以下で完了遅い側の利用体験
p99おおむね99%のRequestが、この時間以下で完了さらに少数の遅いRequest
最大値観測期間で最も遅かった値最悪の事例。件数や観測期間の影響も受ける

Percentileの算出・補間方式やHistogramの粒度によって値には差が出ます。特に件数が少ない期間のp99は変動しやすいため、対象件数も確認します。また、複数サーバのp95を単純平均しても、全体のp95にはなりません。全体の分布を評価するには、生の計測値や集約可能なHistogramなどを使います。

成功Requestと失敗Requestも分けて見ます。即座にErrorを返すようになれば、平均応答時間だけは短くなることがあります。

RPS・TPS・同時ユーザー数を混同しない

RPSはRequests Per Second、TPSはTransactions Per Secondです。1回の注文操作が3つのHTTP Requestと複数のSQLで構成されるなら、注文TPS、HTTP RPS、SQL実行回数は別の値になります。性能要件には、何を1件と数えるかを記載します。

同時ユーザー数も、実行中のRequest数と一致しません。ログイン中の利用者には、画面を読んでいる人や入力している人も含まれます。次の操作までの待ち時間をThink Timeと呼びます。利用者数から負荷を見積もるときは、操作頻度や1操作あたりのRequest数も必要です。

Throughput・Response Time・Saturation

負荷を増やしたときは、Throughput、Response Time、Saturationを組み合わせて見ます。処理量が伸びているか、応答が悪化していないか、処理しきれない仕事がQueueに滞留していないかを同時に確認します。

負荷を増やしても処理量が増えなくなる点がある

余裕がある範囲では、到着Requestが増えると完了するRequestも増えます。しかし、CPU、I/O、Lock、Connection数などの制約に近づくと、待ちが増え、Throughputの伸びよりもResponse Timeの悪化が目立つようになります。さらに負荷が増えると、Timeoutや拒否が増えたり、処理効率が落ちたりします。

到着率正常完了Throughput成功Requestのp95待ちの状態
100 requests/s約100 requests/s120ms待ちは少ない
300 requests/s約300 requests/s180ms短い待ちが発生
450 requests/s約450 requests/s480ms待ちが目立ち始める
600 requests/s約470 requests/s1,800msQueue増加、Timeout・拒否が発生

上表は負荷段階ごとの概念例です。最後の行で到着率と正常完了数が一致しないのは、失敗・拒否・未完了のRequestが存在するためです。過負荷時には、正常完了、失敗完了、拒否、未完了残数を分けて記録し、差分がどこへ行ったのかを確認します。

負荷を増やしたときの典型的な変化を図にすると、以下のようになります。

重要なのは最大Throughputそのものではなく、Response TimeとError Rateの要件を守って運用できる範囲です。

最大スループットと運用できる処理量は違う

仮に500 requests/sを完了できても、そのときp95が3秒なら、p95 500ms以内という要件には適合しません。ここでは、Response TimeとError Rateの条件を守りながら継続して処理できる範囲を、運用できる処理量として考えます。

余裕の取り方は、負荷の変動、障害時の残存台数、スケールアウトにかかる時間、データ増加などによって変わります。「CPU使用率は必ず70%以下」といった一律の数値で決めず、性能試験で応答時間が悪化する条件を確かめ、必要な余裕を設計します。

リトルの法則(Little’s Law)で処理量・時間・同時数をつなぐ

安定した状態で、同じ測定境界の平均値を扱うとき、システム内の平均Request数、平均Throughput、平均滞在時間の関係をLittle’s Lawで整理できます。ここでいうシステム内のRequest数には、実際に処理しているものと待っているものの両方を含めます。

Little’s Lawの具体例を記載します。

この例から、応答の遅延がメモリやConnectionの使用量を増やす理由が分かります。ただし、平均40件という計算結果は「スレッド Poolを40に設定すればよい」という意味ではありません。スレッドを占有する時間、非同期処理、変動への余裕、下流の制約を別途考慮します。

Little’s Lawにp95を代入しても、同時処理数のp95は求まりません。また、Queueが増え続ける非定常な過負荷区間に、到着率と完了Requestの平均時間だけを当てはめるのも不適切です。入出の対象、失敗や中断の扱い、観測期間をそろえ、安定した区間の平均値で関係を確認します。

BottleneckとQueue

Bottleneckとは、そのWorkloadと構成において、システムの処理量や応答時間を制約している箇所です。CPUだけがBottleneckになるわけではありません。ストレージ I/O、ネットワーク、Lock、スレッド Pool、Connection Pool、外部APIなども制約になります。

Bottleneckは処理の流れの中で見つける

一例として、DB処理時間が長くなった際に、上流で待ちになる例を記載します。

たとえば、各RequestがDB Connectionを1回取得し、平均100ms保持するとします。Connectionが20本なら、Poolの保持時間だけから計算した目安は20 ÷ 0.1 = 200 requests/sです。ただし、これは各Connectionが繰り返し利用され、DB側がその並列処理を支えられると仮定した値です。

Connectionを40本に増やしても、DBのCPUやI/Oに余力がなければ400 requests/sにはなりません。競合が増えて保持時間が延びることもあります。Poolの上限に達した事実だけで設定不足と判断せず、なぜConnectionが長く保持されているのかを調べます。

Response Timeには処理時間と待ち時間がある

単純な1つの処理拠点では、Response Timeを「Service Time(処理に必要な時間)」と「Queueでの待ち時間」に分けて考えられます。なお、どこを処理拠点とするかで分け方は変わります。アプリケーションから見たDB呼び出し時間には、DB内部の待ち時間も含まれます。

同じ処理でもQueueによって応答が変わる例を記載します。

M/M/1モデル

待ち行列の性質を示す単純なモデルにM/M/1があります。到着がPoisson過程、処理時間が指数分布、処理窓口が1つ、待機場所に上限がないなどの仮定を置き、利用率ρが1未満の定常状態を考えます。このモデルでは、平均Service TimeをSとすると、平均Response TimeはR = S / (1 − ρ)になります。

この計算は実システムの性能予測値ではなく、処理能力の限界に近づくほど待ち時間が急に増え得ることを理解するための例です。実際のWebシステムには複数の処理窓口、キャッシュ、Lock、Batch、Request種別ごとのばらつきがあります。特定のCPU使用率から応答時間をこの式だけで算出することはできません。

Queueは一時的な変動を吸収するが、処理能力は増やさない

Queueは短いBurstを吸収できますが、処理能力そのものは増やしません。毎秒600件が到着し、500件しか完了しない状態が続けば、途中離脱がない限り10秒で1,000件の未完了Requestが増えます。

Queue上限を大きくすることは、待てる量を増やすことであり、処理能力を増やすことではありません。利用者がTimeoutした後も処理が残ると、不要になった仕事がResourceを占有します。Queue長だけでなく待ち時間を測り、受付上限、Timeout、Cancelの伝播、重要な処理の優先度を設計します。

過負荷時には、すべてを長時間待たせるより、処理できないRequestを早期に拒否する方が、受け付けた仕事の品質を守れる場合があります。

Utilization・Saturation・Error

利用者側のResponse Time、Throughput、Error Rateを確認したら、内部のリソースへ掘り下げます。その整理に役立つのが、Brendan Gregg氏のUSE Methodです。各リソースについてUtilization、Saturation、Errorsを確認します。

Utilization:どれだけ使われているか

CPUでは、観測期間のうち処理に使われた時間の割合が代表例です。Connection Poolなら、上限に対して何本使っているかを確認できます。ただし、メモリの占有率とCPUの稼働時間率は性質が違うため、同じ「使用率」として一律に判定しません。

Saturation:どれだけ待たされているか

リソースを利用したい仕事がすぐに実行できず、待っている状態を確認します。CPUなら実行可能なのにCPUを得られないTask、Poolなら空きConnectionを待つRequestが例です。待ちの数と待ち時間を合わせると、利用率だけでは判断しにくい混雑を捉えられます。

Error:失敗は起きているか

I/O Error、送信Drop、Connection取得失敗など、リソースやその周辺で発生する失敗を確認します。ここでいうResourceのErrorと、利用者が受け取るHTTP Errorは別の観測です。内部で再試行してHTTPは成功する場合もあるため、両方を関連付けて見ます。

対象Utilizationの観測例Saturationの観測例Errorの観測例
CPUCore別の実行時間率実行待ち、Scheduling遅延Hardware Errorなど。取得可能性は環境次第
メモリ使用量、利用可能量、上限Reclaimの停滞、メモリ Pressure割当失敗、OOMによる終了
ストレージ / I/OIOPS・帯域とDevice稼働状況未完了I/O数、Queue待ちI/O Error、Device Timeout
ネットワーク送受信帯域とLink Capacity送信Queue、Bufferの滞留Drop、Interface Error
スレッド / Connection Pool使用数 / 上限取得待ち数、取得待ち時間取得Timeout、受付拒否

上表は本章のWebシステム向けの観測例です。すべてのリソースで、3種類の指標を同じ形で取得できるとは限りません。指標がない場合も「異常なし」とせず、「未計測」として次の確認方法を決めます。

CPUが低くても、システムは遅くなる

CPU使用率が30%でも、全RequestがDBのLock解除を待っていれば応答は遅くなります。また、8 Coreのうち1 Coreだけが忙しい場合、全体の平均使用率では局所的な制約が見えにくくなります。平均値に加えて、Core別・プロセス別・スレッド別の偏りを確認します。

LinuxのLoad AverageもCPU使用率そのものではありません。実行可能なTaskに加え、割り込み不能な待ち状態のTaskなどが関係するため、Load Averageが高いという情報だけでCPU増強を決めることはできません。CPU・I/O・待ちの状況を合わせて解釈します。

メモリやストレージも使用率だけでは判断しない

Linuxでは空きメモリがキャッシュとして利用されるため、「使用量が大きい」だけでメモリ不足とは限りません。利用可能量、Swapの出入り、Reclaimやメモリ Pressure、アプリケーションの応答を確認します。ストレージも、並列処理できるDeviceでは稼働時間率だけで限界を判断できません。I/Oサイズ、IOPS、帯域、Latencyを合わせて見ます。

大切なのは、「高い値を探す」ことから一歩進み、どのリソースの制約が、どの待ちを作り、その待ちが利用者の応答に影響しているかを確かめることです。

WebシステムをEnd-to-Endで考える

利用者が経験するResponse Timeは、複数Layerの処理・通信・待ち時間の合計です。Web サーバのAccess Logだけではクライアント側の通信やブラウザ処理は分からず、アプリケーションの実行時間だけでは受付前のQueueが見えない場合があります。

WebシステムのEnd-to-Endの経路例を記載します。

時間を足す前に、重複と並列処理を確認する

説明のため、あるRequestの300msを、重ならない区間として分解したとします。クライアントとの通信・接続に40ms、入口での待ちに20ms、アプリケーション固有の処理に60ms、DB Connection取得待ちに80ms、DB呼び出しに100msかかったなら、合計は300msです。

区間時間次に確認する候補
クライアントとの通信・接続40msRTT、接続再利用、データ量
入口での待ち20ms受付Queue、Workerの空き
アプリケーション固有の処理60msCPU処理、Object生成、内部Lock
DB Connection取得待ち80msPool使用数、保持時間、待機数
DB呼び出し100msSQL、DB内部の待ち、DBとの通信

ただし、実際のAccess Logやトレースの時間には包含関係があります。アプリケーションの所要時間240msがDB待ちとDB呼び出しを含んでいるなら、そこへ80msと100msをもう一度加えてはいけません。

また、外部APIへの100msと150msの呼び出しを同時に開始して両方の完了を待つなら、その並列部分は単純な250msではなく、おおむね遅い方の150msと制御のオーバーヘッドになります。利用者の完了時刻を決める依存経路をCritical Pathと呼び、その経路にある待ちや処理を優先して調べます。

メトリクス・ログ・トレースをつなげる

情報分かること利用例
メトリクス量・時間・使用率などの時系列変化p95が悪化した時刻とPool待機数の増加を比較
ログ個々のイベントや処理結果Request ID、Error理由、遅いSQLを確認
トレース1つのRequestが通った処理と所要時間アプリケーションからDB・外部APIへの呼び出しを追跡

分散トレースでは、処理単位をSpanとして記録し、Contextを伝播して複数プロセスの処理を関連付けます。トレースは依存関係を追うのに役立ちますが、未計測の区間やSamplingで保存されなかったRequestもあるため、メトリクスやログと併用します。

時刻、タイムゾーン、Request ID / トレース ID、対象サーバをそろえると、別々の情報を同じ出来事として追いやすくなります。各サーバの時計がずれていると、別Host間の時刻比較を誤ることがあるため、時刻同期も確認します。

一部の高速化が全体に効く割合を考える

300msのRequestのうち、ある処理が30msだけを占めているとします。その処理を2倍速にして15msにできても、他の条件が同じなら全体は285msで、短縮は5%です。一方、180msを占める待ちを減らせれば、大きな改善につながる可能性があります。

これは、小さな処理の最適化が無意味ということではありません。実行回数やCPU コストには効く場合があります。ただし、利用者のResponse Timeを改善する目的なら、Critical Pathの中で時間を占める箇所から検証すると判断しやすくなります。

Performance Requirementと性能試験

性能試験で最初に決めるのはツールではなく、何をもって合格とするかです。「速いこと」「1,000人が使えること」だけでは、Workloadも測定条件も合否判定も定まりません。

性能要件を測定可能な条件にする

例として、商品検索APIに次の要件を設定します。数値は説明用であり、実際には業務の許容時間、利用実態、コストから決めます。

項目要件・前提の例
対象操作商品検索API。検索結果の内容も正しいこと
Workload軽い条件70%、絞り込み条件20%、重い条件10%。検索語と返却件数の分布も固定
到着負荷平均300 requests/sを30分継続。発行間隔の分布も試験設定に記録
データ条件商品1,000万件。本番相当の偏りとIndex構成
測定境界負荷生成クライアントのRequest送信開始からResponse全体の受信完了まで
接続条件接続を再利用。DNSと初回接続確立はこの応答時間の集計外とし別途計測
Response Time成功Requestのp95が500ms以下、p99が1,000ms以下
Error Rate送信した有効な試験Requestのうち、定義した失敗が0.1%以下
失敗の定義HTTP 5xx・429、クライアント側の2秒Timeout、通信失敗、内容不正。想定外の4xxも失敗
評価区間ウォームアップ後の30分全体、および連続する各5分区間で判定
継続性未完了RequestとQueueが増え続けない。終了後の残存処理も記録
構成台数、CPU・メモリ上限、ミドルウェア設定、バージョン、ネットワーク条件を固定・記録

成功RequestだけでResponse Timeを評価する場合も、失敗を除外した分は必ずError Rateに反映します。TimeoutしたRequestを集計から消してしまうと、遅い処理ほど評価に出てこなくなります。試験終了直前に送信したRequestも、定義した期限まで結果を確定させて判定します。

この例で30分間に540,000件を送信できたなら、Error Rate 0.1%の条件は、その対象全体で失敗540件以下に相当します。ただし、5分区間ごとの条件も同時に満たす必要があります。短時間の障害を長い集計期間で薄めないためです。

Workloadは件数だけでなく中身を再現する

同じ300 requests/sでも、キャッシュヒットする商品詳細と、広い範囲を集計する検索では負荷が異なります。APIの比率、データ量、検索条件の偏り、Responseサイズ、Read / Writeの比率、キャッシュの状態、同時に動くBatchなどをそろえます。

いつも同じIDだけを検索すると、キャッシュが効きすぎて本番より軽くなることがあります。逆に、全件がキャッシュミスになる試験だけでは通常運用を代表できません。通常時、Cold Start、キャッシュ更新直後などを分け、何を再現した結果なのかを明示します。

試験の種類を目的で使い分ける

試験目的確認すること
Baseline Test低負荷時の基準を作る基本の応答時間、単体処理の重さ
Load Test想定する通常・ピーク負荷を確認する要件達成、リソース使用状況
Stress Test想定負荷を超えた振る舞いを確認する限界点、拒否、Timeout、負荷低下後の回復
Spike Test急な負荷変化を確認するQueue増加、スケールアウトの追従、初期化の影響
Soak Test長時間運転時の変化を確認するメモリ増加、Connection Leak、定期処理の影響
縮退・復旧時の試験台数減少や依存先の遅延から回復できるか確認する残存サーバの処理能力、再接続集中、回復時間

試験時間は目的から決めます。30分のLoad Testで性能要件を満たしても、数時間ごとのBatchや緩やかなメモリ Leakを確認できたことにはなりません。また、障害時に2台で支える設計なら、3台稼働時の結果だけで判定せず、縮退構成でも確認します。

Open ModelとClosed Modelを区別する

Closed Modelでは、一定数の仮想ユーザーが処理を終えてから次の処理へ進みます。Response Timeが長くなると、同じユーザー数でも単位時間あたりの発行数が減ります。一定の利用者集団の行動を表すのに適しますが、外部から到着し続ける負荷の試験とは条件が違います。

Open Modelでは、前のRequestの完了とは独立して到着を発生させます。継続して入ってくる負荷を評価する場合に使います。ツールがIteration単位で到着率を制御する場合、1 Iteration内のRequest数が複数なら、設定値はHTTP RPSそのものではありません。

遅延によって負荷生成側まで発行を控えると、意図した到着負荷の下で発生する待ちを過小評価することがあります。

Open ModelとClosed Modelでは、遅延時の発行負荷が異なる点を図で整理します。

たとえば、100ユーザーが1件ずつ順にRequestを送り、平均Response Timeが0.2秒、Think Timeが0.8秒なら、安定時の目安は100 ÷ (0.2 + 0.8) = 100 requests/sです。応答が1.2秒へ悪化すると100 ÷ (1.2 + 0.8) = 50 requests/sになります。この試験結果を、100 requests/sが外部から到着し続ける場合と同じと考えてはいけません。

負荷生成側がBottleneckになっていないか確認する

Open Modelを選んでも、負荷生成クライアントに十分なCPUやConnectionがなければ、設定したRateを発行できません。実発行数、発行できなかったIteration、クライアント側Error、CPU、ネットワーク使用状況を記録します。サーバに届かなかった負荷を、サーバが処理したかのように評価しないことが重要です。

結果は変更前後を同じ条件で比較する

  1. 構成・コード・設定・データ条件を記録し、低負荷でBaselineを測る。
  2. ウォームアップを行い、定義した評価区間で負荷を段階的に増やす。
  3. 到着率、正常完了数、応答分布、Error、Queue、リソースを同時に記録する。
  4. 要件を外れた地点について仮説を立て、原則として主要な変更を1つずつ加える。
  5. 同じ条件で再試験し、改善と副作用、再現性を確認する。

平均Response Timeだけを比較するのではなく、p95・p99、Error Rate、Throughput、コストも確認します。性能のばらつきが大きい場合は複数回測定し、小さな差を確実な改善と判断しないようにします。

Performance Troubleshootingの基本フロー

調査を始めるときは、「誰に、いつから、どの操作で、どの程度の影響があるか」を確認します。障害対応では利用者影響の軽減を優先し、原因分析と並行して進めます。

Performance Troubleshootingに唯一の手順があるわけではありません。本記事では、Brendan Gregg氏のメソッドやGoogle SREの考え方を参考に、Webシステム向けの基本フローとして整理します。

Step 1:症状と影響範囲を確認する

最初に、「何が、いつから、どの範囲で問題になっているのか」を明確にします。全APIか特定APIか、特定の検索条件か、すべてのRequestか一部だけか、特定地域やサーバに偏っているかを確認します。

「遅い」「重い」という申告を、発生時刻、対象機能・API、Response Time、Error Rate、Throughput、影響Request・Userの範囲など、観測可能な情報へ置き換えます。原因を決めつけず、まず何が正常ではないのかを具体化します。

Step 2:時系列・平常時・変更履歴を比較する

問題発生前後のメトリクスやログを時系列で比較し、平常時との違いを確認します。同時に、トラフィック増加、デプロイ、設定変更、データ増加、Batch開始、依存先の遅延など、問題発生前後に何が変化したかを確認します。

たとえば、Response Timeの悪化と同時にCPU使用率が上昇していても、それだけではCPUが原因とは限りません。Request増加によってCPU使用率とResponse Timeの両方が上昇している可能性もあります。相関だけで原因を決めず、平常時との差から調査対象を絞ります。

Step 3:End-to-Endで問題区間を絞る

Webシステムでは、クライアントからデータベースまで複数のLayerを経由してRequestが処理されます。最初からデータベースやCPUなど特定のComponentを疑うのではなく、トレース、メトリクス、ログなどを使って、どの区間で時間を使っているのかを確認します。

クライアント側だけで遅ければネットワークやクライアント側処理を確認します。アプリケーションからデータベースへの呼び出しが長ければ、データベースとの通信とデータベース内部の処理へ調査範囲を絞ります。一方、データベース呼び出し前に時間を使っている場合は、Connection取得待ちやスレッド Pool、アプリケーション内部のQueueなどが候補になります。

Step 4:Resource・Queue・待ちを確認する

問題区間を絞ったら、その区間に関係するリソースとQueue、待ち時間を確認します。CPU、メモリ、ストレージ、ネットワークなどではUtilizationだけでなくSaturationやErrorも確認し、アプリケーションやデータベースではスレッド Pool、Connection Pool、Lock、I/O Wait、下流Serviceの応答待ちなども対象にします。

見えている現象仮説の例次に確認する情報
CPUが高く、Throughputが頭打ちCPU処理能力が限界に近いCore別使用率、実行待ち、プロファイル
CPUは低いがアプリケーションが遅いI/O・Lock・下流応答を待っているトレース、スレッド状態、各種待ち時間
Poolが上限に達し取得待ちが増加Connection保持時間増大、Leak、処理能力不足取得待ち時間、保持時間、未返却、下流の処理能力
p99だけが悪化一部の重い処理や一時停止が存在遅いRequest、GC停止、キャッシュミス、Lock
HTTP成功率は高いが内部負荷が増加リトライによって処理量が増幅リトライ回数、Timeout、依存先Error

すべてのサーバを同じ順番で網羅するのではなく、Step 3で特定した問題区間に関係するプロセス、スレッド、Pool、リソースを優先して観測します。重要なのは、Utilizationが高いResourceを探すことではなく、利用者が経験している遅延やErrorを実際のQueueや待ちと対応付けることです。

Step 5:仮説を立て、観測で検証する

Step 4までの情報から原因仮説を立て、その仮説を支持または否定できる観測を行います。たとえば「データベース処理の遅延でConnection保持時間が増えている」と考えるなら、SQL実行時間、データベースのWait イベント、アプリケーションから見たデータベース呼び出し時間、Connection保持時間を確認します。

必要に応じてプロファイルや詳細なトレースへ進みます。大量のメトリクスやログを集めること自体ではなく、次の判断に必要な証拠を得ることが目的です。

Step 6:対策を実施し、同条件で再計測する

原因に対する対策を実施したら、可能な限り同じWorkload・同じ観測指標で再計測します。対策が成功したかどうかは、設定変更が正常に完了したことではなく、利用者側のResponse Time、Error Rate、Throughputが回復したことで判断します。

CPU使用率が下がっていても、Request自体を処理できなくなったために負荷が減った可能性があります。そのため、入口のRequest数、正常に完了した処理量、Response Time、Error Rateと、内部のリソース・Queue・待ちを合わせて確認します。

Step 7:原因・限界・監視項目を設計へ戻す

問題が解消したら、調査を終了するだけではなく、得られた知見を次の設計へフィードバックします。根本原因と発生条件、システムの処理能力や限界、有効だった対策と副作用、再現・再試験条件、今後監視すべきメトリクスを残します。

今回の例なら、データ量の増加を性能試験の条件へ反映し、SQL処理時間やConnection取得待ちを監視対象へ追加します。また、どの程度Dataが増えると性能が悪化するのかが分かったのであれば、その値を将来のリソース計画や将来の性能試験にも反映します。

個別の障害を解決するだけでなく、同じ条件で再発しにくい設計と観測方法へ変えていくことが、Performance Engineeringの継続的な改善です。

影響が大きい場合は原因調査と並行して緩和策を実施する

本番影響が大きい場合は、すべてのStepを終えてから対策する必要はありません。既知の安定バージョンへのロールバック、重い処理の一時停止、Rate Limit、トラフィック制御、スケールアウトなど、状況に応じた緩和策を原因調査と並行して実施します。

その際も、可能な範囲でメトリクス、ログ、トレースなどの調査に必要な情報を保存します。「復旧を優先すること」と「原因を特定すること」を分けて考えることで、利用者への影響を抑えながら根本原因の調査を続けられます。

まとめ

Webシステムのパフォーマンス設計は、設定値を調整することからではなく、Workloadと性能要件を定義し、Requestの流れ・処理時間・Queue・待ちをEnd-to-Endで捉えることから始まります。

  1. 性能はWorkloadと要件から考える:Response Time、Throughput、Error Rateの測定境界と合否条件を明確にする。
  2. 最大値より運用可能なCapacityを見る:負荷を増やしたときのResponse Time、成功率、Queueの変化を確認する。
  3. Bottleneckは使用率だけで判断しない:どのリソースの制約が、どの待ちを生み、利用者の応答へ影響しているかを確認する。
  4. 性能試験では本番のWorkloadを再現する:Data、負荷モデル、キャッシュ、構成、評価区間をそろえ、変更前後を同じ条件で比較する。
  5. 障害時はEnd-to-Endで区間を絞る:症状と変更点を確認し、仮説を観測で検証し、対策後は同じ指標で回復を確認する。

性能改善で重要なのは「高い値を探すこと」ではありません。どこで仕事が滞留し、なぜ待ちが生まれ、その待ちが利用者のResponse TimeやErrorにどうつながっているかを確認することです。性能試験や障害解析で得た知見を次の設計と監視へ戻すことで、継続的なPerformance Engineeringにつながります。

参考資料

本文の概念をさらに確認したい場合は、以下の一次資料を参照してください。本文の図表・数値例は、本記事の説明用に作成しています。

コメント