【図解】Webシステムの過負荷対策・性能設計|流量制限・Queue・Retry・縮退運転

はじめに

Webシステムでは、CPUやMemoryに余裕があっても、Databaseや外部APIの遅延をきっかけに過負荷が発生することがあります。応答が遅くなると処理中のRequestが滞留し、ThreadやConnectionを占有します。さらに利用者や前段システムがRetryすると、流入量が増えて障害が拡大する場合があります。

過負荷対策の目的は、すべてのRequestを無条件に受け入れることではありません。処理能力を超える負荷から重要な機能を守り、応答時間とError率を制御し、負荷が下がった際に回復できるようにすることです。

本記事では、CDN、Load Balancer、Apache / Nginx、Tomcat、Databaseで構成されるWebシステムを想定し、流量制限、Queue、Timeout、Retry、サーキットブレーカー、負荷遮断、縮退運転の設計を説明します。数値は説明用の例であり、実際の閾値は性能試験で決定します。

過負荷が発生する仕組み

過負荷は、Requestの流入量が増えたときだけでなく、1件あたりの処理時間が長くなったときにも発生します。たとえばDatabaseの応答時間が100msから500msへ増えると、同じRPS(Requests Per Second)でも処理中のRequestが増えます。

定常状態における平均同時処理数は、Little’s Law※を用いて概算できます。

平均同時処理数 ≒ Throughput × 平均処理時間

500 Request/秒 × 0.1秒 = 50件
500 Request/秒 × 0.5秒 = 250件

処理能力が500 RPSのシステムへ700 RPSが継続的に流入すれば、受け付けた超過分は待機または失敗します。Queueを大きくしても処理能力は増えません。過負荷時には、どこで待たせ、どこで拒否するかを決めておく必要があります。

※Little’s Law(リトルの法則)
システム内の平均Request数、Throughput、平均滞在時間の関係を表す法則です。
定常状態では、次の関係が成り立ちます。

L = λ × W

L:システム内の平均Request数
λ:Throughput(Request/秒)
W:平均滞在時間(秒)

過負荷対策の基本方針

対策は、瞬間的なアクセス集中と、継続的な高負荷を分けて考えます。短いバーストは容量を限定したQueueやトークンバケットで吸収できる場合があります。一方、流入量が処理能力を超え続ける場合は、流量制限や負荷遮断によって受付量そのものを減らします。

負荷の種類主な現象設計の方向
バースト短時間にRequestが集中する前段で流量制限し、許容範囲の短い待機を設ける
継続的な高負荷Queueや処理中Requestが増え続ける受付量を制限し、早期に拒否する
Backend遅延流入量が同じでも同時処理数が増えるTimeout、同時実行数制限、サーキットブレーカー
Node障害残存Nodeに負荷が集中する縮退時の処理能力に合わせて流量を制御する

Webシステムでは、前段の処理能力が後段より大きいことが珍しくありません。したがって、入口での制限に加え、Databaseや外部APIの直前でも同時実行数を制御する多層的な設計が必要です。

流量制限の設計

流量制限は、一定期間に受け付けるRequest数を制限する仕組みです。システム全体の保護だけでなく、利用者単位、API単位、認証状態単位などの公平性を確保する目的でも使用します。

制限単位を決める

単位用途注意点
全体RPSシステム全体の受付量を制限する複数Node間での集計方法を決める
Client / API Key特定利用者による占有を防ぐNAT配下の利用者をIPだけで一括制限しない
URI / API負荷の高い機能を個別に保護する同じRPSでもAPIごとにDB負荷が異なる
同時実行数重い処理の並列度を抑えるRPS制限とは別に設計する

トークンバケットとバースト

トークンバケットは、一定の速度で補充されるTokenを使い、Requestの受付量を制御する流量制限の仕組みです。

Bucket(バケツ)には、あらかじめ決められた上限までTokenを蓄積できます。Requestを受け付ける際にTokenを消費し、Tokenが残っていれば処理を許可します。Tokenが不足した場合は、設定に応じてRequestを待機させるか、拒否します。

Token補充速度:100 Token/秒
Bucket容量:200 Token
Request:原則1件につき1 Token

平常時:Tokenが補充される
バースト:蓄積したTokenを使用する
超過時:待機または拒否する

この例は動作を理解するためのもので、200 RPSを継続して処理できることを意味しません。許容するバースト量は、後段のQueue、Memory、同時実行数、応答時間を考慮して決めます。

各レイヤーの流量制御

過負荷を防ぐには、入口で大量のRequestや不正アクセスを抑え、後段に進むほど同時接続数・同時実行数を制御することが重要です。以下の図では、ClientからDatabaseまでの6層について、それぞれが保護するResourceを整理しています。

