AWS CloudFront のキャッシュを JavaScript SDK を使って削除した後に再生成するスクリプトのメモです。
背景
CloudFront でキャッシュはしたい。キャッシュはしたいけど、データは更新される。更新されたらキャッシュも更新したい。キャッシュを削除すれば良いけど、そうすると最初のアクセス時にキャッシュ生成コストを負担することになる。その負担をユーザに課したくない。つまり、削除後に再生成するようにしたい。
やり方
やり方は本当に単純で CloudFront の SDK を使って、指定 URL のキャッシュを削除して、HTTP リクエストでアクセスしてキャッシュを生成するっていうだけです。気をつけなきゃいけないのは CloudFront のキャッシュ条件程度と、HTTP リクエストのタイミングくらいです。
CloudFront キャッシュ削除の反映
CloudFront は現時点 ( 2018/11/20 ) でキャッシュを削除してから約5秒程度でキャッシュが削除されます。
(*) 90%程度のエッジサーバは5秒、全部に完全に反映するには1〜2分程度というアナウンスが AWS からあったようですがオリジナルソースの URL が今は NotFound 状態で公式アナウンスはありません…
というわけで、5秒後くらいに HTTP リクエストしてあげればキャッシュを再生成できるということになります。
コード
const got = require('got')
const AWS = require('aws-sdk')
const cloudfront = new AWS.CloudFront({
apiVersion: '2018-06-18',
accessKeyId: 'ACCESS_KEY',
secretAccessKey: 'SECRET_KEY',
})
(() => {
const invalidCache = async () => {
const timestamp = new Date()
const string_timestamp = String(timestamp.getTime())
const invalidate_items = ['/example.json']
const item_count = invalidate_items.length
const params = {
DistributionId: 'DistributionId',
InvalidationBatch: {
CallerReference: string_timestamp,
Paths: {
Quantity: item_count,
Items: invalidate_items
}
}
}
await cloudfront.createInvalidation(params).promise()
setTimeout(() => {
got('https://example.com/example.json', {
headers: {
'Origin': 'https://example.com'
}
})
}, 5000)
}
invalidCache()
})()
createInvalidation
でキャッシュ削除を開始します。Promise の完了時に削除自体が完了しているわけではなく、あくまでスタックに積んだ状態です。なので、そこからおおよそ5秒後には消えているだろうという見積もりで got
ライブラリを使って HTTP リクエストをしています。
その際に、CloudFront で設定した Headers の値と一致するようなリクエストパラメータにする必要があります。今回のコードだと CORS のために Origin
ヘッダーをホワイトリストに追加しているので Origin
ヘッダーを追加しています。
Invalidation のステータスをポーリング
キャッシュ削除が完全に完了したかどうかを知るには数秒おきにステータス確認するしかないです。2秒おきに getInvalidation
を叩いて該当 ID のステータスを取得しています。ちなみに実際に試してみると InProgress
から Completed
になるまで約2分弱かかりました。
const checkStatus = (id) => {
const params = {
DistributionId: 'DistributionId',
Id: id
}
cloudfront.getInvalidation(params, (err, data) => {
console.log(err, data)
})
}
const invalidCache = () => {
const timestamp = new Date()
const string_timestamp = String(timestamp.getTime())
const invalidate_items = ['/example.json']
const item_count = invalidate_items.length
const params = {
DistributionId: 'DistributionId',
InvalidationBatch: {
CallerReference: string_timestamp,
Paths: {
Quantity: item_count,
Items: invalidate_items
}
}
}
cloudfront.createInvalidation(params, (err, data) => {
const id = data.Invalidation.Id
setInterval(checkStatus, 2000, id)
})
}
以上です。完全ではないですが実用性はそれなりにあると思います。実際には AWS Lambda などを使って、データベース更新コールバックなどで実行するような感じになると思います。