オートスケールは万能ではない。最小インスタンス0の代償を理解していなかった。
サービスをリリースして3日目の朝、サポートチームのSlackチャンネルに「朝イチでアプリが激重なんですが、何かありましたか?」という投稿が来た。確認してみると確かに遅い。Cloud Runのメトリクスを見ると、リクエストレイテンシのグラフが朝9時台だけ異常に跳ね上がっていた。平常時は200ms程度なのに、9時台は3,000ms〜タイムアウトに張り付いている。
原因はコールドスタートだった。Cloud Runの最小インスタンス数を0に設定していたため、深夜から早朝にかけてトラフィックがほぼゼロになるとインスタンスが全て停止する。そこに朝9時の業務開始時間が重なり、一斉にアクセスが集中した。ゼロから起動するコンテナが複数同時に立ち上がり、その起動完了を待つ間にリクエストがキューに詰まった。
このサービスのバックエンドはSpring Bootで、起動シーケンスが重かった。起動時にデータベースのコネクションプールを初期化し、設定ファイルを読み込み、キャッシュのウォームアップ処理が走る。ローカル環境では1秒程度だったが、Cloud Run上ではCloud SQL Auth Proxy経由のDB接続確立に時間がかかり、コールドスタートから最初のリクエストを捌けるようになるまで3〜4秒かかっていた。
この3〜4秒の遅延が、ユーザー体験としては「固まった」「落ちた」と感じられるものだった。特に朝イチの操作で遅いと一日の印象が悪くなる。サポートへの問い合わせが急増し、「昨日より遅くなった?サービス変わった?」という声も来た。
暫定対応としてその日のうちに最小インスタンス数を1に設定した。これでコールドスタートは起きなくなったが、夜間も含めて常に1インスタンスが生き続けるため、アイドル時間の課金が発生するようになった。月額換算で数千円程度の増加だったが、これはトレードオフとして受け入れた。
本質的な解決として取り組んだのはアプリの起動時間短縮だ。DBコネクションの初期化を遅延ロードに変更し、キャッシュウォームアップを非同期化した。これでコールドスタート時間は4秒から0.8秒まで短縮できた。0.8秒なら最小インスタンス数を0に戻してもユーザーへの影響は軽微になり、アイドル課金もなくせた。Cloud Runを使うときは、デプロイ前に必ず「コールドスタートにかかる時間」を本番環境と同じ構成で実測することを勧める。ローカルで計るだけでは実態が掴めない。許容できるコールドスタート時間と最小インスタンス数のコストのバランスをサービスの特性に合わせて設計することが重要だと、この失敗を通じて学んだ。