キャッシュ設定見直したら、CDNの運用コストが60%下がった話
概要
月間アクティブユーザー100万規模のWebサイトにおいて、CDN(CloudFront)のキャッシュ設定を見直した結果、月額コストを約60%削減し、運用費の変動が収まり、ユーザー体験の向上が実現できた話をしたいと思います
きっかけ
現職へ転職後、早々にAWSのコストを確認するとCDN(CloudFront)のコストが無駄に多いと感じ、AWS Cost Explorerを確認すると、DataTransfer-Out-Bytesでやたらコストが発生してることが判明しました
※ DataTransfer-Out-BytesはエンドクライアントとCDN間のデータ転送量を示しています。
また、CloudFrontの詳細な分析を行うため、キャッシュ統計レポートを確認したところ、サイトのページビュー数に対して、画像へのリクエスト数が異常に多いことが判明しました。
通常であれば、適切なキャッシュ設定により画像リクエストの多くがエンドユーザーのブラウザキャッシュやCloudFrontのエッジキャッシュで処理されるはずです。しかし実際には、多くのリクエストがオリジンサーバーまで到達していることが分かりました。
現状分析と問題の整理
当時の構成
既存の構成では、Lambda@Edgeを使用して画像圧縮を行っており、CloudFrontでキャッシュする仕組みが導入されていました。

CloudFrontのキャッシュキーにはURLが設定されており、2回目以降のリクエストはCloudFrontのエッジロケーションにキャッシュされる形です。
コストが発生していた要因
問題1: パラメーター未付与時のオリジナル配信
画像圧縮パラメーターが付与されていないリクエストに対して、オリジナルサイズの画像が配信されていた。
CloudFront自体にキャッシュ設定を行っていましたが、オリジナルをキャッシュしているのでエッジロケーションとエンドユーザー間で大容量が配信されている形でした

問題2: エンドユーザーのキャッシュが活用されてない
アプリケーションはRailsで、コンテンツのアップロードにはCarrierWaveを使用していました。
アップローダーにはmax-age: 0の設定がされており、これが原因でエンドクライアント側では画像をキャッシュせず、毎回リクエストが発生してました。
サイトのページビュー数に対して、画像へのリクエスト数が異常に多いことが判明しました。
前述の原因はまさにmax-ageですね。毎回画像を取得しているので、PVとは比にならない、大量のリクエストが発生していました。
アプローチ
パラメーターの自動付与
まずはオリジナルの配信を止めることで、転送量を大きく抑えることができます。
今回は、CloudFront Functionをリクエスト側に配置し、画像リクエストにパラメーターが付与されていない場合に、自動的にWebP変換パラメーターを付与するよう設定しました。
これにより、エッジロケーションのキャッシュキーはURLとなっているので、キャッシュがあればエッジロケーションから配信されますし、無ければLambda@Edgeはパラメーターを確認して、WebPに圧縮したコンテンツを返してくれるようになります。

注意点
実装時には以下の重要な考慮事項に注意する必要がありました
1. 編集対象画像の判定
運用しているサービスはユーザーが過去の投稿を編集が可能です。編集時はWebPは使えないので、オリジナルコンテンツを返してあげる必要があります。
CloudFront Function単体では判定が困難なため、編集機能からのリクエストにはf=originパラメータを付与し、オリジナル画像を配信するよう設定しました。
2. ブラウザ対応の考慮
WebPに対応していないブラウザに対してWebPを返さないよう、リクエストヘッダーのAcceptを確認してブラウザの対応フォーマットを判定し、適切な形式で画像を配信するよう設定しました。
3. 既存パラメーターの保護
新規資産に関しては、画像のリクエスト時にフォーマットを指定してるケースが多かったため、CloudFront到達時にパラメーターが付与されている場合、何も変更を加えないようにしました。
この変更により、すべての画像リクエストが適切に最適化されるようになり、オリジナルサイズでの配信を防ぐことができました。
Cache-Control設定の最適化
max-ageを設定することで、次回のリクエスト以降、クライアント側で保有しているキャッシュを使うようになるので、転送量が大幅に減ります。
今回は、Lambda@Edge側でmax-ageを一律1ヶ月に設定しました。
CloudFrontのResponse Headers Policyでも設定可能ですが、この時は知らず…
効果の測定と検証
コスト面での改善
改善前後でのコスト推移を測定した結果、以下の効果が確認できました:
- コスト削減率: 約60%の削減を実現
- 変動幅の安定化: 月ごとの大きな変動がなくなり、予算管理が容易に

パフォーマンス面での副次効果
コスト削減以外にも、以下の効果が確認できました:
- 体感速度の向上: ブラウザキャッシュの活用により、画像の表示速度が向上
- ユーザー体験の改善: ページ読み込み時間の短縮により、ユーザー体験が向上
-
ギガの節約!:
max-ageによりクライアントにキャッシュされるようになったので通信量が抑制
まとめ
キャッシュはめっちゃ大切
CloudFrontのキャッシュ設定は、適切に設定されていない場合に予想以上のコスト増大を招きお財布が厳しくなります。
AWSと言う巨人の肩に乗っているので、お財布が厳しいだけにも思えますが、Webアプリケーションでも同様です
1日でそんなに情報が変わらないページなどはうまくキャッシュを活用することで、サーバーの負荷を大きく抑えられるので、キャッシュは滅茶苦茶大切です
もっとコストを抑えたい!
今回の改善により相当、コストやパフォーマンスが改善されました
しかし、もっとコストを抑えられたらベンチャーなどの弱小企業としては嬉しいです
主DNSをCloudflareとして活用し、Cloudflareにキャッシュがない場合や、Cloudflareに障害が発生した場合、CloudFrontをフェイルーバーにするような構成を取ることで、もっとコスト最適化が可能です。
Discussion