CloudFront Functionsでキャッシュキーの正規化を行ってキャッシュヒット率を向上させてみた
AcceptヘッダーでWebPを配信するか判断するようにしたけどキャッシュヒット率が低い
こんにちは、のんピ(@non____97)です。
皆さんAcceptヘッダーでWebPを配信するか判断するようにしたけどキャッシュヒット率が低いと思ったことはありますか? 私はあります。
以下の記事でAcceptヘッダーでWebPを配信するか判断する方法を紹介しました。
こちらの記事の中で、以下のようにAccept
ヘッダーはブラウザやアクセスの仕方によって大きく異なるため、キャッシュヒット率に影響を与えやすいことを紹介しています。
注意点として、
Accept
ヘッダーはブラウザやアクセスの仕方によって大きく異なるため、キャッシュヒット率が低下します。例えば、私のChromeの場合は
image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8
やtext/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
で、Safariはimage/webp,image/avif,image/jxl,image/heic,image/heic-sequence,video/*;q=0.8,image/png,image/svg+xml,image/*;q=0.8,*/*;q=0.5
です。
キャッシュポリシーで定義したキャッシュキーは、そのキーの値がキャッシュ済みのコンテンツと完全に一致するかどうかでキャッシュを返すか判断をします。
例えば、Accept
ヘッダーをキャッシュキーとしている場合、Accept
ヘッダーの値がimage/avif,image/webp
でアクセスした後に、次回アクセス時のヘッダーがimage/webp,image/avif
のように異なる値になっているとキャッシュを返してくれません。
異なるクライアントからの初回アクセス毎にオリジンへ通信をするようにすると、パフォーマンスが悪いですし、オリジンへの負荷も高まりがちです。
今回はその対応としてCloudFront Functionsを使って、キャッシュキーの正規化(Cache Key normalization)を行い、キャッシュヒット率を向上させてみます。
Cache Key normalizationの説明は以下Black Beltの資料が分かりやすいです。
抜粋 : AWS Black Belt Online Seminar - Amazon CloudFront (CloudFront Functions / Lambda@Edge 編)
やってみた
検証環境
検証環境は以下のとおりです。
検証環境は全てAWS CDKでデプロイしました。使用したコードは以下GitHubリポジトリに保存しています。
こちらのベースとなったコードの詳細な説明は以下記事をご覧ください。
CloudFront Functionsの紹介
実行しているCloudFront Functionsは以下のとおりです。
async function handler(event) {
const request = event.request;
const uri = request.uri;
// Check WebP support in Accept header
const headers = request.headers;
const acceptHeader = headers.accept ? headers.accept.value : '';
const viewerAcceptWebP = acceptHeader.split(',')
.some(type => {
const parts = type.trim().split(";");
const mimeType = parts[0];
const params = parts[1] || "";
if (mimeType === 'image/webp') {
return true;
}
const qMatch = params.match(/q=([0-9.]+)/);
const q = qMatch ? parseFloat(qMatch[1]) : 1.0;
if (q < 1) {
return false;
}
return mimeType === '*/*' || mimeType === 'image/*';
});
// Regular expression for image extensions
const imageExtRegex = /\.(jpe?g|png)$/i;
if (imageExtRegex.test(uri)) {
request.headers['x-viewer-accept-webp'] = {
value: `${viewerAcceptWebP}`
};
}
return request;
}
末尾がjpeg or jpg or pngの場合にx-viewer-accept-webp
ヘッダーに"true"
か"false"
を設定するというシンプルなものです。
このx-viewer-accept-webp
をキャッシュキーとします。
"true"
であれば、クライアントがWebPをサポートしていると判定し、逆に"false"
であればクライアントがWebPをサポートしていないと判定しています。
ブラウザによってはWebPをサポートしているがAccept
ヘッダーに*/*
やimage/*
としか返さないものもあるため、そちらにもサポートするようにしています。ただし、それらの場合本当にWebPをサポートしているのかどうか不明であるためq
(重み)が1未満の場合はWebPをサポートしていないと判定しています。
各ブラウザのAccept
ヘッダーの既定値はMDN Web Docsにまとまっています。
その他のポイントは値は文字列である必要があるというところです。もし、booleanで返してしまうと、以下のようなHTTP 503エラーとなります。
The request could not be satisfied.
The CloudFront function returned an invalid value: request.headers value field must be a string. We can't connect to the server for this app or website at this time. There might be too much traffic or a configuration error. Try again later, or contact the app or website owner.
If you provide content to customers through CloudFront, you can find steps to troubleshoot and help prevent this error by reviewing the CloudFront documentation.
curlでAcceptヘッダーを変更しながらアクセス
実際にデプロイして動きを確認してみます。
まず、初回アクセスです。
Accept
ヘッダーにimage/webp
を付与してアクセスします。
> time curl -I https://www.non-97.net/non__97.png -H "Accept:image/webp"
HTTP/2 200
content-type: image/webp
content-length: 5464
date: Sun, 02 Feb 2025 08:42:04 GMT
last-modified: Fri, 24 Jan 2025 03:43:34 GMT
etag: "54b0857ccfbab67746434fb9042aacff"
x-amz-server-side-encryption: AES256
accept-ranges: bytes
server: AmazonS3
x-cache: Miss from cloudfront
via: 1.1 663c57b4ec4e2561ada30794913fe298.cloudfront.net (CloudFront)
x-amz-cf-pop: NRT12-P1
alt-svc: h3=":443"; ma=86400
x-amz-cf-id: SzZucVEqwoOqnTjdkp6dSiwdO4jxwZm95CqN4sBg3OoWmNGGLThbpw==
x-xss-protection: 1; mode=block
x-frame-options: SAMEORIGIN
referrer-policy: strict-origin-when-cross-origin
x-content-type-options: nosniff
strict-transport-security: max-age=31536000
________________________________________________________
Executed in 3.39 secs fish external
usr time 23.17 millis 0.23 millis 22.93 millis
sys time 25.52 millis 1.67 millis 23.85 millis
Miss from cloudfront
とあることからキャッシュヒットしなかったようです。コンテンツを返すのに3秒もかかっていますね。
再度同じパラメーターでアクセスします。
> time curl -I https://www.non-97.net/non__97.png -H "Accept:image/webp"
HTTP/2 200
content-type: image/webp
content-length: 5464
date: Sun, 02 Feb 2025 08:42:04 GMT
last-modified: Fri, 24 Jan 2025 03:43:34 GMT
etag: "54b0857ccfbab67746434fb9042aacff"
x-amz-server-side-encryption: AES256
accept-ranges: bytes
server: AmazonS3
x-cache: Hit from cloudfront
via: 1.1 fa9e00318667b610e39aa2c387f16a32.cloudfront.net (CloudFront)
x-amz-cf-pop: NRT12-P1
alt-svc: h3=":443"; ma=86400
x-amz-cf-id: f-8Z4NJFibNiHBQRaOn2njz_vrDYqKSfZECsIlViFMXDWYp91Vd00w==
age: 3
x-xss-protection: 1; mode=block
x-frame-options: SAMEORIGIN
referrer-policy: strict-origin-when-cross-origin
x-content-type-options: nosniff
strict-transport-security: max-age=31536000
________________________________________________________
Executed in 169.52 millis fish external
usr time 22.78 millis 0.19 millis 22.59 millis
sys time 19.17 millis 1.04 millis 18.13 millis
はい、キャッシュヒットしました。
Accept
ヘッダーの値をimage/webp, test
としてアクセスします。
> time curl -I https://www.non-97.net/non__97.png -H "Accept:image/webp, test"
HTTP/2 200
content-type: image/webp
content-length: 5464
date: Sun, 02 Feb 2025 08:42:04 GMT
last-modified: Fri, 24 Jan 2025 03:43:34 GMT
etag: "54b0857ccfbab67746434fb9042aacff"
x-amz-server-side-encryption: AES256
accept-ranges: bytes
server: AmazonS3
x-cache: Hit from cloudfront
via: 1.1 da8c4d7ff604f51ba4f83ffed7115acc.cloudfront.net (CloudFront)
x-amz-cf-pop: NRT12-P1
alt-svc: h3=":443"; ma=86400
x-amz-cf-id: lNuanoIiArvThyYkzF9RLEY6N49PHYkYD13K7jjsIvwMvkbLesr2tg==
age: 10
x-xss-protection: 1; mode=block
x-frame-options: SAMEORIGIN
referrer-policy: strict-origin-when-cross-origin
x-content-type-options: nosniff
strict-transport-security: max-age=31536000
________________________________________________________
Executed in 154.43 millis fish external
usr time 23.65 millis 0.19 millis 23.46 millis
sys time 22.91 millis 1.06 millis 21.85 millis
Accept
ヘッダーをキャッシュミスするとこと、キャッシュヒットしていますね。もちろん、コンテンツもWebPを返しています。
さらにAccept
ヘッダーの値をimage/webp, test, 2
としてアクセスします。
> time curl -I https://www.non-97.net/non__97.png -H "Accept:image/webp, test, 2"
HTTP/2 200
content-type: image/webp
content-length: 5464
date: Sun, 02 Feb 2025 08:42:04 GMT
last-modified: Fri, 24 Jan 2025 03:43:34 GMT
etag: "54b0857ccfbab67746434fb9042aacff"
x-amz-server-side-encryption: AES256
accept-ranges: bytes
server: AmazonS3
x-cache: Hit from cloudfront
via: 1.1 6f5c56b3519e8f4cd3e201cadf5f5b40.cloudfront.net (CloudFront)
x-amz-cf-pop: NRT12-P1
alt-svc: h3=":443"; ma=86400
x-amz-cf-id: GSw8SzTbBT5lL06cFKqwK-6l1QI_qRT-oJL_vlCDqItYx5Lbp6lXUQ==
age: 25
x-xss-protection: 1; mode=block
x-frame-options: SAMEORIGIN
referrer-policy: strict-origin-when-cross-origin
x-content-type-options: nosniff
strict-transport-security: max-age=31536000
________________________________________________________
Executed in 132.59 millis fish external
usr time 15.81 millis 194.00 micros 15.62 millis
sys time 19.48 millis 996.00 micros 18.49 millis
こちらもキャッシュヒットしていますね。
Acceptヘッダーにimage/webpを含めずにアクセス
Accept
ヘッダーを付与していない場合はWebPではなく、PNGを返すことを確認します。
> time curl -I https://www.non-97.net/non__97.png
HTTP/2 200
content-type: image/png
content-length: 76942
date: Sun, 02 Feb 2025 08:42:41 GMT
last-modified: Fri, 24 Jan 2025 03:43:32 GMT
etag: "cac2eacf135495f8eb947890b6c84526"
x-amz-server-side-encryption: AES256
accept-ranges: bytes
server: AmazonS3
x-cache: Miss from cloudfront
via: 1.1 f2f4975292b62b8912a072e49f082cbc.cloudfront.net (CloudFront)
x-amz-cf-pop: NRT12-P1
alt-svc: h3=":443"; ma=86400
x-amz-cf-id: y_jTiyFTKb1VT45kFZrjYMNqF39IpCT236xV9MgNqidcd1yafK9pGA==
x-xss-protection: 1; mode=block
x-frame-options: SAMEORIGIN
referrer-policy: strict-origin-when-cross-origin
x-content-type-options: nosniff
strict-transport-security: max-age=31536000
________________________________________________________
Executed in 1.02 secs fish external
usr time 20.54 millis 0.21 millis 20.33 millis
sys time 19.46 millis 1.13 millis 18.33 millis
キャッシュヒットせずにPNGを返すことが分かります。
他のヘッダーが指定されている場合やimage/webp
が含まれない場合もPNGを返すことも確認します。
> time curl -I https://www.non-97.net/non__97.png -H "test:test"
HTTP/2 200
content-type: image/png
content-length: 76942
date: Sun, 02 Feb 2025 08:42:41 GMT
last-modified: Fri, 24 Jan 2025 03:43:32 GMT
etag: "cac2eacf135495f8eb947890b6c84526"
x-amz-server-side-encryption: AES256
accept-ranges: bytes
server: AmazonS3
x-cache: Hit from cloudfront
via: 1.1 fa9e00318667b610e39aa2c387f16a32.cloudfront.net (CloudFront)
x-amz-cf-pop: NRT12-P1
alt-svc: h3=":443"; ma=86400
x-amz-cf-id: Txeh9zJA79P5g6EvShBZimkmweR_9jp78PhU-BoishrXfixlhSpF8Q==
age: 14
x-xss-protection: 1; mode=block
x-frame-options: SAMEORIGIN
referrer-policy: strict-origin-when-cross-origin
x-content-type-options: nosniff
strict-transport-security: max-age=31536000
________________________________________________________
Executed in 140.97 millis fish external
usr time 17.90 millis 0.22 millis 17.68 millis
sys time 26.45 millis 1.34 millis 25.11 millis
> time curl -I https://www.non-97.net/non__97.png -H "Accept:image/png"
HTTP/2 200
content-type: image/png
content-length: 76942
date: Sun, 02 Feb 2025 08:42:41 GMT
last-modified: Fri, 24 Jan 2025 03:43:32 GMT
etag: "cac2eacf135495f8eb947890b6c84526"
x-amz-server-side-encryption: AES256
accept-ranges: bytes
server: AmazonS3
x-cache: Hit from cloudfront
via: 1.1 f22f45735eceb3450fbe806ce121aab8.cloudfront.net (CloudFront)
x-amz-cf-pop: NRT12-P1
alt-svc: h3=":443"; ma=86400
x-amz-cf-id: Han-6KT-EIz9vvy618HeQIgE9cPB7voV_uJYRf51E7xGwkoQDC2pTA==
age: 2224
x-xss-protection: 1; mode=block
x-frame-options: SAMEORIGIN
referrer-policy: strict-origin-when-cross-origin
x-content-type-options: nosniff
strict-transport-security: max-age=31536000
________________________________________________________
Executed in 351.61 millis fish external
usr time 25.41 millis 0.20 millis 25.20 millis
sys time 22.42 millis 1.15 millis 21.27 millis
意図した動作ですね。
クライアント側でx-viewer-accept-webpヘッダーを付与してアクセス
続いて、クライアント側でx-viewer-accept-webp
ヘッダーを付与してアクセスした時の挙動を確認します。
> time curl -I https://www.non-97.net/non__97.png -H "x-viewer-accept-webp:true"
HTTP/2 200
content-type: image/png
content-length: 76942
date: Sun, 02 Feb 2025 08:42:41 GMT
last-modified: Fri, 24 Jan 2025 03:43:32 GMT
etag: "cac2eacf135495f8eb947890b6c84526"
x-amz-server-side-encryption: AES256
accept-ranges: bytes
server: AmazonS3
x-cache: Hit from cloudfront
via: 1.1 23bc6d6a912d17773e1bf97197cbfc1e.cloudfront.net (CloudFront)
x-amz-cf-pop: NRT12-P1
alt-svc: h3=":443"; ma=86400
x-amz-cf-id: AkPDmff3H7mSA9V_4kRZw3LoHfNwU74_Dx0pgsmGlXvBVIscykWuSQ==
age: 41
x-xss-protection: 1; mode=block
x-frame-options: SAMEORIGIN
referrer-policy: strict-origin-when-cross-origin
x-content-type-options: nosniff
strict-transport-security: max-age=31536000
________________________________________________________
Executed in 338.66 millis fish external
usr time 26.68 millis 0.20 millis 26.47 millis
sys time 23.13 millis 1.21 millis 21.92 millis
> time curl -I https://www.non-97.net/non__97.png -H "x-viewer-accept-webp:true" -H "Accept:image/webp, test, 2"
HTTP/2 200
content-type: image/webp
content-length: 5464
date: Sun, 02 Feb 2025 08:42:04 GMT
last-modified: Fri, 24 Jan 2025 03:43:34 GMT
etag: "54b0857ccfbab67746434fb9042aacff"
x-amz-server-side-encryption: AES256
accept-ranges: bytes
server: AmazonS3
x-cache: Hit from cloudfront
via: 1.1 0ef0d5d7817de0dbb2171006ac28bb0c.cloudfront.net (CloudFront)
x-amz-cf-pop: NRT12-P1
alt-svc: h3=":443"; ma=86400
x-amz-cf-id: 5p3q7rBRzA1gyAgG7jFOOCNxDm3PmrpauG5t0-D2Kko4tv5BbkpFhQ==
age: 120
x-xss-protection: 1; mode=block
x-frame-options: SAMEORIGIN
referrer-policy: strict-origin-when-cross-origin
x-content-type-options: nosniff
strict-transport-security: max-age=31536000
________________________________________________________
Executed in 141.13 millis fish external
usr time 16.58 millis 0.19 millis 16.39 millis
sys time 20.82 millis 1.04 millis 19.78 millis
> time curl -I https://www.non-97.net/non__97.png -H "x-viewer-accept-webp:false" -H "Accept:image/webp, test, 2"
HTTP/2 200
content-type: image/webp
content-length: 5464
date: Sun, 02 Feb 2025 08:42:04 GMT
last-modified: Fri, 24 Jan 2025 03:43:34 GMT
etag: "54b0857ccfbab67746434fb9042aacff"
x-amz-server-side-encryption: AES256
accept-ranges: bytes
server: AmazonS3
x-cache: Hit from cloudfront
via: 1.1 9b8a6e30994167e8de984036681d4ff6.cloudfront.net (CloudFront)
x-amz-cf-pop: NRT12-P1
alt-svc: h3=":443"; ma=86400
x-amz-cf-id: WHoxz5deMdKe3lPh119vp6zz7lR9UoZwT3O50MdcvRRtf5YcSLdbpg==
age: 127
x-xss-protection: 1; mode=block
x-frame-options: SAMEORIGIN
referrer-policy: strict-origin-when-cross-origin
x-content-type-options: nosniff
strict-transport-security: max-age=31536000
________________________________________________________
Executed in 151.70 millis fish external
usr time 18.45 millis 0.19 millis 18.26 millis
sys time 18.87 millis 1.09 millis 17.78 millis
> time curl -I https://www.non-97.net/non__97.png -H "x-viewer-accept-webp:false" -H "Accept:image/webp, test, 2, 3"
HTTP/2 200
content-type: image/webp
content-length: 5464
date: Sun, 02 Feb 2025 08:42:04 GMT
last-modified: Fri, 24 Jan 2025 03:43:34 GMT
etag: "54b0857ccfbab67746434fb9042aacff"
x-amz-server-side-encryption: AES256
accept-ranges: bytes
server: AmazonS3
x-cache: Hit from cloudfront
via: 1.1 8d094829a2df82945a7c7fbea18cea10.cloudfront.net (CloudFront)
x-amz-cf-pop: NRT12-P1
alt-svc: h3=":443"; ma=86400
x-amz-cf-id: ldd4NOuHDY7zQBuYTFLDDw5Q2LWxD_qHA4XcHqtChzduyd_Fl9h-GA==
age: 138
x-xss-protection: 1; mode=block
x-frame-options: SAMEORIGIN
referrer-policy: strict-origin-when-cross-origin
x-content-type-options: nosniff
strict-transport-security: max-age=31536000
________________________________________________________
Executed in 173.48 millis fish external
usr time 22.89 millis 0.21 millis 22.68 millis
sys time 20.49 millis 1.17 millis 19.32 millis
> time curl -I https://www.non-97.net/non__97.png.webp -H "x-viewer-accept-webp:false"
HTTP/2 200
content-type: image/webp
content-length: 5464
date: Sun, 02 Feb 2025 09:12:30 GMT
last-modified: Fri, 24 Jan 2025 03:43:34 GMT
etag: "54b0857ccfbab67746434fb9042aacff"
x-amz-server-side-encryption: AES256
accept-ranges: bytes
server: AmazonS3
x-cache: Miss from cloudfront
via: 1.1 f0499023f5cce9a24cc0ed91910c47ee.cloudfront.net (CloudFront)
x-amz-cf-pop: NRT12-P1
alt-svc: h3=":443"; ma=86400
x-amz-cf-id: kCwa-wCA-0S-P_FamHRGty0jCaZMcPe58eHuluEEoqp0UmPA9UNtdg==
x-xss-protection: 1; mode=block
x-frame-options: SAMEORIGIN
referrer-policy: strict-origin-when-cross-origin
x-content-type-options: nosniff
strict-transport-security: max-age=31536000
________________________________________________________
Executed in 1.11 secs fish external
usr time 25.52 millis 0.22 millis 25.30 millis
sys time 25.15 millis 1.26 millis 23.89 millis
はい、例えクライアント側でx-viewer-accept-webp
ヘッダーを指定していたとしても、CloudFront Functionsで設定し直す動きをするので、キャッシュを返すか否かに影響を与えません。
異なるブラウザでのアクセス
実際に異なるブラウザでアクセスしてみましょう。
キャッシュをクリアしてからChromeでアクセスします。
この時のAccept
ヘッダーはtext/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
です。
続いてFirefoxでアクセスします。
この時のAccept
ヘッダーはtext/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
ですが、キャッシュヒットしていることが分かります。Content-TypeヘッダーからもWebPであることも分かります。
意図した挙動をしていますね。
キャッシュヒット率を上げる際にはCloudFront Functionsを使ったNormalizationも検討しよう
CloudFront Functionsを使ってCloudFrontのキャッシュヒット率を向上させてみました。
キャッシュヒット率を上げる際にはCloudFront Functionsを使ったNormalizationも検討すると良いでしょう。
ただし、CloudFront Functionsは実行回数に対する課金が走ります。(100 万件の呼び出しあたり 0.10 USD)
CloudFront Functionsは現時点でViewer RequestかViewer Responseでしか動作することはできません。つまり、今回の場合はユーザーがWebページにアクセスして、裏側で画像ファイルを読み込む回数分だけCloudFront Functionsが実行されます。
そのため、画像ファイルが大量にあるWebサイトの場合、CloudFront Functionsの料金が負担になることも考えられます。仮にWebPの導入目的が転送量の削減による配信速度向上と転送量課金削減の場合、転送量課金削減の効果は感じづらくなるでしょう。
この記事が誰かの助けになれば幸いです。
以上、クラウド事業本部 コンサルティング部の のんピ(@non____97)でした!