はじめに
AWSにWebアプリを載せて、無事に動いている。ユーザーも少しずつ増えてきた。……そこで、ある日こう聞かれます。
「利用者が増えても、このシステム大丈夫なの?」
答えに詰まりました。落ちてはいません。でも「何人まで耐えられるか」を聞かれると、正直に言って分かりませんでした。
さらに困ったことに、事業側に「想定ユーザー数はどれくらいですか?」と聞き返すと、返ってきた答えは「サービスの売れ行き次第だから、まだ分からない」。設計の前提になるはずの数字が、そもそも存在しなかったのです。
スケーラビリティの記事を検索すると、マイクロサービス化や分散データベースといった重厚な話が出てきて、うちの規模には明らかに過剰。かといって「増えたらそのとき考える」では、増えたそのときに間に合いません。
この記事は、スケーラビリティ対応をはじめて任された人が、何をどの順番で考えればいいのかを整理したものです。実装の詳細よりも、決め方と進め方に重きを置いています。なお、記事中の人数や金額はすべて説明用の仮の数字です。
想定読者
- AWSにWebアプリ(ECS + RDS のようなよくある構成)を載せて運用している
- 「オートスケーリング? 負荷試験? 聞いたことはあるけど、やったことはない」
- 専任のインフラ担当はいない、小〜中規模のチーム
- 「何人まで耐えられるの?」と聞かれて答えに困った経験がある
まず用語:スケールには2方向ある
最初に押さえる用語はこれだけです。
| 用語 | 意味 | たとえるなら |
|---|---|---|
| スケールアップ(垂直スケール) | 1台のスペックを上げる(CPU・メモリ) | 店員1人を鍛えてベテランにする |
| スケールアウト(水平スケール) | 台数を増やす | 店員の人数を増やす |
| オートスケーリング | 混雑に応じて台数を自動で増減する | 混んできたら自動でシフトを追加する |
どちらか一方が正解なのではなく、使い分けます。この使い分けの判断基準は後半(手順5)で扱います。
最初のつまずき:「想定規模」が決まらない
教科書的には、スケーラビリティ設計は「想定同時ユーザー数を決める → それを満たす構成を設計する」の順で進みます。ところが現実には、想定規模が決められないことがよくあります。私たちもそうでした。新しいサービスの売れ行きなんて、誰にも分からないからです。
ここで数字が出てくるのを待っていると、永遠に始まりません。そこで発想を逆にしました。
想定規模から設計するのではなく、今の構成の限界を実測して、「利用者がここまで来たらこれをやる」という段階表を作る。
段階表があれば、想定規模が未確定のままでも「ここまでは今のまま、この人数が見えたらこれ、その次はこれ」と、規模が判明したその日に打ち手が決まる状態にできます。これがこの記事のゴールです。
大原則:ボトルネックは一度にひとつ。直すと次に移る
具体的な手順に入る前に、いちばん大事な原則をひとつ。
リクエストはユーザーからDBまで、いくつもの区間を通ります。全体の処理能力を決めるのは、その中でいちばん細い1箇所だけです。
そして厄介なことに、いちばん細い首を太くすると、2番目に細かった首が新しいボトルネックになります。つまりスケーラビリティ対応は一発の大改造ではなく、
測る → いちばん細い首を太くする → また測る
のくり返しです。やみくもに全部を強化すると、効かない投資と複雑化だけが残ります。私たちは最初に構成を眺めて「詰まりそうな順」のリストを作りました。観点は単純で、「1台固定・小型・自動増減なし」の箇所を探すことです。
| 確認する場所 | よくある「詰まる予備軍」の状態 |
|---|---|
| アプリのコンテナ | 台数1固定・最小スペック・オートスケーリング未設定 |
| DB | 最小クラスの1台・レプリカ無し |
| 外部AIサービス等のAPI | 利用上限(クォータ)が小さいまま |
| バッチ処理 | 1件ずつ直列処理 |
| CDN・LB・S3 | AWSマネージドで勝手にスケールする(通常は心配不要) |
逆に言えば、マネージドサービスが自動で面倒を見てくれる部分は最初から除外できます。心配すべき場所は、思ったより少ないはずです。
手順1. まず「測れる」ようにする(観測)
スケールを語る前に、「どこが・いつ・どれだけ詰まるか」を数字で見えるようにします。これが無いと、以降の判断がぜんぶ当て推量になります。
最低限そろえたいメトリクス(CloudWatchで見られます)は次のとおりです。
| 対象 | 見るもの | 何が分かるか |
|---|---|---|
| コンテナ(ECS) | CPU使用率・メモリ使用率・稼働タスク数 | アプリ側の余力 |
| DB(RDS) | CPU使用率・接続数・空きメモリ | DB側の余力(接続数が意外と重要) |
| LB(ALB) | 応答時間・5xxエラー数・リクエスト数 | ユーザーから見た体感 |
| キュー(SQS等) | 滞留メッセージ数 | バッチ系の詰まり |
ポイントは、ダッシュボードを作り込むことではなく「詰まったときに犯人を指させる」ことです。後述しますが、私たちのケースでは「CPUもDBも余裕なのに詰まる」という一見不可解な状況になり、この観測データがあったおかげで真犯人にたどり着けました。
手順2. 限界を実測する(負荷試験)
観測の準備ができたら、今の構成が何人まで耐えられるのかを実際に測ります。私たちが実施した形は次のとおりです。
- 本番相当の環境を検証用に新しく作って、そこを壊す。本番やほかの人が使う共用環境に負荷をかけてはいけません。検証が終わったら全部削除します(数日で終わらせれば、環境費用は数千円のレンジに収まります)
- 負荷ツールはk6などのOSSで十分。自分たちのVPCの内側から負荷をかけます(社外のネットワーク越しにかけると、攻撃と区別がつかないうえ回線がノイズになります)
- シナリオは「ログイン → 一覧を見る → 別の一覧を見る」のような実ユーザーの一連の操作を再現します。単一APIの連打では実態と乖離します
- 仮想ユーザー数を少人数から段階的に増やし(例えば5→10→20→40→80のように)、各段階を数分ずつ維持します
「限界」の定義を先に決めておく
やってみて気づいた大事な点です。限界とはサーバーが落ちる瞬間ではなく、ユーザーにエラーが見え始める境目です。私たちは「エラー率がしきい値(例えば1%)を超えたら限界と判定して打ち切り」と先に決めておきました。
こうして実測すると、「最小構成のコンテナ1台の実力は同時◯人まで」という性能の単価が手に入ります。この記事では以降、仮に「1台=25人」だったとして話を進めます。この1個の数字が、以降のすべての設計の基準になります。
💡 限界点で観測される「エラー率1%超」のような数字は、本番でこれを許容するという意味ではありません。壊れ始める点を意図的に探しに行った測定値です。本番はその手前(エラー0%の範囲)で運用し、超えそうになったら台数で吸収します。この区別を報告書に書いておかないと、「エラーが出るって大丈夫なの?」という誤解を招きます。
手順3. 犯人を特定する — スペックの前にアプリの設定を疑う
ここがこの記事でいちばん伝えたい部分です。
私たちの試験では、限界を迎えた瞬間のメトリクスが不可解なことになっていました。DBのCPUは1桁%、DBの接続数も上限に対してわずか、コンテナのメモリも余裕。リソースは余っているのに、リクエストは詰まってタイムアウトしているのです。
答えはアプリのログにありました。SQLAlchemy(PythonのDBライブラリ)を使っている場合、こんなログが出ます。
QueuePool limit of size 5 overflow 10 reached, connection timed out
犯人は、アプリ内部のDB接続プールでした。アプリはDBへの接続を毎回張り直すと遅いので、数本を開いて使い回します(コネクションプール)。この本数がフレームワークの既定値(SQLAlchemyなら常時5本+一時追加10本=最大15本)のままで、コードに変更の口すら無い状態だったのです。
同時アクセスがプールの本数を超えると、あふれた分は空きが返るまで待たされ、待ち時間が上限(SQLAlchemyの既定では30秒)を超えるとエラーになります。マシンスペックを何も使い切らないまま、アプリの設定値ひとつで頭打ちになっていたわけです。
ここからの教訓は2つです。
- 「詰まる=リソース不足」とは限らない。インスタンスサイズを上げる前に、アプリ側の設定値(接続プール、ワーカー数、タイムアウト)を疑う。設定変更はほぼタダですが、サイズアップは毎月お金がかかります
- 接続の「待ち」はCPUを消費しない。この事実が、次の手順の落とし穴に直結します
手順4. オートスケーリングの指標を選ぶ — CPU基準は発火しないことがある
オートスケーリングの入門記事はたいてい「CPU使用率が60%を超えたら台数を増やす」という設定を紹介します。私たちも最初そう組みました。そして負荷試験でまったく発火しませんでした。
理由は手順3のとおりです。このアプリの詰まり方は「接続プールの空き待ち」で、待っている間CPUは暇です。ユーザーが詰まりまくっている最中もCPU使用率は低いままで、しきい値には永遠に届きません。
そこで指標を**「1台あたりのリクエスト数/分」**に変更したところ、正しく発火するようになりました。負荷をかけると1台から複数台へ自動で増えてエラーなく捌けるようになり、負荷を止めるとしばらくして最小台数まで自動で戻ることも確認できました。
| スケーリング指標 | 向いている詰まり方 | 今回のアプリでは |
|---|---|---|
| CPU使用率 | 計算量が多くてCPUを使い切るタイプ | ❌ 発火しなかった |
| 1台あたりリクエスト数 | 接続待ち・I/O待ちで詰まるタイプ | ✅ 正しく発火した |
⚠️ 「オートスケーリングを設定した」と「スケールする」は別物です。指標が実際の詰まり方に合っていないと、設定はただの飾りになります。負荷試験で「本当に発火するか」まで確認して、はじめて完成です。
オートスケーリングは「あると良い」ではなく「必須」だった
もうひとつ、試験中に怖い発見がありました。一度プールが枯渇するまで過負荷にすると、負荷が下がった後もエラーが出続け、コンテナを再起動するまで直らなかったのです(接続が返却されないまま漏れていく、いわゆる接続リーク)。
つまりこのアプリにとってオートスケーリングは、快適さのためのオプションではなく、「一度壊れたら自力で戻れない状態」を未然に防ぐための必需品でした。あわせてアプリ側も、接続を確実に返却する修正を入れたところ、過負荷後に数分で自力回復するようになりました。
スケーラビリティには「たくさん捌ける」だけでなく「壊れても自力で戻れる(回復性)」も含まれる、というのが実際にやってみて得た視点です。
手順5. 垂直と水平をどう使い分けるか — 隠れた予算は「DB接続数」
アプリを強化するレバーは、実は4本あります。
| レバー | 何が伸びるか | DB接続数への影響 | 費用感(目安) |
|---|---|---|---|
| ① 接続プール拡大(設定変更のみ) | 接続待ち型の収容人数 | 1台あたり増える | ±0 |
| ② 垂直スケール(コンテナのスペック増) | CPU詰まり型の処理能力 | 増えない(ここが利点) | 月に数千円レンジ/台 |
| ③ 水平スケール(オートスケーリング) | 総収容人数 | 台数に比例して増える | 負荷がある時間だけ増える |
| ④ DBのサイズアップ | DB側の接続上限・処理能力 | 上限側が増える | 月に数千円レンジ |
使い分けの鍵は、意外にもDBの接続数です。DBが受けられる接続数には上限があり(PostgreSQLのRDSならインスタンスのメモリサイズで決まります。小型クラスなら200前後)、各コンテナのプールは最悪時この式で上限に迫ります。
(1台あたりのプール本数 × 最大タスク数) + その他の接続(バッチ・Lambda・運用) ≦ DBの接続上限 × 安全率
ここで①〜④を見比べると、性格の違いが見えてきます。
- 水平スケール(③)は接続を「掛け算」で消費します。例えばプール30本のコンテナを8台に増やせば、それだけで240本。小型DBの上限を突き破ります
- 垂直スケール(②)は接続を1本も増やしません。同じ人数を捌くにも、1台を強くすれば必要台数が減り、接続予算にやさしい
なので私たちの結論は「まず①+②(プール拡大と垂直スケールをセットで)、足りなくなったら③、接続予算が尽きたら④」でした。実際に①+②を適用して同じ条件で再測定したところ、1台の収容人数は狙いどおり伸びました。仮の数字で言えば「1台=25人」が「1台=50人」になるイメージです。効果は必ず同条件の再測定で確認します。伸びなければ、プール以外の場所(例えばアプリの同期処理)が新しい首になったというシグナルです。
💡 ①と②をセットにしたのには理由があります。プールだけ広げて同時処理が増えると、今度は最小スペックのCPUが天井になる公算が大きかったからです(限界付近ではCPUも使い切りかけていました)。「次に詰まる場所」を予想して、一手先まで含めて変更する——これも「ボトルネックは移る」原則の応用です。
手順6. 段階表を作って締める
ここまでの実測値を組み合わせると、冒頭のゴールだった段階表が作れます。骨子はこんな形です(人数・金額は「1台=25人」の仮定で組んだ架空の例です。皆さんの環境では皆さんの実測値に置き換わります)。
| 同時利用者数 | やること | 追加費用(目安) |
|---|---|---|
| 〜25人 | 現構成のまま(1台)で足りる | ±0 |
| 〜50人 | プール拡大+垂直スケール(設定変更のみ・実測で効果確認済み) | 月に数千円レンジ |
| 〜100人 | オートスケーリングの上限台数を引き上げ(設定1行) | 負荷時のみ数千円レンジ |
| それ以上 | DBをワンサイズ上へ+再度実測 | 月に1万円前後〜 |
この表の価値は、「想定規模が分からない」という冒頭の問題を無害化できることです。規模を予言する必要はもうありません。利用者数を観測していて、しきい値が見えてきたら表の次の行を実行するだけ。事業側にも「増えたらこの表のとおりに動きます。費用はこのレンジです」と一枚で説明できます。
初心者がつまずきやすいポイント
私たちが実際に踏んだ・気づいた落とし穴です。
1. 「オートスケーリングを入れた」は「スケールする」ではない
手順4のとおりです。指標が詰まり方に合っていないと発火しません。負荷試験で発火の実物を見るまでは完成と言わないことです。増える方だけでなく、負荷が下がったら**ちゃんと減る(スケールイン)**ことの確認も忘れずに。減らないと費用が上がったままになります。
2. スペック増強の前に、アプリの設定値を疑う
手順3のとおりです。接続プール・ワーカー数・タイムアウトなどフレームワークの既定値が律速していると、いくらインフラにお金を積んでも1台の効率は上がりません。「リソースは余っているのに詰まる」ときは、ほぼアプリ側です。
3. 一度壊れたら自力で戻れないことがある
過負荷「中」のエラーは想定できても、過負荷「後」に壊れたままになるパターンは負荷試験をしないと見つかりません。さらに、ヘルスチェックがDB疎通を見ていないと、壊れたコンテナが「正常」と判定され続けてリクエストを受け続けます。回復性の確認は負荷試験のメニューに最初から入れておくことをおすすめします。
4. 負荷試験は「負荷をかける側」でつまずく
アプリの限界より先に、試験の段取りで転びます。私たちが実際に踏んだのは次のとおりです。
- 全仮想ユーザーが同一アカウントでログインすると、認証基盤(Cognito等)のレート制限に引っかかる。アプリの限界ではなく認証側の制限で試験が止まります。ログインユーザーを複数に分散して回避します
- テストユーザーの権限不足で対象APIが403になり、負荷になっていなかった
- 環境を作り直すと送信元IPが変わって、負荷元の許可設定が外れていた
「試験が止まった=アプリの限界」とは限りません。何の限界に当たったのかをログで確かめる癖をつけると、誤った結論を持ち帰らずに済みます。
5. スケールさせると、別の上限に当たる
台数が増えると、DB接続数(手順5)のほかにも連動して詰まる場所があります。外部AIサービスのAPIクォータ(呼び出し回数/分の上限)、外向き通信の出口(NAT)など、「台数×1台あたりの消費量」で効いてくる上限を一度棚卸ししておくと、増やした日に別の場所で詰まる事故を防げます。
6. コストの上限は自分で決める
オートスケーリングは便利ですが、最大台数を大きくしすぎると費用が青天井になります。最大台数は「守りたい体験」ではなく「払える金額」から逆算して設定し、段階表と一緒に事業側と合意しておくのが健全です。
決めたことは必ず文書に残す
スケーラビリティ対応も「作って終わり」ではなく、利用者が増えたそのときに、別の人が使う仕組みです。少なくとも次を残しておくとよいと思います。
| 残すもの | 内容 |
|---|---|
| 段階表 | 「ここまで来たらこれをやる」の一覧。この文書の主役 |
| 実測値とその条件 | 1台=同時◯人、測定日、シナリオ。条件が変わったら測り直しの合図 |
| 限界の定義 | エラー率◯%で限界と判定した、など |
| 設定値の根拠 | プール本数・スケール指標としきい値・最大台数を「なぜその値か」まで |
| 次に詰まる場所 | 今回は直さなかったボトルネック候補。次の担当者への最大の親切 |
いちばん抜けがちなのは最後の「次に詰まる場所」です。ボトルネックは移るので、今回の対応が効けば効くほど、次の詰まりは必ずやって来ます。
まとめ
- スケーラビリティ対応は「測る → いちばん細い首を太くする → また測る」の反復。一発の大改造ではない
- 想定規模が決まらなくても止まらない。現構成の限界を実測して段階表を作ることがゴール
- 限界とは落ちる瞬間ではなくエラーが見え始める境目。判定基準(エラー率◯%)を先に決める
- 詰まりの犯人はスペックよりアプリの設定値のことがある。「リソースが余っているのに詰まる」はアプリ側のサイン
- CPU基準のオートスケーリングは発火しないことがある。指標が詰まり方に合っているか、発火の実物を負荷試験で確認する
- 垂直と水平の使い分けはDB接続数の予算で考える。水平は接続を掛け算で消費し、垂直は増やさない
- 回復性(壊れても自力で戻れる)もスケーラビリティの一部。過負荷「後」の挙動まで試験する
「利用者が増えたらどうするの?」と聞かれて答えに困っていた状態から、「この表のとおりに動きます」と一枚で答えられる状態へ。この記事がその最初の一歩の材料になれば嬉しいです。
負荷試験の具体的な組み立て(k6のシナリオ設計、本番相当環境の作り方と後片付け)や、オートスケーリングのCDK実装は、需要があれば実践編として別途書きます。