受け取り損ねていた割引 - SpotとGravitonはなぜ安いのか
AWSの請求書が2か月連続で予算を超えた。削れるリソースを探したが、本当のレバーは「どれだけ使うか」ではなく「どう買うか」だった。SpotとGravitonがなぜ安いのか、そして無駄に見えて実は切ってはいけないコスト(RDS Proxy)まで - 請求書の数字ひとつひとつに宿る「理由」についての話。
要点まとめ
クラウド費用を減らせと言われると手はまず「何を止めるか」に向かうが、リソースがすでに最適化済みなら残るレバーは支払い方だ。Fargate Spotは、AWSが余った容量を「2分以内の回収」条件付きで投げ売りするものなので70%安く(条件付きなのでstagingに)、Gravitonは AWS自社設計のARMチップで中間マージンがないため20%安い(無条件なのでプロダクションまで)。逆にRDS Proxyのように、無駄に見えてコネクションサージを防ぐ保険であるコストもある。請求書を読むとは数字を減らすことではなく、それぞれの数字の理由を知ることだ - 条件を引き受けられる割引は受け取り、事故を防ぐコストは残す。
目次
AWSの請求書が2か月連続で予算を超えた。超過幅は一桁台のパーセンテージにすぎず、金額自体は大きくないが、方向が右肩上がりだった。どこをもっと削れるかとリソースを一つずつ洗い直したが、結論は意外なものだった。削れるリソースがほとんど残っていなかったのだ。本当のレバーは「どれだけ使うか」ではなく「どう買うか」にあった。この過程で学んだのは、請求書の数字にはそれぞれ理由があり、その理由を知ってはじめて何を止め何を残すべきかが判断できるということだった。
リソースはすでに絞り込まれていた
まず使用量から見直した。ところがこの部分にはすでに相当手が入っていた。NATゲートウェイはなくしてパブリックサブネット構成にしており、ログは7日保持のみ、トレーシングは1%サンプリング、監視ダッシュボードは無料枠に収まるよう組んでいた。DBとRedisはすでにより安いARMインスタンスを使っており、S3は自動階層化、踏み台サーバーは必要なときだけ起動する方式だった。リソース単位で絞れるものはほぼ絞りきっていた。
6月の請求書を項目別に見るとこうなる。最も大きいのはDB(RDS)で全体の約40%を単独で占め、次いでコンピュート(ECS Fargate)が約17%、ロードバランサーとWAFがそれぞれ約10%、パブリックIP・キャッシュ(ElastiCache)・S3は一桁台のパーセンテージにとどまった。ここからインスタンスをさらに小さくするのは逆行だった。DBはメモリ不足で一度スケールアップした経緯があり、WAFはプロダクション投入前にルールを検証するため意図的に置いていたものだった。残るレバーはリソースではなく支払い方だった。同じリソースをそのまま使いながら、より安く買う道。
なぜSpotは安いのか
まず目についたのは、コンピュートをオンデマンド定価だけで買っていたことだった。AWSには同じサーバーをずっと安く売る窓口がある。Fargate Spotだ。オンデマンド比で70%ほど安い。
これほど安いのには理由がある。AWSはピーク需要に合わせてデータセンターを構築している。平常時はサーバーが余る。余った容量を遊ばせておくと収益は0なので、「定価の客が来たら2分以内に明け渡す」という条件付きで、その余剰容量を格安で売っているのだ。航空会社が出発直前の空席を投げ売りするのと同じ原理だ。どのみち空席のまま飛べば売上は0なので、条件をつけてでも売ったほうが得というわけだ。
問題は、その条件、つまり「明け渡せと言われたら明け渡す」を許容できるかどうかだ。プロダクションだと困る。ユーザーがつながっている状態でタスクが2分後に回収されると、一瞬でもサービスが揺らぐ。しかしstagingは事情が違う。実ユーザーはおらず、数分ダウンしてもQA担当が一瞬不便を感じるだけだ。そこでstagingのAPIをSpotに移した。パフォーマンスはオンデマンドと完全に同一だ。vCPUもメモリも同じハードウェア上で動くからだ。変わるのは「回収されうる」という可用性の条件ひとつだけで、それはstagingが喜んで引き受けられる条件だった。適用後、stagingのタスクはSpot上で問題なく動き始めた。
なぜGravitonは安いのか
2つ目はコンピュートのチップだった。DBとRedisはすでにARMインスタンスを使っていたのに、肝心のアプリケーションを動かすFargateだけがx86に残っていた。AWS自社設計のARMチップであるGravitonに移せば、同じ性能を20%ほど安く使える。
これが安い理由はSpotとは性質が違う。通常のインスタンスはAWSがIntelやAMDからCPUを仕入れて使うものなので、我々が払う金額にはチップメーカーのマージンが乗っている。Gravitonは AWSが自社で設計したチップなので、その中間マージンがない。さらにARMアーキテクチャは同じ演算をこなすのに必要な電力と発熱が少ないため、データセンターの電気代・冷却コストも抑えられる。AWSからすれば原価が低いので20%安く売っても利益が出るし、安く売ることで顧客を自社チップのエコシステムに引き込む戦略でもある。
ここに重要な違いがある。Spotの割引には「回収を受け入れる」という条件がつくが、Gravitonの割引には何の条件もない。ただ単に安いチップというだけだ。だからSpotはstagingにしか使えなくても、Gravitonはプロダクションまでそのまま適用できる。パフォーマンスもさほど心配はいらない。Node.jsはGravitonでもvCPU当たりの性能がx86と同等か、むしろ良好だ。唯一注意すべきはコールドスタートだ。アプリが最初に起動するときはシングルコア速度がものを言うが、ここでGravitonが最新のx86よりやや遅いことがあり、起動に数秒余分にかかりうる。そこでまずstagingに載せて起動時間を測ってから、プロダクションへ昇格させる順序にした。事前チェックはネイティブモジュールがarm64向けにビルドされているかを確認するだけでよい。
2つの割引の性格を整理するとこうなる。Spotは不便を引き受ける代償として得られる割引なので条件がつき(だからstaging)、GravitonはAWS自社チップの原価優位を分けてもらう割引なので条件がない(だからプロダクションまで)。性能が同じなのに、わざわざ高いx86の定価にとどまる理由はなかった。実のところ今回の最適化は、何か新しく倹約したというより、受け取り損ねていた割引を今になってようやく受け取り始めたことに近い。
切ってはいけないコストもある
ところが請求書を眺めていると、逆方向の落とし穴もある。無駄に見えて実は保険という項目だ。
RDS Proxyがそうだった。毎月一定額の固定費が出ていて、最初は削減候補に見えた。Proxyの機能の一つがシークレットの自動ローテーションを無停止で行うことなのだが、そのローテーション機能は今は無効になっていたからだ。ローテーションを使っていないのになぜProxyを置いているのかと思った。
これは誤読だった。Proxyの本来の目的はローテーションではなく、コネクションサージを防ぐことだった。デプロイやスケールアウトでタスクが急に増えると、DBへ向かうコネクションが急増するが、DBには同時に受け付けられるコネクション数に上限がある。Proxyはその手前でコネクションをまとめて多重化し、上限を超えないよう守っている。よりによってこのDBはメモリ不足で一度スケールアップした経緯があるインスタンスで、コネクション一つひとつがメモリを食う。つまりその固定費は無駄ではなく、デプロイのたびにDBがコネクションの殺到で倒れないよう守ってくれる保険料だった。切った瞬間に節約になるのではなく、防御壁を取り払うことになるところだった。
誤読の原因はコードのコメントだった。そのProxyを説明するコメントがローテーションの観点中心に書かれていたため、読む人が「ローテーションが無効なのだからProxyも不要」と誤解しやすかった。そこで削減案からProxyを外す代わりに、コメントに「主目的はコネクションサージの抑制、ローテーションは付加機能」という一文を加えた。次に請求書を見る人が同じ判断ミスをしないように。
請求書の数字にはそれぞれ理由がある
クラウド費用を減らせと言われると、手はまず「何を止めるか」に向かう。インスタンスを減らし、機能を切り、リソースを消す。しかし今回学んだのはその逆だった。削る前に問うべきは「この数字はなぜここにあるのか」だ。
ある数字は受け取っていなかった割引だ。SpotとGravitonのように、同じリソースをより安く買える窓口があるのに、ただ定価で買っていただけというケースだ。こうした割引にはそれぞれ安い理由があり、その理由(回収を引き受けるのか、無条件の原価優位なのか)がわかれば、どこに安全に適用できるかが決まる。ある数字は逆に、切ってはいけない保険だ。無駄に見えても、それが防いでいる事故があるなら、その項目は支出ではなく防御なのだ。
結局のところ請求書を読むとは、数字を小さくすることではなく、それぞれの数字の理由を知ることだ。理由がわかれば、条件を引き受けられる割引は受け取り、事故を防ぐコストは残せる。私たちがやったこともそれだけだった。新しく何かを倹約したのではなく、受け取るべき割引と残すべき保険を区別しただけだ。
よくある質問
Fargate Spotはプロダクションでも使っていいですか?
Spotのトレードオフは、2分前の警告後にタスクが回収されうるという点です。この中断を許容できる環境であればプロダクションでも使えます。タスクが複数あるHA構成で、数分間の容量減少が致命的でないなら部分適用も可能です。ただし単一タスクだったり中断に敏感なワークロードなら危険なので、中断が無害なstagingから始めるのが安全です。
Graviton(ARM)に移すとパフォーマンスは落ちませんか?
ほとんどのワークロードで同等か、むしろ良好です。特にNode.jsはGravitonでもvCPU当たりの性能がx86と互角です。唯一注意すべきはコールドスタートで、アプリが最初に起動するときはシングルコア速度が最新のx86よりやや遅く、起動に数秒余分にかかることがあります。stagingで起動時間を実測してからプロダクションに昇格させ、ネイティブモジュールがarm64向けにビルドされているかだけ事前に確認すればよいです。
関連記事
- 💻 開発
ミニPCにDocker + Cloudflare Tunnelでセルフホスティング
自宅のミニPC 1台で複数のWebサービスを安全に公開する方法。ポート開放なしでCloudflare TunnelでHTTPSをつなぎ、Docker Composeで運用します。
- 💻 開発
終了フックが二重に走っていた - SIGTERMの持ち主が二人いたとき
デプロイのたびにSentryにPrismaのP2028エラーが落ちていた。終了順序を直そうと踏み込んだら、NestJSのenableShutdownHooksとgraceful shutdownライブラリの両方がSIGTERMを掴んでいて、終了フックが毎回二重に実行されていたことが分かった話。
- 💻 開発
「stagingで止めればいい」で終わらせない - うるさいアラートが隠していた本当のバグ
stagingで毎日鳴っていたGoogle Playの404アラート。発生源を止めれば静かになるが、それと失敗モードを直すことは別の問題だった。404は再試行しても意味のない恒久的なエラーなのに、5xxと一緒くたにされて再試行されていたという話。