こんにちは、InflabのDevOpsエンジニアのJakeとGoofyです。
Inflabは最近、Amazon S3の保管コストとCDNトラフィックの最適化で成果を上げました。具体的には、S3 Intelligent-Tieringストレージクラスの導入と、CDNで配信する画像のAVIF・MP4フォーマット変換への対応です。
Inflabが何を問題と捉え、どのように最適化したのか、そして実際に移行する中で考慮した点と具体的な経験をあわせて紹介します。
ささやかなノウハウではありますが、皆さんのお役に立てれば幸いです。それでは始めましょう。
Amazon S3のコストを節約する
なぜコストがかさむのか?
Inflearnでは、膨大な数の講座動画をAmazon S3バケットに保管しています。講師の方がアップロードした元の動画と、それを受講者の方が再生するのに最適な品質・サイズにエンコードしたバージョンの両方です。
VODサービスでは、クライアントのネットワーク環境に適した動画を配信できるよう、動画を解像度ごとにエンコードします。主に540p、720p、1080pなど、さまざまな解像度に合わせて動画を用意する必要があります。
オーディオトラックも別途管理しています。音量が不安定な動画は、視聴時に大きなストレスになることがあります。そこで利用者の利便性を考え、VODパイプラインではすべてのオーディオトラックに音量の正規化(volume normalization)をかけています。
speech normalization
最近では、再生時の利便性のために各区間のフレームをプレビューできるサムネイルスプライト画像もVODパイプラインで生成し始めたことで、以前よりはるかに多くのオブジェクトを管理するようになりました。
thumbnail sprite
VODパイプラインで処理するデータやサポートする機能が増えるほど通過する段階も増え、各段階で大量のファイルが生成されます。CDNのアクセスログのような大量のログもS3バケットに継続的に蓄積されており、日を追うごとにS3の保管コストが目に見えて増えていきました。
そこで、ユーザー体験に大きな影響を与えずにS3の保管コストを削減できる方法を模索することになりました。
S3ストレージクラス
Amazon S3は、複数の種類のストレージクラスをサポートしています。保管コストと取得時間をトレードオフできる選択肢を提供しているわけです。オブジェクトを素早く取得したければ、高い保管コストを支払う必要があります。一方、あまりアクセスしないファイルであれば、安く保管して、取得に時間がかかることを受け入れるのです。
簡単に言えば、ハードディスクより保管コストが安い磁気テープ(magnetic tape)にたとえられます。保管コストが安い代わりに、読み書きに時間がかかる仕組みです。
よく使われる主なストレージクラスは次のとおりです。
-
Standard:一般的なデータ、即時アクセスが必要 -
Intelligent-Tiering:アクセスパターンに応じて自動で最適化 -
Glacier:長期保管、復元(Retrieved)に時間が必要(数分〜数時間) -
Deep Archive:最も安価、復元(Retrieved)に非常に長い時間がかかる(数時間〜数日)
4〜5年以上保管する必要があるものの、アクセスは数年に一度程度というログの遠隔地バックアップなど、オブジェクトの特性によっては Glacier や Deep Archive クラスも検討するとよいでしょう。
(データの復元にはかなりの時間とコストがかかるため、トレードオフをよく計算する必要があります。オブジェクトにユーザーのアクセスが必要なユースケースであれば、続いて紹介する
Intelligent-Tieringで十分です。)
数ミリ秒以内に応答すべき静的ファイルをS3からCDN経由で配信する場合や、ログファイルの遠隔地バックアップのためのアーカイブなど、さまざまなユースケースに合わせてストレージクラスを切り替えれば、コストを節約できます。
しかし、これをアップロード時に一つひとつ管理するのは簡単ではないでしょう。そのため、Lifecycle configurationを使って構成するのが一般的です。
S3 Lifecycle configuration
Amazon S3は、オブジェクトの管理を自動化できるよう、Lifecycle configurationという機能を提供しています。
- オブジェクトのアップロード時点からn日経過後にストレージクラスを変更
- 以前のバージョンのオブジェクトのストレージクラスを変更
- n日経過後にオブジェクトを失効(expire)、つまり完全に削除
このようにさまざまな機能があり、オブジェクト名のプレフィックス(prefix)やタグ、ファイルサイズでフィルターを設定することもできます。
うまく応用すれば、アップロードから30日経過後はA、90日経過後はBのストレージクラスに自動で切り替わるように構成できます。
とても便利な機能ですが、次の点には注意が必要です。
- オブジェクトのストレージクラスを変更する際には、API呼び出しの料金がかかります。
- オブジェクトの作成日単位でしかクラスを変更できないため、頻繁にアクセスされる
hotなオブジェクトと、たまにアクセスされるcoldなオブジェクトを区別して管理するのが難しくなります。
難しい理由
すべてのオブジェクトの利用パターンを把握していて、変化が少ない場合は、自分でLifecycle ruleを設定するのがよいでしょう。しかし実際にサービスを運用していると、オブジェクトのアクセスパターンを予測するのは非常に難しいものです。
一部のストレージクラスには最低保管期間が設けられており、この期間が過ぎる前にGetObjectを呼び出してそのオブジェクトを取得すると、追加料金が発生します。(GBあたりの取り出し(Retrieval)料金が発生)
また、30日経過、90日経過など、区間ごとにストレージクラスを切り替えるたびに追加料金が発生します。
- Intelligent-TieringクラスへのLifecycle移行時:1000リクエストあたり$0.01
- S3 Glacier Instant RetrievalクラスへのLifecycle移行時:1000リクエストあたり$0.02
InflabのDevOpsチームでも、こうした理由からS3のコスト最適化にはなかなか手を出せずにいました。しかし最近、ストレージコストがTop 10に入るほど負担が大きくなってきました。
そんな中、Intelligent-Tieringストレージクラスなら移行料金がかからず、複雑な設定もなしに自動で保管コストを削減できると知り、すぐに導入を決めました。
S3 Intelligent-Tiering
端的にお伝えします。次の条件に当てはまるなら、今すぐS3 Intelligent-Tieringを導入することを強くおすすめします。
- バケットに主に保管しているオブジェクトの大半が128KBを超えている。(Intelligent-Tieringのティア移行は、128KBを超えるファイルにのみ適用されるためです。)
- コールドストレージであるにもかかわらず、ときどきランダムアクセスがあるため、すべてのオブジェクトを数ミリ秒以内に取得できる必要がある。
- S3バケットにアクセスする別のキャッシュレイヤーがある。
(CDN-CloudFront/Cloudflare、WebServer-Nginx/Caddyなど) - オブジェクトが多すぎて、どのようなパターンでアクセスされているかをすべて把握するのが難しい。
リクエストが特定のオブジェクトに偏りがちなので、オブジェクト単位でストレージクラスを管理する必要がある。
Intelligent-Tieringは、ファイル数に応じた管理料金を支払うだけで、自動的にストレージクラスを管理し、保管コストを節約してくれます。複雑なlifecycle ruleを設定する必要はありません。クラスの移行料金や取り出し(retrieval)料金もかかりません。(!!)
基本的に、アップロードから30日間アクセスがなければ IA ティアに、90日間アクセスがなければ AIA ティアに自動で移行してくれます。(本記事では、Intelligent-Tiering Archive configurationsで設定できるDeep Archive Accessティアは扱いません。)
-
IA- Infrequent Access
- 頻繁にはアクセスしないティア
- ミリ秒単位のアクセス
- $0.0138 per GB (in Seoul)
- Standardより44.8%安い
-
AIA- Archive Instant Access
- 即時アクセス可能なアーカイブティア
- ミリ秒単位のアクセス
- $0.005 per GB (in Seoul)
- Standardより80%安い
IAティアとAIAティアは、Standardクラスと比べて保管コストがそれぞれ44.8%、80%も安くなります。ただし、続いて紹介する移行料金や、オブジェクト数に応じた管理料金もかかるので、しっかり計算する必要があります。
Lifecycle ruleで自分でストレージクラスをティアリングする場合と比べてみましょう。頻繁に使うオブジェクトも同じポリシーでクラスがダウングレードされるため、取得や復元の料金がばかになりません。ランダムアクセスの要件がある場合は、適用が難しくなります。
では、私たちのような顧客にIntelligent-Tieringのような機能を提供して、Amazonには何が残るのでしょうか?そのためか、オブジェクトのモニタリングとティア管理の自動化という名目で、1000オブジェクトあたり$0.0025の料金がかかります。それを払っても、ストレージコストの節約分のほうがはるかに大きくなります。
ストレージクラスは、オブジェクトのアップロード(PutObject)時に指定することも、lifecycle ruleの設定で変更することもできます。
Lifecycle ruleで指定すれば、複雑な手順なしにバケット内のすべてのオブジェクトを一度に移行できるので便利です。
新しいファイルをアップロードするのであれば、次のようにPutObject APIを実行することで移行料金を節約できます。あらかじめストレージクラスを指定しておけば、移行する必要がないからです。
import boto3
# ファイルのアップロード時にIntelligent-Tieringストレージクラスを指定
s3_client = boto3.client("s3")
s3_client.put_object(
Bucket="YOUR_BUCKET_NAME",
Key="video.mp4",
Body=open("/tmp/video.mp4", "rb"),
StorageClass="INTELLIGENT_TIERING",
)
では、もしS3バケットに数十から数百万、いや数百億個ものオブジェクトがすでにある場合は、どうすればよいでしょうか?ストレージクラスの移行にかかる料金が負担にならないでしょうか?
一度にすべてのオブジェクトをIntelligent-Tieringクラスに移行する必要はありません。
オブジェクトサイズが大きく、アクセスの少ないバケットから、prefixやオブジェクトサイズの条件でLifecycle ruleを指定すればよいのです。アップロード直後に、条件に合うオブジェクトをIntelligent-Tieringストレージクラスに移行するよう構成できます。
導入コスト
Intelligent-Tieringを導入すると、大きく2つのコストが追加されます。
-
オブジェクトの管理料金
1000オブジェクトあたり$0.0025の料金がかかります。
-
(Lifecycle設定時)Lifecycle移行リクエストの料金(Lifecycle transition requests)
1000オブジェクトあたり$0.01の料金がかかります。
(PricingページのRequests & data retrievalsの項目に記載されています。)
Lifecycle移行リクエストの料金は、ルールで既存のオブジェクトを移行するときにだけ発生し、その後のIntelligent-Tieringクラス内でのティア移動には追加料金はかかりません。
理解を深めるために、一緒に計算してみましょう。
約10万個のオブジェクトがあるバケットを、現在すべてStandardクラスで保管しているとします。この場合、Intelligent-Tieringクラスへの移行にかかる料金は次のとおりです。
(Lifecycle移行料金) = (オブジェクト数) / 1000 * $0.01
(10万個のオブジェクトがあるバケットのI.T.移行料金) = 100_000 / 1000 * $0.01 = $1
移行料金はオブジェクトの数に比例するため、オブジェクト数が少なく、ファイルサイズが大きい場合に有利です。
先ほど、利用パターンに合っているかを確認する必要があるとお伝えしたのは、このためでもあります。
バケットの種類によって、導入効果がまったく異なるからです。
そのため、最初からすべてのバケットに適用するよりも、少しずつ適用しながらコストをモニタリングするのがよいでしょう。
CopyObjectを直接呼び出す方法
ここで、Intelligent-Tiering導入時のLifecycle移行料金を節約する方法がもう1つあります。
Lifecycleルールを設定すると、S3は内部的にオブジェクトを新しいストレージクラスに移動させ、その際に「移行リクエスト」が発生します。
この処理は実質的にCopyObject APIによる内部コピーと似た動作なので、同じことを自分でCopyObjectを呼び出して実現することもできます。
この場合、Lifecycle移行リクエストと比べて約2.2倍安く処理できます。
- ListObjects:1000件あたり$0.005(全件の一覧取得時に数回呼び出すだけ)
- CopyObject:1000件あたり$0.0045(オブジェクト数だけ呼び出す)
また、同じバケット内でのCopyObjectには、データ転送料金(Data Transfer Cost)がかかりません。S3の料金体系では、インターネットやリージョン間の転送にのみ料金が発生し、同じバケットまたは同じリージョン内の転送は無料です。
つまり、CopyObject方式はトラフィック料金が0で、Lifecycle移行よりもリクエスト単価が安いため、総コストを抑えられます。
オブジェクト数が多く移行料金が負担になる場合は、ListObjects → CopyObjectの流れで手動移行する方法も十分検討に値します。
これも一種のトレードオフなので、管理しているオブジェクトの数をよく検討してみてください。
実施内容と成果
そこで私たちは、まずオブジェクト数をもとに移行料金を試算しました。
今後、保管コストを劇的に削減できると見込んでの取り組みでした。
90日間アクセスされないオブジェクトが過半数を占めるパターンだったので、最大80%まで保管コストを削減できるからです。
次の手順で、ほとんどのオブジェクトにIntelligent-Tieringを適用しました。
-
新しくアップロードされるオブジェクトから、PutObject呼び出し時にIntelligent-Tieringを適用しました。
ストレージクラスの移行料金を節約するためです。 -
元の動画を保存するアーカイブ的な性格のバケットは、気兼ねなく一括適用できたので、CopyObjectで全体を処理しました。
-
エンコード結果を保存するバケットは小さいファイルが多いため、Lifecycle ruleを使ってファイルサイズごとに段階的に適用しました。
ストレージクラス移行のAPI呼び出し料金はモニタリングしながら適用しました。
90日連続でアクセスされなかったオブジェクトは、最も低いティアであるArchive Instant Accessティアに移動します。そのため適用から3〜4か月が経つと、あまりアクセスされないオブジェクトの保管料金が下がり、38%のコストを削減できました。
S3のコスト削減
S3 Inventoryの活用
すべてのケースが私たちと同じとは限らないでしょう。ご自身のユースケースをよく把握し、オブジェクト数が非常に多く取得頻度も高いのか、移行料金はどのくらいか、オブジェクトの管理料金はどうなるのかを、しっかり計算してみてください。
おおよそのオブジェクト数とバケットサイズは、Webコンソールでも簡単に確認できます。MetricsタブのTotal bucket size、Total number of objectsの指標を見れば分かります。
では、もしS3バケットに大小さまざまなオブジェクトが混在している場合はどうすればよいでしょうか?その場合は、S3 Inventoryを使えばよいのです。
S3 Inventoryは、バケット内のすべてのオブジェクトのメタデータを、定期的にレポート形式で生成する機能です。オブジェクトキー、サイズ、最終更新日、ストレージクラスなどの情報を、CSV、ORC、Parquet形式で提供します。
バケットのManagementタブでInventory configurationsを設定すると、毎日または毎週レポートが自動生成されます。生成されたレポートは、Athenaを使ってSQLクエリで分析できます。
例えば、次のようなクエリで128KBを超えるファイルの数を確認できます。
SELECT
COUNT(*) as object_count,
SUM(size) / 1024 / 1024 / 1024 as total_size_gb
FROM s3_inventory_table
WHERE size > 128 * 1024;
Inflabでも導入前に、128KBを超えるオブジェクトが何個あるか、想定される移行料金はいくらかを見積もってから作業に取りかかりました。おかげで、想定した金額の範囲内でスムーズに移行できました。
オブジェクトの保管パターンを把握しにくい場合は、S3 Inventoryの活用もおすすめします。
画像CDNトラフィックの最適化
なぜコストがかさむのか?
動画の保管だけでなく、サービスのあちこちで使われる画像のCDNレスポンストラフィックも、最適化の対象でした。
Inflearnのトップページでは、数十個の講座サムネイル画像を表示しています。講座詳細ページ、質問・回答ページ、検索結果ページなど、サービスのほぼすべての場所に講座のサムネイルが登場します。
画像が多いほど、ページの読み込みに時間がかかります。ユーザーが最初にアクセスしたときにダウンロードすべき容量が大きいと、ページが遅く感じられ、それはそのままユーザー体験の低下につながります。
コストの面でも負担になっていました。CDNトラフィックが増えるほどコストも増えますが、画像を多用するサービスの特性上、CDNレスポンストラフィックの料金はばかになりませんでした。
このように画像を多用するサービスでは、画像の最適化によってCDNトラフィックのコストを削減し、ユーザー体験も改善できます。
https://www.inflearn.com
Lambda@Edge画像リサイザー
一覧では小さな画像が必要で、詳細ページでは大きな画像が必要です。元の画像をそのまま返す代わりに、用途に合ったサイズと最適化されたフォーマットに変換すれば、トラフィックを削減できるはずです。
このように、状況に応じたリサイズと変換を適用するための変換器が必要です。
CloudFrontを使っているなら、AWSが公開しているサンプルコードをもとに、Lambda@Edgeベースのon-the-fly画像リサイザー&フォーマット変換器を簡単に構築できます。
Lambda@Edge
では、Lambda@Edgeとは何でしょうか?
Lambda@Edgeは、CloudFrontのエッジロケーションでLambda関数を実行できるサービスです。ユーザーに近い場所でコードを実行するため、レイテンシーを短縮できます。
Lambda@Edge関数は、CloudFrontの4つのイベントに関連付けることができます。
Viewer Request → Origin Request → Origin Response → Viewer Response
↑ ↓
ユーザー ユーザー
Origin Responseを使うと、オリジン(S3)から元の画像を受け取った直後に処理することになります。そのため、キャッシュミスのときにだけLambda関数が実行され、変換された画像がCloudFrontにキャッシュされます。以降の同じリクエストはキャッシュから即座に返されるので、Lambda関数は実行されません。
画像のリサイズにOrigin ResponseのLambda@Edgeを使えば、S3にあらゆるサイズの画像をあらかじめ保存しておかなくても、リクエスト時に必要なサイズへ簡単に変換して提供できます。
Lambda@Edgeは、リクエスト数と実行時間で課金されます。
- リクエスト数:100万件あたり$0.60(ソウルリージョンの場合)
- 実行時間:メモリ128MBの場合、1msあたり$0.00000625125
Lambda@EdgeはCloudFrontのキャッシュと組み合わされるため、キャッシュヒット率が高いほど、実際のLambdaの実行コストは下がります。例えば、キャッシュヒット率が90%なら、Lambdaのコストは10%分しかかかりません。S3にあらゆるサイズの画像をあらかじめ生成して保存しておくよりも効率的です。
AVIF変換の追加
AWSのサンプルコードではJPGとWebPしかサポートしていませんが、CDNトラフィックをさらに削減する方法を探す中で、AVIFフォーマットに注目しました。
AVIF(AV1 Image File Format)は、動画コーデックAV1をもとに作られた画像フォーマットです。WebPより約20〜30%小さいファイルサイズで、同等の品質を実現できます。
実際にInflearnの講座サムネイル画像を変換してみた結果は、次のとおりでした。
画像の種類によって、変換後の容量は異なる場合があります。
| フォーマット | ファイルサイズ | 削減率 |
|---|---|---|
| 元のJPG | 100 KB | - |
| WebP | 68 KB | 32% |
| AVIF | 55 KB | 45% |
テスト結果でも、AVIFフォーマットの圧縮率が最も高いことが確認できました。
幸い、画像リサイザーで使っているSharpライブラリはAVIFをサポートしていました。Sharpは内部的にlibvipsを使っており、libvips v8.9.0からlibheifを通じてAVIFのエンコードをサポートしています。
そのため、わずかなコードの変更で最適化を適用できました。Lambda@Edge環境でも問題なく動作することを確認したうえで、許可するフォーマットに追加しました。
const allowedFormats = ['jpg', 'jpeg', 'png', 'webp', 'avif'];
...
sharp(imageBuffer)
.resize({width})
.toFormat(format, { quality: 80 });
これで、?w=960&f=avif の形でリクエストすると、AVIFフォーマットに変換された画像が返されます。
ただし、AVIFはWebPよりエンコードに1.5倍ほど時間がかかります。画像の特性やサイズによって差はありますが、一般的により多くのCPU演算が必要です。それでも、CloudFrontのキャッシュによって最初の1回だけ変換すればよいので、大きな問題にはなりませんでした。
ブラウザーのサポート
AVIFは、Chrome 85+(2020年)、Safari 16+(2022年)からサポートされるようになりました。2025年現在、グローバルなブラウザーシェアで見ると、約90%以上がソフトウェアでAVIFをサポートしています。
しかし、ハードウェアデコードのサポートは限定的です。Apple M3+など最新のチップセットでしかハードウェアアクセラレーションをサポートしておらず、それ以外のデバイスではCPUでデコードするため、バッテリー消費が大きくなる可能性があります。特に低スペックのモバイル端末では、デコードに時間がかかることがあります。
こうした理由から、フロントエンドで <picture> タグを使ってフォールバックを構成することが重要です。
<picture>
<source srcset="inflearn-logo.jpg?f=avif" type="image/avif">
<source srcset="inflearn-logo.jpg?f=webp" type="image/webp">
<img src="inflearn-logo.jpg" alt="Description" width="360" height="240">
</picture>
ブラウザーは、上から順にサポートしているフォーマットを選択します。AVIFをサポートしていればAVIFを、サポートしていなければWebPを、どちらもサポートしていなければ元のJPEGをリクエストします。こうすることで、すべてのブラウザーで互換性を保ちつつ、最新のブラウザーでは最適化されたフォーマットの恩恵を受けられます。
GIFトラフィックの最適化
一方、講座のサムネイルやコミュニティの投稿では、アニメーションGIFがアップロードされることもあります。GIFは256色に制限されているうえに圧縮効率が低く、ファイルサイズが大きくなりがちです。特に、高画質の動画をGIFに変換すると、容量が急激に増えます。
動く画像を最適化するために、いくつかのフォーマットへの変換をテストしてみました。FFmpegを使って実際のGIFファイルを変換した結果は、次のとおりでした。
| フォーマット | ファイルサイズ | 削減率 |
|---|---|---|
| 元のGIF | 1000 KB | - |
| WebP (animated) | 860 KB | 14% |
| AVIF (animated) | 281 KB | 72% |
| MP4 (H.264) | 322 KB | 68% |
画像と同様にAVIFの圧縮率が最も高く、MP4はAVIFより少し大きい程度でした。
Sharp、Lambda@Edgeの考慮事項
しかし、いざ適用しようとすると、いくつか難しい点がありました。
Sharpでアニメーションの変換をサポートしているフォーマットはWebPだけです。animated AVIFへの変換はサポートしておらず、GIFの最初のフレームしか抽出できません。これは、AVIFのアニメーションが比較的最近標準化されたもので、Sharpが依存しているlibheifライブラリのサポートが限定的なためです。(ドキュメント、 関連Issue)
さらに、Lambda@Edgeはエッジロケーションで実行される環境のため、制約が多くあります。
- パッケージサイズ:最大50MB
- 実行時間:Origin Responseは最大30秒
- メモリ:最大3008MB
- レスポンスサイズ:Origin Responseは最大1MB
MP4に変換するにはFFmpegが必要ですが、バイナリサイズが60〜80MBにもなり、パッケージサイズの制限(50MB)を超えてしまいます。
ほかのフォーマットに変換する場合でも、特に問題になったのはレスポンスサイズと実行時間の制限でした。InflearnにアップロードされるGIFの中には数MBに達するものもあり、WebPに変換しても結果が1MBを超えることがたびたびありました。アニメーションのフレーム数が多かったり解像度が高かったりするGIFは、処理にも時間がかかり、30秒の制限を超えることもありました。
こうした理由から、すべてのGIFをLambda@Edgeでリアルタイムに変換するのは無理があると判断しました。
MP4変換Lambda
Lambda@Edgeはリアルタイム変換には向いていなかったため、アップロード時にあらかじめ変換しておく方式を選びました。
通常のAWS Lambdaは、Lambda@Edgeとは異なり、大きなGIFファイルを処理するのに十分な環境です。FFmpegのような大きなバイナリも、AWSが提供しているLambda Layerを活用すれば簡単に使えます。
構成したLambdaの動作は次のとおりです。
- ユーザーがGIFファイルをS3バケットにアップロード
- S3が
s3:ObjectCreated:*イベントを発行 - Lambda関数が自動でトリガーされる
- LambdaがGIFをダウンロードし、FFmpegでMP4に変換
- 変換したファイルを
.mp4の拡張子でS3に保存
非同期で処理されるため、変換が完了すると自動的に最適化されたMP4が表示されるようになります。最初のアップロード時には多少の遅延がありますが、一度変換しておけばすべてのユーザーが最適化されたファイルを受け取れるため、長期的にCDNトラフィックを削減できます。
フロントエンドでは、MP4が存在する場合は <video> タグで表示します。
<video muted loop autoPlay playsInline>
<source src={gifUrl} type="video/mp4" />
</video>
この方式は、最初のアップロード時に多少の遅延がありますが、一度変換しておけばすべてのユーザーが最適化されたファイルを受け取れるため、長期的にCDNトラフィックを大きく削減できます。
ただし、状況に応じてGIFを小さなサムネイルとして表示する場合は、前述のLambda@Edgeリサイザーを使っています。アニメーションが必要かどうかに応じて、適切に選択できるでしょう。
実施内容と成果
画像フォーマットの切り替えは、フロントエンドとの緊密な連携が必要な作業でした。
すべてのページに一括で適用するのではなく、トラフィックの多い主要なページから順に、効果を検証しながら段階的に適用しました。
なかでも、Inflearnの講座一覧ページやRallitの求人一覧ページのように、多数のサムネイル画像を同時に表示するページで、最も大きな改善効果が確認できました。
画像CDNでは、AVIFフォーマットの適用後、画像のレスポンスサイズ(P90値)が適用前と比べて平均で約15%減少し、その後も全体的に低い水準を維持しています。特にRallitでは、ユーザーの体感パフォーマンスを表すLCP(Largest Contentful Paint)の指標も明確に改善しました。
- デプロイ前2日間の平均:LCP 1.16秒
- デプロイ後2日間の平均:LCP 0.87秒
画像CDNのリクエストあたりのレスポンスサイズ P90
モニタリング
最適化の作業を終えた後も、継続的なモニタリングが重要です。
今回の最適化も、普段からページの読み込み速度とCDNトラフィックの推移を欠かさずチェックしてきたおかげで実現できました。トラフィックが増加するパターンを見つけ、どこを改善すればよいかをデータに基づいて判断できました。
皆さんも、モニタリングツールを積極的に活用して、トラフィックの変化やリソースのボトルネックをチェックすることをおすすめします。
DatadogのRUM機能を使っているなら、ユーザーのRUM Sessionで読み込まれたリソースのうち、時間がかかったりサイズが大きかったりするリクエストをクエリして、ダッシュボードを構成できます。
Datadog RUM Resources Query
RUM Resource Dashboard
CloudFrontのログをAthenaでクエリしているなら、次のようにGrafana Dashboardを構成できます。
AthenaをDatasourceとするGrafana Dashboard
おわりに
Inflabで取り組んだS3保管コストとCDNトラフィックの最適化について紹介しました。内容をまとめると、次のとおりです。
S3 Intelligent-Tieringは、複雑な設定なしに自動でストレージクラスを管理する機能です。オブジェクト数とサイズのパターンさえきちんと把握すれば、コスト削減効果を簡単に見積もれます。私たちは移行料金をあらかじめ計算し、バケットごとに順次適用することで、38%のコストを削減できました。
画像CDNトラフィックの最適化では、Lambda@EdgeリサイザーにAVIFフォーマットのサポートを追加するだけでも、大きな効果が得られました。フロントエンドと連携して主要なページから適用し、画像のリクエストあたりのレスポンスサイズP90が15%以上改善しました。GIFをMP4に変換するLambdaもあわせて適用し、アニメーションコンテンツの容量を大きく削減できました。
最初は複雑に見え、リスクも大きそうに思えましたが、小さなところから始めて段階的に広げていったことで、無理なく進められました。
もちろん、本記事で紹介した方法がすべての状況で正解とは限りません。私たちの現在の規模と利用パターンに合っていると判断して適用した事例です。皆さんのオブジェクトサイズの分布やアクセスパターン、トラフィックの規模によっては、別の方法のほうが適している場合もあるので、状況に合わせて選択されることをおすすめします。
本記事が、皆さんのコスト最適化に少しでもお役に立てば幸いです。ありがとうございました。
参考資料
- Amazon S3 Storage Classes
- S3 Intelligent-Tiering
- S3 Lifecycle Configuration
- S3 Inventory
- Lambda@Edge Image Resizing Sample
- Sharp Image Processing Library
- AVIF Browser Support
※この記事は2025年10月29日に執筆されたものです。