IT/AI救済センター

相談事例 /

スタートアップのAWSコストが月104万円に膨張 ─ NAT GatewayとRDS設計の見直しで24.8万円台に圧縮した事例

業界
-
規模
-
対応期間
-

課題

解決策

成果

2024年9月2日の月曜朝、そのスタートアップのCTOから相談が来た。「先月のAWS請求が104万円でした。前月は64万円だったのに、何が起きているのか全くわからない」という内容だった。

従業員14名、2021年11月創業のB2B SaaSで、シリーズAのクローズが翌月に迫っていた。月100万円超のインフラコストは財務的にも投資家へのナラティブとしても深刻で、「VC側から指摘される前に直したい」というのが本音だったようだ。

初日にAWS Cost Explorerを開き、サービス別コストを分解した。8月の内訳はこうだった。NatGateway 36.2万円、RDS 22.4万円、EC2 18.7万円、Lambda 14.8万円、S3+CloudFront 8.1万円、その他 4.2万円。前月と比べて跳ね上がっていたのはLambdaとNAT Gatewayで、それぞれ前月から2.1倍、1.4倍に膨らんでいた。

**NAT Gateway ── 月36万円を生んだ設計の盲点**

NAT Gatewayのコストはデータ転送量に比例する。処理したバイト数 × $0.062/GBが基本単価で、8月は転送量が約581GBに達していた。

原因はアーキテクチャにあった。バックエンドのEC2インスタンス群はすべてプライベートサブネットに置かれていた。それ自体は正しい。問題は、外部SaaSへのAPI呼び出し(Stripe、SendGrid、Slack Webhook、Salesforce API)が全件NAT Gateway経由で出ていたことだ。機能追加のたびに外部API連携が増え、送受信データが積み上がっていた。

対処は二段階で進めた。まず、VPC Endpointで解決できるAWSサービス(S3、SQS、DynamoDB、Lambda)への通信をすべてPrivate Link経由に切り替えた。これだけで転送量の約40%を削減できた。次に、外部SaaSへの送信APIコール自体をレビューし、Stripe Webhookの受信用エンドポイントだけ Public Subnet の ALB に移動した。Salesforce連携はバッチ処理化して通信回数を1/8に圧縮した。

結果、9月のNAT Gateway費用は12.1万円まで落ちた。アーキテクチャ上の誤りではなく、誰も気にしていなかった積み上げが招いた結果で、設計レビューの時点でVPC Endpointの適用範囲を確認していれば避けられた費用だった。

**RDS ── リードレプリカが一度も使われていなかった**

RDSの構成はdb.r5.xlargeのMulti-AZ + リードレプリカ2台で、月22.4万円だった。高可用性のために構成したと聞いたが、コードを確認するとwriterエンドポイントにしか接続していなかった。Cluster Endpointは設定されていたが、アプリ側のDSNが全件writerを直指定していた。

これはよくある。インフラ担当がCluster Readエンドポイントを用意しても、アプリ開発側が接続先を切り替えなかっただけだ。誰を責める話でもないが、月約8万円分のリードレプリカが2台、丸々無駄になっていた。

読み取り系クエリをread replicaに流す修正をLaravel(このプロジェクトのフレームワーク)のdatabase.phpで対応し、一方のリードレプリカを削除した。残った1台もdb.r5.largeにダウングレードした。スループット要件を実測したところ、現状の負荷でもdb.r5.largeで余裕があることが確認できた。

Multi-AZは残した。障害時のフェイルオーバーを捨てるのはシリーズA後のプロダクトとして許容できないと判断した。ここは削らない選択が正しい。

**EC2 ── 開発環境を「本番と同じ」にしていた**

EC2の18.7万円のうち、実に11.2万円が開発環境・ステージング環境のインスタンスだった。m5.2xlarge(8vCPU/32GB)が3台、24時間365日稼働していた。本番と全く同じスペックで構成されており、明らかにオーバースペックだった。

開発環境をt3.medium(2vCPU/4GB)に変更し、夜間・週末はEC2 Instance Schedulerで自動停止に変えた。ステージングも本番と同構成にする必要があるのは負荷試験の時だけなので、平時はt3.largeで運用してリリース前2週間だけ本番同等に戻すルールを作った。

Instance Schedulerの設定自体は30分で終わる。それで月11万円削減できる。ただ、エンジニアが「俺の夜間作業どうすんだ」という抵抗を示すことがある。実態を確認したところ、深夜の開発は月に3〜4回程度。「必要な時だけ起動する」という習慣に変えてもらうことで合意した。

**Lambda ── 無限再試行ループが8億回の呼び出しを生んでいた**

Lambdaの14.8万円が最も深刻だった。前月は7万円程度だったのが倍増していた。CloudWatch Metricsで確認すると、8月17日から特定の関数の呼び出し回数が異常に増えていた。累計約8.1億回、うち9割がエラー終了だった。

SQSをトリガーにした非同期処理の関数で、受信したメッセージをSlack APIに転送する処理だった。8月17日にSlack側のWebhook URLが変更になったにもかかわらずアプリ側が古いURLのままで、関数が毎回タイムアウトしていた。SQSのデッドレターキュー設定がなく、メッセージは何度でも再試行された。

URLを直した上で、SQSトリガー側にMaximumReceiveCount=5、可視性タイムアウトを900秒に設定し、DLQを追加した。本来はデプロイ時に確認すべき設定漏れだ。エラー時の再試行回数に上限を設けないのは、コスト的にも運用的にも危険で、これを機に全Lambda関数のDLQ設定を棚卸しした。

**3週間後の結果**

修正が全て本番反映されたのは9月23日。同月の最終請求は24.8万円で、前月比76.2%の削減だった。作業工数は調査5日、修正・テスト・リリース12日の計17日間。

シリーズAのクローズ後、CTOから「VCのデューデリで『インフラコストの構造が整理されている』と評価された」という連絡が来た。コスト削減が直接評価に影響したかどうかは不明だが、説明できる状態になっていたことは確かだ。

教訓を一つ挙げるなら、コストアラートを早期に設定することだ。このスタートアップには月次コスト超過の通知が一切設定されていなかった。AWS Budgetsで「前月比120%を超えたらSlack通知」を設定しておけば、Lambdaの異常も8月初旬に気づけた。設定は5分で終わる。

同じようなお悩み、ありませんか?

まず60分の無料相談で、状況整理から始めましょう。

60分無料相談を予約
緊急 AI診断 60分予約