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で強制削除するかの二択だ。

graph LR Viewer["ビューワー
(ブラウザ)"] Edge["エッジロケーション
(CloudFrontキャッシュ)"] Origin["オリジン
(S3バケット)"] Invalidation["Invalidationリクエスト
(キャッシュ削除)"] Viewer -->|"HTTPリクエスト"| Edge Edge -->|"キャッシュヒット: TTL内"| Viewer Edge -->|"キャッシュミス: オリジンフェッチ"| Origin Origin -->|"オブジェクト返却 + キャッシュ格納"| Edge Invalidation -->|"指定パスのキャッシュを削除"| Edge
  1. ビューワーリクエスト — ユーザーのブラウザが最寄りのエッジロケーションにHTTPリクエストを送る。
  2. キャッシュヒット — TTL内のオブジェクトが存在すれば、エッジがそのままレスポンスを返す(オリジンへの通信なし)。
  3. キャッシュミス / Invalidation後 — エッジにオブジェクトがない場合、オリジン(S3)からフェッチしてキャッシュに格納する。
  4. 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

マネジメントコンソールから実行する手順

  1. CloudFrontコンソールを開き、対象のディストリビューションを選択する。
  2. 'Invalidations'タブ → 'Create invalidation'をクリック。
  3. 対象パスを入力する(例: /images/hero.jpg または /*)。
  4. '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のオブジェクトキーではない。オリジンパスが設定されている場合は特に注意が必要だ。
graph TD ViewerReq["ビューワーリクエスト
/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
  1. ビューワーパス — ブラウザが/js/app.jsをリクエスト。これがInvalidationに指定すべきパス。
  2. オリジンパスプレフィックス — CloudFrontのビヘイビア設定で/assetsが付与される。
  3. S3オブジェクトキー — 実際にS3に格納されているキーはassets/js/app.js
  4. 誤ったInvalidation/assets/js/app.jsを指定してもキャッシュは削除されない。

コスト最適化 — Invalidationを乱用しない設計

CloudFrontのInvalidationは月1,000パスまで無料で、超過分は有料になる。ワイルドカード/*は1パスとしてカウントされるため、全体をパージする場合はコスト効率が良い。ただし、頻繁に/*を実行する設計は根本的な問題を示している。

本番環境で推奨されるアプローチはファイル名バージョニングだ。app.jsではなくapp.v2.1.0.jsapp.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のトラブルシューティング手順

  1. キャッシュヒット状態を確認する
    まずレスポンスヘッダーを見て、本当に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側を確認する。
  2. S3のオブジェクトが実際に更新されているか確認する
    Invalidation前にオリジンのコンテンツが正しいことを確認する。S3のオブジェクトが古いままでInvalidationを実行しても意味がない。
    aws s3api head-object \
      --bucket my-bucket-name \
      --key images/hero.jpg \
      --query '{LastModified:LastModified,ContentLength:ContentLength,ETag:ETag}'
    
  3. Invalidationのパスがビューワーリクエストと一致しているか確認する
    ビヘイビア設定のオリジンパスプレフィックスを確認し、Invalidationパスがビューワー側のURLパスと一致していることを検証する。前述の誤診パターンが最も多い。
    aws cloudfront get-distribution-config \
      --id E1234567890ABC \
      --query 'DistributionConfig.Origins.Items[*].{DomainName:DomainName,OriginPath:OriginPath}'
    
  4. Invalidationのステータスを確認する
    InProgressのまま長時間経過している場合は、AWSのサービスヘルスダッシュボードを確認する。通常は数分以内にCompletedになる。
    aws cloudfront list-invalidations \
      --distribution-id E1234567890ABC \
      --query 'InvalidationList.Items[?Status==`InProgress`]'
    
  5. ブラウザキャッシュと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不要でキャッシュを制御する手法

コメント

このブログの人気の投稿

EC2 SSH接続タイムアウトの原因と修正方法 — セキュリティグループのインバウンドルール完全ガイド

S3パブリックアクセス拒否の原因と解決策:バケットレベルの「Block Public Access」が優先される仕組み

EC2インスタンスIDをメタデータから取得する方法 — IMDSv2が安全な理由