この記事は性能テストを実施したときに直面した課題と、その解決策(アプローチ)をまとめた。
期間としては1ヶ月ほど実施していたが、今回直面した問題をもっと早く知っていれば、もっと早く解決できたので、そのときの教訓を残しておく。
前提となる想定ワークロード
- コンシューマー向け(平常時): 1,000 RPS(秒間1,000リクエスト)
- コンシューマー向け(ピーク時): 10,000 RPS
- スパイクアクセス: メディア露出時などの瞬間的な超アクセス
- バッチ処理向け: 複数件のデータを一括で取得する重いリクエスト
- 管理画面向け: データのキャッシュを更新するリクエスト
メモリ枯渇(OOM)と連鎖的なシステムダウン
- 課題: データ量が多いバッチ向けの一括取得リクエストがくると、コンテナのメモリが枯渇(OOM)してダウン。その負荷が他の健全なコンテナに流れ込み、連鎖的に全滅(カスケード障害)してしまった。
- 解決策: 「機能を切り捨てる(トレードオフ)」
- コンテナのメモリ上限を引き上げる案は、全体コストが跳ね上がるため却下。
- 「高速な参照API」と「重いバッチ処理」を同じシステムに同居させるのはアンチパターンと判断し、一括取得の機能自体をシステムから削除して分離した。
突発的なアクセス(スパイク)による過負荷
- 課題: 瞬間的な超アクセスが発生した際、コンテナの増加(スケールアウト)が間に合わず、負荷に耐えきれなくなった。
- 解決策: 「サーキットブレーカーの導入と起動の高速化」
- 限界ラインを超えたリクエストには無理に処理せず、「503エラー」を返すよう設定し、システム全体が倒れるのを防いだ。
- コンテナ起動時のヘルスチェック(DBやキャッシュへの疎通確認など)を並列処理化し、コンテナがリクエストを受け付けられるようになるまでの時間を短縮した。
スケールアウト時:増えたコンテナにアクセスがいかない
- 課題: オートスケールによってコンテナの台数が増えたにもかかわらず、新規コンテナにトラフィックが流れず、既存のコンテナに負荷が偏り続けた。
- 解決策: 「Keep-Aliveの最適化」
- ロードバランサーと既存コンテナ間のHTTP接続(Keep-Alive)が維持され続けていたのが原因。適切にコネクションを切断し、新しいコンテナへ再接続されるように設定を見直した。
スケールイン時:ロードバランサーで502エラーが頻発
- 課題: ピークが過ぎてコンテナの台数が減る(スケールイン)タイミングで、ロードバランサー側に「502 Bad Gateway」のエラーが頻発した。
- 解決策: 「Graceful-Shutdownと待機時間の調整」
- ロードバランサーからの切り離しが完了する前に、コンテナ内のアプリが終了してしまっていたのが原因。
- 処理中のリクエストが終わるまで待つ「Graceful-Shutdown」をアプリに実装。さらに、インフラ側(preStopフックなど)で終了待機時間を延長し、安全に通信をクローズできるようにした。
レスポンスタイムの伸び悩み
- 課題: データサイズの肥大化や、プログラム言語の標準機能の重さが原因で、目標とするレスポンスタイムに届かなかった。
- 解決策: 「データ圧縮とコードレベルのチューニング」
- クライアント側に
Accept-Encodingを強制し、レスポンスデータを圧縮して通信のボトルネックを解消。 - 配列初期化時のメモリ確保(キャパシティ指定)や、高速な外部JSONライブラリへの切り替えなど、コードレベルで泥臭く処理を軽量化した。
学び
「性質の異なるワークロードを単一のサービスに詰め込むと、破綻(あるいはトレードオフ)を招く」
コンシューマー向けの「高速な参照処理」、管理画面からの「更新処理」、そして大量のリソースを消費する「バッチ処理」を1つのアプリケーション(密結合)にまとめてしまうのは、避けるべきアンチパターン。この設計はリソースの競合やスケールの妨げになり、結果として想定外のインフラコスト増加につながる。
このような事態を防ぐため、システム設計において以下の3点が重要である。
- 処理特性の徹底的な把握
- 負荷パターンや依存関係、目標とするスループット(RPS)を事前に正確に分析しておくこと。
- SLA/SLOに基づく優先度制御
- システムの中でどの機能が最も重要かを見極め、重要度に応じて適切なコンピューティングリソースを配分すること。
- 疎結合なアーキテクチャ設計
- コスト・性能・信頼性のすべてを両立させるため、特性の異なる処理は可能な限り物理的・論理的に分離(疎結合化)する戦略をとること。

