【ITニュース解説】Autoscaling doesn't save you at 30,000 requests per second — your dependencies do
2026年10月05日に「Dev.to」が公開したITニュース「Autoscaling doesn't save you at 30,000 requests per second — your dependencies do」について初心者にもわかりやすく解説しています。
ITニュース概要
オートスケーリングは負荷時にPodを増やすが、依存するDB接続制限などでシステムが障害を起こす場合がある。各Podの接続プールと最大Pod数を適切に設定し、リトライは一箇所に集約、DR訓練を定期実施するなど、依存関係を考慮した設計が重要だ。
ITニュース解説
システムが多くのリクエストを処理しなければならないとき、多くの人は「オートスケーリング」という技術を頼りにする。これは、ユーザーからのアクセスが増えたら、自動的にアプリケーションの実行環境(Podと呼ばれる、アプリケーションのインスタンスのようなもの)を増やし、アクセスが減ったら減らすことで、常に適切な処理能力を保つ仕組みだ。これにより、システムは安定し、エンジニアは夜間でも安心して過ごせると考えがちである。しかし、この便利なオートスケーリングが完璧に機能したにもかかわらず、システム全体が停止するという予期せぬ事態が発生することがある。
実際にあった事例では、毎秒数万件という大量のリクエストが集中した際、オートスケーラーは期待通りにPodの数を12個から40個へと急激に増やした。各Podは起動時にデータベースへの接続を20個ずつ確立するように設定されていたため、12個のPodでは合計240個の接続が必要となり、これは問題なく動作していた。しかし、Podが40個に増えた途端、合計で800個ものデータベース接続を要求することになった。ここで問題が発生した。利用していたデータベースの同時接続上限が500個だったのだ。結果として、上限を超えた接続要求はデータベースに拒否され、接続できなかったPodは「準備ができていない」と判断されて再起動される。すると、これらのPodは再びデータベースへの接続を試み、また拒否されるという悪循環に陥ってしまう。この状況では、オートスケーラーはさらにシステムの応答が遅れていると判断し、Podをさらに増やそうとするため、事態は一層悪化する。
このとき、システムの監視ダッシュボードには奇妙な表示がされていた。CPUやメモリの使用率は低く、Podの数は十分にあるにもかかわらず、エラー率は天井知らずに上昇していたのだ。この経験から得られた教訓は、オートスケーリングはアプリケーションの実行環境自体を増やす「水平スケール」という問題の半分しか解決しないということだ。本当の課題は、新しく増えたPodたちが会話する相手、つまりデータベースや外部APIなどの「依存先のシステム」が、増えたPodからの大量のリクエストや接続要求に耐えられない点にある。オートスケーリングは、アプリケーション側の「処理能力の不足」という問題を、依存先のシステムが抱える「並行処理(同時に多くの処理をこなすこと)の限界」という問題にすり替えてしまうのである。依存先がデータベースの接続数制限や外部サービスのAPIレート制限、あるいは古いシステムのスレッドプールサイズなど、固定されたリソースを持つ場合、Podを増やせば増やすほど、状況は悪くなるばかりだ。
この問題を解決するためには、Podごとの設定だけでなく、システム全体として依存先のリソースをどう使うかという「グローバルな予算」を考える必要がある。例えば、データベースの同時接続上限が500個で、予備の接続分(運用や緊急時に使うための余裕)を確保する安全係数を0.8と設定し、Podの最大数を40個にすると決めた場合、各Podが持てる接続プールのサイズは「(500 × 0.8) / 40 = 10個」となる。つまり、1つのPodは最大10個のデータベース接続しか持つべきではないと計算できる。この最大Pod数(maxReplicas)は、アプリケーションのコストだけでなく、依存先のリソース制限に基づいて設定することが非常に重要だ。この数値を勝手に増やしてしまうと、過去の障害が再発する可能性が高まるため、なぜその数値が設定されているのかを明確に文書化しておくべきである。また、オートスケーリングのトリガー(引き金)には、CPU使用率ではなく、「処理中のリクエスト数(in-flight requests)」や「キューの深さ(処理待ちのリクエストの量)」といった、サービスが実際にどれだけ飽和しているかを示す指標を用いるべきだ。CPU使用率が低くても、依存先の応答が遅いためにリクエストが滞っている場合、サービスは深刻な状態に陥っている可能性があるからだ。さらに、Podのスケールアップ(増やすこと)は迅速に行い、スケールダウン(減らすこと)はゆっくりと行うような非対称な設定が望ましい。これは、Podの数が頻繁に変動すると、そのたびに依存先との接続を確立し直す負荷が発生し、かえってシステムに負担をかけるためである。
次に、リトライ(再試行)の扱いも重要だ。複数のサービスが連携して動作するシステム(例えばサービスAがサービスBを呼び出し、サービスBがサービスCを呼び出すような構造)で、各サービスが失敗時にそれぞれ独自に数回のリトライを行うような設定は非常に危険だ。仮にサービスCで一時的な障害が発生した場合、サービスBからの1つのリクエストが3回のリトライにより3つのリクエストになり、さらにサービスAからのリクエストも3回リトライされることで、最終的にサービスCには元の1つのユーザー操作が27個ものリクエストとなって押し寄せることになる。このように、リトライが多層的に行われると、システムの一部での一時的な不調が、全体的な障害に発展する可能性が高まる。これを避けるためには、リトライはシステムの最も外側の層、つまりユーザーに近い層でのみ行い、それより内側のサービスは失敗したらすぐにエラーを返す「フェイルファスト」の原則に従うべきである。また、リトライの回数を各リクエストごとに設定するのではなく、特定の依存先への総トラフィックの「リトライ予算(例えば、リトライは全リクエストの20%を超えてはならない)」として設定するのが良い。これにより、障害時には自動的にリトライが停止し、依存先への過剰な負荷を回避できる。さらに、「サーキットブレーカー」という仕組みを導入することも非常に有効だ。これは、特定の依存先でエラーが一定数続いた場合に、その依存先への通信を一時的に遮断し、すぐに失敗を返すことで、無駄なリクエストやタイムアウト待ちによるリソースの消費を防ぐ。サーキットブレーカーを設定する際には、「エラー率が一定の割合を超えた場合でも、すべての接続を遮断するのではなく、一部だけを遮断する(例えば、maxEjectionPercent: 50のように最大50%まで)」という設定を忘れないことが重要である。これにより、部分的な障害がシステム全体を巻き込む完全な障害になるのを防ぐことができる。
システム障害に備える「フェイルオーバー(障害発生時に待機系に切り替える仕組み)」についても、単に文書化されているだけでは不十分だ。重要なのは、そのフェイルオーバー手順がいつ、どのくらいの時間をかけて、実際に人間によって実行されたか、ということである。もし監査のためだけに年に一度、40分かけて手動で実行されるようなものであれば、それは実際の障害時に役立つフェイルオーバーとは言えない。プレッシャーのかかる状況下での手動操作は、ヒューマンエラーの温床となる。本当のフェイルオーバーは、スクリプトとして自動化され、日常的なテストとして「普通の火曜日」に実行できるレベルであるべきだ。特に忘れがちなのが、「事前ウォームアップ」というステップだ。災害復旧サイトが最小限のリソースで稼働している場合、そこに突然ピーク時の全トラフィックを流し込むと、キャッシュが空だったり、データベースに大量の接続が殺到したりして、かえって障害を引き起こしてしまう。フェイルオーバーを行う際は、事前にDRサイトのリソースを増強し、準備を整えておく必要がある。また、トラフィックの切り替えも、いきなり全量を移すのではなく、10%、25%、50%と段階的に増やしていくべきだ。これにより、切り替え中に問題が発生しても、すぐに元に戻せる柔軟性を持てる。
最後に、監視システムは単に情報を表示するだけでなく、収集したデータに基づいて自動的に行動を起こすように進化させるべきだ。例えば、システムが飽和状態にあると判断されたら自動的にPodをスケールアップする、プライマリサイトで障害が継続的に発生している場合はDRサイトを自動でウォームアップする、といった具合だ。ただし、トラフィックの切り替えのような重要な操作は、完全に自動化する前に、まずは自動化された手順を人間の目で何度も確認し、安全だと確信できてから慎重に自動化を進めるべきである。また、誤検知による不必要なアクションを防ぐため、1つの監視信号だけでなく、複数の異なるソースからの情報が一致した場合にのみ行動を起こすように設定することも大切だ。例えば、エラーレートの上昇と同時に、合成トランザクション(ユーザーの操作を模倣した自動テスト)が失敗していることを確認してからフェイルオーバーを判断するなどである。これにより、信頼性の高い自動化が可能になる。
システムエンジニアを目指す上で、これらの知識は非常に重要だ。もし時間があれば、自分の関わるシステムについていくつかの点を確認してみることをお勧めする。例えば、各アプリケーションの接続プールのサイズとPodの最大数を掛け合わせ、それが依存先のデータベース接続数や外部サービスのレート制限などの固定された制限を超えていないか確認する。システム内でリトライが何層にもわたって行われていないかを確認し、もしそうなら修正を検討する。そして、災害復旧(フェイルオーバー)の訓練がいつ最後に行われたかを確認し、もし半年以上前であれば、実際にリハーサルを計画してみる。さらに、アプリケーションのオートスケーリングがCPU使用率だけでなく、処理中のリクエスト数など、サービスの「飽和度」を適切に監視してスケールしているかを確認することも重要である。これらの点は、オートスケーラーのダッシュボードには現れないが、システムの安定稼働には不可欠な要素であり、それがゆえに多くの場合見落とされがちなのだ。