図のポイントは、前段で不要な流入を減らし、後段で処理能力を超える同時実行を防ぐことです。各レイヤーの上限を個別に設定するだけでなく、後段の処理能力に合わせて全体の受付量を設計します。

CDN / WAF:流量制限とBot対策

CDN / WAFは、Webシステムの入口で大量のRequestや不正アクセスを抑制します。IPアドレス、URI、認証情報など、利用する製品が対応する条件に応じて流量制限を設定し、不要なRequestが後段へ到達する前に遮断します。Bot対策と正規利用者の流量制御は目的が異なるため、制限条件を分けて設計します。

特に、NAT配下に複数の利用者がいる場合は、IP単位の制限だけで正規利用者を一括して遮断しないよう注意します。CDN / WAFの流量制限は製品・契約プランによって集計単位や超過時の動作が異なるため、導入環境の仕様を確認します。

Load Balancer:接続数・流量の制御と負荷分散

Load Balancerは、健全なWeb Serverへ通信を分散し、必要に応じて同時接続数や流入量を制御します。Health Checkによって異常なNodeを分散対象から外すことも重要です。ただし、単に負荷を分散しても、残存NodeやDatabaseの合計処理能力を超える流入は吸収できません。

L4で接続を拒否した場合、ClientにはHTTPエラーではなく接続失敗やTimeoutとして見えることがあります。429や503などのHTTP応答で制限理由を伝えたい場合は、対応するL7機能やWeb Server側の制御と組み合わせます。

Apache / Nginx:Request数・同時接続数の制限

Apache / Nginxでは、Web Requestの受付量と同時接続数を制御し、Tomcatへ無制限にRequestを流さないようにします。Nginxではlimit_reqでRequest Rateを、limit_connで指定したKeyに対する同時接続数を制限できます。worker_connectionsはWorkerが扱うConnectionの上限に関係しますが、Reverse ProxyではClient側とBackend側の接続をともに考慮します。

ApacheではMPMとMaxRequestWorkersが同時処理能力に関係します。ただし、Worker数の上限は利用者単位の流量制限とは異なります。Request Rate、接続数、Worker数を区別して設定します。Apache / Nginxの内部設計はChapter 6を参照してください。

Tomcat:同時Request数・待機時間の制御

Tomcatでは、処理に使用するThread数とConnection数に上限を設けます。maxThreadsは通常のThreadベースのConnectorで同時にRequestを処理するThread数に関係し、maxConnectionsはConnectorが受け付けるConnection数の上限です。acceptCountは、接続受付が上限に達した際のOS側の接続待ちに関係する値であり、HTTP Requestの待機件数そのものではありません。

Databaseの応答が遅れると、DB応答待ちのTomcat Threadが増え、後続Requestの待機時間も長くなります。Thread数を増やす前に、DB Connection Poolの取得待ち、SQL実行時間、Request全体のTimeoutを合わせて確認します。実際の同時実行数はConnector方式や非同期処理の有無によっても変わります。

Database:Connection Pool・SQL同時実行数の制御

Databaseへの流入は、主にTomcat上のApplicationが使用するDB Connection Poolで制御します。Poolの上限を増やすと接続取得待ちが減る場合がありますが、Database側のCPU・Storage I/O・Lock競合が増えれば、かえってSQLの実行時間が長くなる可能性があります。Connection Poolの上限と、実際に同時実行されるSQL数は必ずしも一致しない点にも注意します。

全Tomcat NodeのPool上限にBatchや運用接続を加え、Databaseが許容できる接続数と処理能力を確認します。Pool取得待ち時間、実行中SQL数、CPU・I/O・Lock待ちを監視し、性能試験によって適切な同時実行数を決定します。

Queueとバックプレッシャーの設計

Queueは短時間の流入変動を吸収できますが、長く待たせるほど利用者の応答時間は悪化します。過負荷時にQueueが増え続けないよう、長さと待機時間の両方に上限を設けます。

流入:700 Request/秒
処理:500 Request/秒
差分:200 Request/秒

10秒間受け付け続けると、単純計算で2,000件が滞留

バックプレッシャーは、後段の処理能力に応じて前段の流入を抑える考え方です。同期HTTPでは、後段のQueueを無制限に増やすのではなく、前段へ早めに失敗を返す、受付枠を減らす、非同期処理へ切り替えるなどの方法を組み合わせます。

待機場所主なResource設計で確認する点
Web Server接続、Worker、Buffer前段の待機とTimeout
TomcatThread、接続、Task Queue同時処理数とQueue上限
DB Connection Pool利用可能なConnection取得待ちの件数と時間
DatabaseCPU、I/O、Lock同時SQLとTransactionの競合

