CloudFrontのキャッシュを即時クリアする — S3ファイル更新後のInvalidation完全ガイド
S3のファイルを差し替えたのに、CloudFrontが古いバージョンを返し続ける。デプロイ直後にこの状況に直面したエンジニアは多い。原因はCloudFrontのエッジキャッシュにある。CloudFront Invalidationを使えばエッジロケーションのキャッシュを即時パージできるが、実行タイミングや対象パスの指定を誤ると、コストが無駄になるか、キャッシュが残り続ける。
TL;DR — CloudFront Invalidationの要点
| 項目 | 内容 |
|---|---|
| 問題 | S3更新後もCloudFrontが古いオブジェクトを返す |
| 原因 | エッジロケーションにキャッシュされたオブジェクトのTTLが残っている |
| 解決策 | Invalidationリクエストで対象パスのキャッシュを強制削除 |
| 注意点 | 月1,000パス超は有料。ワイルドカード/*は1パスとしてカウント |
| 推奨アプローチ | ファイル名バージョニング + Invalidationの組み合わせ |
CloudFrontのキャッシュ動作を理解する
CloudFrontはオリジン(S3など)から取得したオブジェクトを、世界中のエッジロケーションにキャッシュする。ビューワーからのリクエストはオリジンではなく最寄りのエッジに到達し、キャッシュヒットすればオリジンへの通信は発生しない。
キャッシュの有効期間はCache-Controlヘッダー(max-age)またはCloudFrontのディストリビューション設定(デフォルトTTL / 最大TTL)によって決まる。S3でファイルを上書きしても、エッジのキャッシュエントリが生きている限りCloudFrontは古いオブジェクトを返し続ける。TTLが切れるのを待つか、Invalidationで強制削除するかの二択だ。
(ブラウザ)"] Edge["エッジロケーション
(CloudFrontキャッシュ)"] Origin["オリジン
(S3バケット)"] Invalidation["Invalidationリクエスト
(キャッシュ削除)"] Viewer -->|"HTTPリクエスト"| Edge Edge -->|"キャッシュヒット: TTL内"| Viewer Edge -->|"キャッシュミス: オリジンフェッチ"| Origin Origin -->|"オブジェクト返却 + キャッシュ格納"| Edge Invalidation -->|"指定パスのキャッシュを削除"| Edge
- ビューワーリクエスト — ユーザーのブラウザが最寄りのエッジロケーションにHTTPリクエストを送る。
- キャッシュヒット — TTL内のオブジェクトが存在すれば、エッジがそのままレスポンスを返す(オリジンへの通信なし)。
- キャッシュミス / Invalidation後 — エッジにオブジェクトがない場合、オリジン(S3)からフェッチしてキャッシュに格納する。
- Invalidation実行 — 指定パスのキャッシュエントリを全エッジロケーションから削除。次のリクエストは必ずオリジンフェッチになる。
CloudFront Invalidationの作成方法
Invalidationはマネジメントコンソール、AWS CLI、SDKのいずれからでも実行できる。本番環境ではCIパイプラインからCLIで自動実行するケースが多い。
AWS CLIでInvalidationを作成する
特定ファイルのキャッシュを削除する場合は対象パスを明示する。ディストリビューションID(E1234567890ABCの形式)は事前に確認しておく。
# 特定パスを指定してInvalidationを作成
aws cloudfront create-invalidation \
--distribution-id E1234567890ABC \
--paths "/images/hero.jpg" "/css/style.css"
全オブジェクトを一括削除する場合はワイルドカードを使う。ワイルドカード/*は1パスとしてカウントされる。
# 全オブジェクトを対象にInvalidation(ワイルドカード)
aws cloudfront create-invalidation \
--distribution-id E1234567890ABC \
--paths "/*"
Invalidationの完了を待機してから次の処理に進みたい場合(デプロイスクリプトなど)はwaitサブコマンドを使う。
# Invalidation IDを取得して完了を待機
INVALIDATION_ID=$(aws cloudfront create-invalidation \
--distribution-id E1234567890ABC \
--paths "/*" \
--query 'Invalidation.Id' \
--output text)
aws cloudfront wait invalidation-completed \
--distribution-id E1234567890ABC \
--id "$INVALIDATION_ID"
echo "Invalidation completed: $INVALIDATION_ID"
ディストリビューションIDを確認するコマンド
ディストリビューションIDが手元にない場合は以下で一覧を取得できる。
aws cloudfront list-distributions \
--query 'DistributionList.Items[*].{ID:Id,Domain:DomainName,Status:Status}' \
--output table
マネジメントコンソールから実行する手順
- CloudFrontコンソールを開き、対象のディストリビューションを選択する。
- 'Invalidations'タブ → 'Create invalidation'をクリック。
- 対象パスを入力する(例:
/images/hero.jpgまたは/*)。 - 'Create invalidation'ボタンで実行。ステータスが'Completed'になるまで数分かかる。
Invalidationの完了状態を確認する
Invalidationはリクエスト送信後、全エッジロケーションへの伝播が完了するまで時間がかかる。通常は数分以内に完了するが、完了前にアクセスしたエッジでは古いキャッシュが返る可能性がある。
# Invalidationの一覧と状態を確認
aws cloudfront list-invalidations \
--distribution-id E1234567890ABC \
--query 'InvalidationList.Items[*].{ID:Id,Status:Status,CreateTime:CreateTime}' \
--output table
# 特定のInvalidationの詳細を確認
aws cloudfront get-invalidation \
--distribution-id E1234567890ABC \
--id INVALIDATION_ID_HERE
実際の現場で起きた誤診 — パスの指定ミス
Invalidationを実行したのにキャッシュが消えない、というケースがある。ログを見るとStatus: Completedになっているのに、ブラウザはまだ古いファイルを返している。
最初はCloudFrontの伝播遅延を疑う。しかしcurl -Iでレスポンスヘッダーを確認するとX-Cache: Hit from cloudfrontが返ってくる。Invalidationが完了しているのにキャッシュヒットしている。
実際の原因はパスの指定ミスだった。S3のオブジェクトキーがassets/js/app.jsなのに、Invalidationに指定したパスが/js/app.jsだった。CloudFrontのビヘイビア設定でオリジンパスプレフィックス(/assets)が設定されていたため、ビューワーからは/js/app.jsでアクセスできるが、Invalidationに指定するパスはビューワーリクエストのURLパスと一致させる必要がある。つまり/js/app.jsが正しく、S3のキーパスではない。
Invalidationのパスはビューワー(ブラウザ)がリクエストするURLのパスと一致させる。S3のオブジェクトキーではない。オリジンパスが設定されている場合は特に注意が必要だ。
/js/app.js"] Behavior["CloudFrontビヘイビア
オリジンパス: /assets"] S3Key["S3オブジェクトキー
assets/js/app.js"] InvalidOK["正しいInvalidationパス
/js/app.js"] InvalidNG["誤ったInvalidationパス
/assets/js/app.js"] ViewerReq --> Behavior Behavior --> S3Key ViewerReq -->|"一致: キャッシュ削除成功"| InvalidOK ViewerReq -->|"不一致: キャッシュ残存"| InvalidNG style InvalidOK fill:#2d6a4f,color:#fff style InvalidNG fill:#9b2226,color:#fff
- ビューワーパス — ブラウザが
/js/app.jsをリクエスト。これがInvalidationに指定すべきパス。 - オリジンパスプレフィックス — CloudFrontのビヘイビア設定で
/assetsが付与される。 - S3オブジェクトキー — 実際にS3に格納されているキーは
assets/js/app.js。 - 誤ったInvalidation —
/assets/js/app.jsを指定してもキャッシュは削除されない。
コスト最適化 — Invalidationを乱用しない設計
CloudFrontのInvalidationは月1,000パスまで無料で、超過分は有料になる。ワイルドカード/*は1パスとしてカウントされるため、全体をパージする場合はコスト効率が良い。ただし、頻繁に/*を実行する設計は根本的な問題を示している。
本番環境で推奨されるアプローチはファイル名バージョニングだ。app.jsではなくapp.v2.1.0.jsやapp.abc123.js(コンテンツハッシュ)のようにファイル名にバージョンを含める。新バージョンは別のURLになるため、Invalidationが不要になる。HTMLファイルのみTTLを短く設定するか、Invalidationで更新する。
| アプローチ | Invalidation頻度 | キャッシュ効率 | 適用場面 |
|---|---|---|---|
| ファイル名バージョニング | HTMLのみ | 高い | 静的サイト、SPAのビルド成果物 |
| 特定パスのInvalidation | 更新ファイルのみ | 中程度 | 更新頻度が低いアセット |
ワイルドカード/* | デプロイごと | 低い(全キャッシュ消失) | 緊急時のフルパージ |
| TTL短縮 | 不要 | 低い(キャッシュ効果減) | 頻繁に更新されるコンテンツ |
必要なIAMパーミッション
CIパイプラインやLambdaからInvalidationを実行する場合、最小権限の原則に従って以下のポリシーを付与する。
🔽 IAMポリシー例(クリックして展開)
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowCloudFrontInvalidation",
"Effect": "Allow",
"Action": [
"cloudfront:CreateInvalidation",
"cloudfront:GetInvalidation",
"cloudfront:ListInvalidations"
],
"Resource": "arn:aws:cloudfront::123456789012:distribution/E1234567890ABC"
},
{
"Sid": "AllowListDistributions",
"Effect": "Allow",
"Action": "cloudfront:ListDistributions",
"Resource": "*"
}
]
}
cloudfront:ListDistributionsはリソースレベルの制限をサポートしていないため"Resource": "*"が必要になる。AWS Service Authorization Referenceで確認できる。
CloudFront Invalidationのトラブルシューティング手順
-
キャッシュヒット状態を確認する
まずレスポンスヘッダーを見て、本当にCloudFrontのキャッシュが原因かを確認する。オリジン側の問題(S3のファイルが実際には更新されていない)を先に排除する。curl -sI https://d1234567890abc.cloudfront.net/images/hero.jpg | grep -i 'x-cache\|age\|cache-control'X-Cache: Hit from cloudfrontが返ればエッジキャッシュが原因。X-Cache: Miss from cloudfrontならオリジンから取得しているため、S3側を確認する。 -
S3のオブジェクトが実際に更新されているか確認する
Invalidation前にオリジンのコンテンツが正しいことを確認する。S3のオブジェクトが古いままでInvalidationを実行しても意味がない。aws s3api head-object \ --bucket my-bucket-name \ --key images/hero.jpg \ --query '{LastModified:LastModified,ContentLength:ContentLength,ETag:ETag}' -
Invalidationのパスがビューワーリクエストと一致しているか確認する
ビヘイビア設定のオリジンパスプレフィックスを確認し、Invalidationパスがビューワー側のURLパスと一致していることを検証する。前述の誤診パターンが最も多い。aws cloudfront get-distribution-config \ --id E1234567890ABC \ --query 'DistributionConfig.Origins.Items[*].{DomainName:DomainName,OriginPath:OriginPath}' -
Invalidationのステータスを確認する
InProgressのまま長時間経過している場合は、AWSのサービスヘルスダッシュボードを確認する。通常は数分以内にCompletedになる。aws cloudfront list-invalidations \ --distribution-id E1234567890ABC \ --query 'InvalidationList.Items[?Status==`InProgress`]' -
ブラウザキャッシュとCloudFrontキャッシュを区別する
Invalidation完了後もブラウザが古いファイルを表示する場合は、ブラウザのキャッシュが原因の可能性がある。シークレットモードで確認するか、curlで直接取得して検証する。CloudFrontのキャッシュが消えていてもブラウザのローカルキャッシュは残る。curl -sI https://d1234567890abc.cloudfront.net/images/hero.jpg
CloudFront Invalidationのまとめと次のステップ
S3ファイル更新後のCloudFrontキャッシュ問題は、CloudFront Invalidationで解決できる。ただし、Invalidationはあくまで緊急手段として位置づけ、本番環境ではファイル名バージョニングを組み合わせた設計が望ましい。
次のステップとして、デプロイパイプライン(CodePipeline / GitHub Actions)にInvalidationステップを組み込み、デプロイ完了と同時にキャッシュをパージする自動化を検討する。また、CloudFrontのキャッシュポリシーとオリジンリクエストポリシーを見直し、TTL設定がユースケースに合っているかを確認する。
用語集
| 用語 | 説明 |
|---|---|
| Invalidation(無効化) | CloudFrontのエッジロケーションにキャッシュされたオブジェクトを強制削除するリクエスト |
| エッジロケーション | CloudFrontがコンテンツをキャッシュする世界各地のデータセンター拠点 |
| TTL(Time to Live) | キャッシュエントリの有効期間。Cache-Controlヘッダーまたはディストリビューション設定で制御する |
| オリジンパスプレフィックス | CloudFrontがオリジンへのリクエスト時に付与するパスのプレフィックス。ビューワーパスとS3キーのマッピングに影響する |
| ファイル名バージョニング | コンテンツハッシュやバージョン番号をファイル名に含めることでURLを変え、Invalidation不要でキャッシュを制御する手法 |
コメント
コメントを投稿