こまった事
こんな感じのアプリ構成で
CloudFront経由でS3に格納したコンテンツを配信する場合、
CloudFrontのCloudFrontエッジロケーション(キャッシュサーバー)からコンテンツをクライアントに配信する為、
クライアントに中々、最新のコンテンツが行き渡らない事象がしばしば発生します。
CloudFrontにキャッシュを残さないようにすれば?
設定次第でそれもできますが、
それだとCloudFrontの意味ないです。
それと毎回、配信元のS3にアクセスしに行く事になるのでS3のアクセス数が増加してしまいます。(お金がかかってしまう)
解決方法
CloudFrontの配信元のファイル(S3)が更新されたら自動的にCloudFrontのキャッシュをクリア(Invalidation)してあげる仕組みを作ります。
配信するファイルにもクライアントにキャッシュさせないようにメタ情報を付与します。
方法はこちらの記事を参考に
https://qiita.com/anchoor/items/2dc6ab8347c940ea4648
その仕組みどんな風に作るの?
配信元のファイル(S3)の更新をトリガーとして実行するLambdaFunctionを作成します。
そのLambdaFunction内でCloudFrontのキャッシュをクリア(Invalidation)してあげます。
このように作るのだ
S3のオブジェクトの更新をトリガーとして発火する
LambdaFunctionを作成します。
※詳細は、こちらの記事で記載しています。
AWS S3のオブジェクトの更新をトリガーとして発火する LambdaFunctionを作成する
LambdaFunctionの中身
今回はGoで書きました。
AWSのAPI(AWS SDK for Go)にCloudFrontののキャッシュをクリア(Invalidation)するAPIがあるので
そいつをキックしてあげるだけです。
https://docs.aws.amazon.com/sdk-for-go/api/service/cloudfront/#CloudFront.CreateInvalidation
※詳細は、こちらの記事で記載しています。
AWS S3バケット名からCloufFrontのキャッシュをクリア(CreateInvalidation)するAPIをコールする
実装に苦労した点
ロール周りや、パーミッションの問題で実際に動作させるまで時間がかかりました。
これはエラーメッセージが丁寧に出力されるので一つ一つ対応していけば問題ないのですが、
S3オブジェクトの更新トリガーについはそうもいきませんでした。
私はS3バケットの中全てのオブジェクトを更新チェック対象としている為、
オブジェクト更新したら更新した数のオブジェクトの数分、発火してしまいます。
つまり、対象のS3バケットの中身20個のファイルを格納、更新したら20回発火します。
苦労した点の対応
Goの実装内でAPI(CloudFront.CreateInvalidation)実行時に一意なIDを指定します。
そのIDには現在年月日時分秒を指定していましたが、
それを現在年月日時分を指定する事で1分に一回しかCreateInvalidationを実行しないようにしました。イベント発火の最初の1回のみ実行され、後の発火は全てIDが重複している為、エラーとなりますので1分に1回しか実行されません。
よって、S3バケットの中身20個のファイルを格納、更新したら20回発火しますが、1回だけ正常にCreateInvalidationを実行し、19回はエラーとなります。
所感
これでクライアントは常にCloudFrontエッジロケーション(キャッシュサーバー)を見に行き、見にいく先のCloudFrontエッジロケーション(キャッシュサーバー)は最新のコンテンツが行き渡っています。
CloudFront標準の機能で用意してくれててもいいような気がする機能でした。
この機能を実装した直後にこんな記事が...
「【朗報】Amazon CloudFrontのキャッシュ削除(Invalidation)が速くなりました【5秒で90%】」
https://dev.classmethod.jp/cloud/aws/cloudfront-fast-invalidation/
※構成図はこちらの記事を参考にして作成しました。
https://qiita.com/nave-m/items/68425f476b254a1a47b0