複数レイヤーに大きなQueueを設けると、待機時間の総和を把握しにくくなります。どこで待機させるかを明確にし、Request全体の期限と整合させます。

Timeoutの設計

Timeoutは、応答しない処理によってThreadやConnectionが長時間占有されることを防ぎます。接続確立、接続取得、SQL実行、外部APIの読み取り、Request全体など、何を待つ時間なのかを分けて設計します。

Timeout対象設計の観点
Connect Timeout通信先との接続確立接続障害を早期に検知する
Read / Response Timeout通信先からの応答遅延時に待ち続けない
DB Pool取得Timeout空きConnectionの取得Threadの長時間滞留を防ぐ
SQL TimeoutSQLの実行Database側の長時間処理を抑える
Request全体のDeadline利用者への最終応答下位処理とRetryを含めた時間を制御する

Timeoutの整合

各レイヤーのTimeoutを独立に長く設定すると、利用者が待てる時間を超えてBackend処理が続く場合があります。全体のDeadlineから残り時間を計算し、後段の呼び出しに割り当てる考え方が必要です。

上記は直列処理を単純化した設計例です。実際にはネットワークの遅延、複数回のSQL実行、並列処理、Retryなども考慮します。Timeoutした呼び出し先で処理が自動的に中断されるとは限らないため、キャンセル、Transactionの終了、Connectionの返却まで確認します。

Retryの設計

Retryは一時的な障害からの回復に有効ですが、過負荷時にはRequestを増幅します。どのエラーを再試行するか、何回まで実行するか、どのレイヤーで実行するかを明確にします。

対象Retryの考え方
一時的な接続障害再試行可能な条件と回数を限定する
429 Too Many RequestsRetry-Afterがあれば考慮し、負荷を増やさない
503 Service Unavailable一時障害か確認し、Backoffを設ける
業務エラー・認証エラー通常は同じRequestを繰り返さない
送金などの更新処理Idempotency Keyや処理結果照会で二重実行を防ぐ

Exponential BackoffとJitter

Retryを一定間隔で繰り返すと、多数のClientが同じタイミングで再接続し、回復中のシステムに再び負荷をかける場合があります。Exponential Backoffで間隔を広げ、JitterでRetry時刻を分散させます。

Retry回数と総待機時間はRequest全体のDeadline内に収めます。接続先が過負荷を示している場合は、再試行よりも受付抑制を優先する設計も必要です。

Retry Stormを防ぐ

Web Server、Tomcat、Application、外部API ClientがそれぞれRetryすると、1件の利用者Requestから多数のBackend呼び出しが発生することがあります。

利用者Request:1件
  ↓
中間サービスで3回試行
  ↓
後段サービスでも各3回試行
  ↓
Backend呼び出し:最大9回

Retryするレイヤーを絞り、再試行の総予算を設けます。また、更新系APIでは、Timeout後に元の処理が成功している可能性を考慮し、Idempotency Keyや業務IDによる重複排除、結果照会を設計します。

サーキットブレーカーの設計

サーキットブレーカーは、接続先の失敗やTimeoutが増えた際に、新たな呼び出しを一時的に抑制する仕組みです。障害中のBackendを繰り返し呼び出さず、ApplicationのThreadやConnectionを保護します。

サーキットブレーカーは単独でシステムの流入量を減らす仕組みではありません。流量制限やTimeoutと組み合わせ、回復確認の試行が集中しないようにします。

設定項目設計の観点
失敗の判定Timeout、接続失敗、対象とするHTTP Status
評価期間少数の偶発的な失敗で過敏に開かない
OPENの継続時間Backendの回復時間と整合する
HALF_OPENの試行数回復確認自体で過負荷を起こさない
Fallback利用者へ返すエラーや代替応答を決める

負荷遮断(Load Shedding)の設計

負荷遮断(Load Shedding)は、処理能力を超えたRequestを意図的に拒否し、受け付けたRequestの応答時間や重要機能を守る仕組みです。長いQueueに入れてからTimeoutさせるより、入口で早期に拒否する方がResourceを節約できる場合があります。

HTTP APIでは、流量制限超過に429、サービスの一時的な処理不能に503を返す設計が考えられます。ただし、どのStatusを返すかは拒否理由とAPI契約に合わせて決めます。Retry可能な場合はRetry-Afterなどを利用し、Clientが一斉に再試行しないようにします。

CPU使用率だけを閾値にすると、Databaseや外部APIの遅延による過負荷を見落とす場合があります。処理中Request数、Queue長、p95 / p99応答時間、Error率、Backend待機時間などを組み合わせます。

縮退運転の設計

縮退運転は、過負荷や部分障害が発生した際に、重要度の低い機能を制限し、主要な業務機能を維持する考え方です。すべての機能を同じ優先度で扱うと、重要な取引まで停止する場合があります。

機能過負荷時の対応例
主要な取引・更新処理優先的に処理能力を確保する
推薦・ランキング表示一時停止またはCache結果を返す
検索・集計件数制限、簡易表示、非同期化を検討する
通知・分析処理Queueへ退避し、後から処理する

機能の停止や代替応答は、業務上の整合性と利用者への影響を確認して決めます。特に金融取引などの更新処理では、結果が確定していないのに成功や失敗を断定しない設計が必要です。

障害・縮退時の過負荷対策

Nodeが停止すると、残存Nodeに負荷が集中します。通常時に各Nodeが余裕を持って動作していても、1台停止した瞬間に処理能力を超える可能性があります。

この例は均等分散を仮定しています。実際にはSessionの偏り、Cache Miss、Connection再確立、Retryなどによって負荷が変動します。縮退時に処理できるRPSを別途測定し、その範囲に合わせて入口の流量制限を切り替える設計が有効です。

復旧時の流入制御

復旧したNodeへ大量のRequestを一度に戻すと、Applicationの初期化、CacheのWarm-up、DB Connectionの作成が集中します。Load Balancerの段階的な復帰、接続数の緩やかな増加、Retryの分散などを検討します。

Databaseや外部APIが障害から復旧した直後も同様です。サーキットブレーカーのHALF_OPEN試行数を限定し、処理能力の回復を確認しながら流入量を戻します。

過負荷対策の性能検証

設計した制御が期待どおりに動くかは、通常時の最大RPSだけでは確認できません。バースト、継続負荷、Backend遅延、Node停止、復旧まで含めて検証します。試験の実施方法はChapter 11で詳しく扱い、ここでは過負荷対策に必要な観点を整理します。

試験確認すること
段階的な負荷増加処理能力の限界、流量制限の発動条件
バースト負荷Bucket・Queueの動作、応答時間の悪化
継続的な過負荷Queueが増え続けず、早期拒否できるか
Database遅延Thread滞留、Timeout、サーキットブレーカー
Retry集中Retry Stormを発生させないか
Node停止残存Nodeの負荷、縮退時流量制限
Node復旧段階的な流入再開、回復時間

監視すべき指標

分類主な指標
入口RPS、429 / 503件数、制限発動回数
応答平均、p95、p99、Timeout率
Queue待機件数、待機時間、拒否件数
Tomcat使用中Thread、Connection数、DB Pool待機
Database実行中SQL、CPU、I/O、Lock待ち
保護機能Retry回数、サーキットブレーカー状態、負荷遮断件数

流量制限によって成功Request数が減っても、重要な業務が目標応答時間を維持できているかを評価します。拒否率だけでなく、利用者への影響と回復時間も確認します。

過負荷対策の設計手順

  • 通常時と縮退時の目標RPS、応答時間、Error率を定義する
  • APIごとの処理コストと、CPU・Database・外部APIの処理能力を把握する
  • バーストと継続負荷を分け、前段と後段の流量制限を決める
  • 同時実行数とQueueの上限を決め、無制限に待たせない
  • Request全体のDeadlineから各Timeoutを設計する
  • Retryする条件・レイヤー・回数を限定し、Jitterと重複排除を設ける
  • サーキットブレーカー、負荷遮断、縮退運転の発動条件を決める
  • 障害・縮退・復旧を含む性能試験で、閾値と運用手順を調整する

まとめ

Webシステムの過負荷対策では、各レイヤーの処理能力に合わせて流入量を制御し、待機時間と同時実行数に上限を設けることが重要です。流量制限で入口を守り、TimeoutとRetryの設計で障害の増幅を防ぎ、サーキットブレーカーと負荷遮断でBackendを保護します。

さらに、重要機能を維持する縮退運転、Node停止時の縮退設計、復旧時の段階的な流入再開まで考慮します。設定値は単独で最適化せず、ClientからDatabaseまでの全体像を基に性能試験で検証します。

参考文献

AWS Builders Library: Timeouts, retries, and backoff with jitter

AWS Builders Library: Using load shedding to avoid overload

Google SRE Book: Handling Overload

NGINX: ngx_http_limit_req_module

NGINX: ngx_http_limit_conn_module

Apache Tomcat 10.1 HTTP Connector

Resilience4j CircuitBreaker

コメント