ちょっと話題の記事

Lambda@Edge で CloudFront キャッシュヒット率を向上させるんや!

CloudFront はクエリ文字列毎のキャッシュが可能ですが、パラメータの順序、大文字小文字の違いによって、別のキャッシュと判断されます。Lambda@Edge でクエリ文字列を標準化し、キャッシュヒット率を向上させる方法をご紹介します。
2018.07.07

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

みなさん、Lambda@Edge 使ってますか!?「いまいち使い所がわからない・・・」という方も少なくないのではないでしょうか?今回はユースケースの一例として、クエリ文字列を標準化することでキャッシュヒット率を向上させる方法についてご紹介したいと思います。

クエリ文字列のキャッシュについて理解する

CloudFront では Query String Forwarding and Caching の設定により、クエリ文字列パラメータに基づいて個別にキャッシュすることが可能ですが、ここで注意したいポイントは、パラメータの順番や、大文字小文字のレベルで別のキャッシュとして処理される、という点です。つまり、以下のようなクエリ文字列は、すべて同じ結果を返すにも関わらず、別のキャッシュとして処理されます。

  • http://example.net?monthnum=11&s=aws
  • http://example.net?s=aws&monthnum=11 (パラメータの順番が違う)
  • http://example.net?s=aws&monthNUM=11 (パラメータの大文字小文字が違う)
  • http://example.net?s=aWs&monthnum=11 (パラメータ値の大文字小文字が違う)

実際にキャッシュの動作を見てみましょう。(長くなるので、x-cache のみ抽出しています)

$ curl -I "https://d2845sipn2gl6v.cloudfront.net?monthnum=11&s=aws"
x-cache: Miss from cloudfront
$ curl -I "https://d2845sipn2gl6v.cloudfront.net?monthnum=11&s=aws"
x-cache: Hit from cloudfront
 > 完全一致しているのでキャッシュヒット

$ curl -I "https://d2845sipn2gl6v.cloudfront.net?s=aws&monthnum=11"
x-cache: Miss from cloudfront
 > パラメータの順番が変わったのでヒットしない

$ curl -I "https://d2845sipn2gl6v.cloudfront.net?s=aws&monthNUM=11"
x-cache: Miss from cloudfront
 > パラメータが大文字小文字で異なるのでヒットしない

$ curl -I "https://d2845sipn2gl6v.cloudfront.net?s=aWs&monthNUM=11"
x-cache: Miss from cloudfront
 > パラメータ値が大文字小文字で異なるのでヒットしない

と、このようになります。ウェブアプリケーション側で CloudFront に転送する各クエリ文字列パラメータの順序、大文字小文字などが統一されているならば問題ありませんが、そのようになっていない場合、無駄にキャッシュを消費している可能性があります。ウェブアプリケーション を改修してキャッシュヒット率を向上させるのも手ですが、対象の洗い出しから始めて、改修するのも一苦労でしょう。

そこで Lambda@Edge というわけです。

Lambda@Edge でクエリ文字列パラメータを標準化する

Lambda@Edge は CloudFront のリクエスト/レスポンス に対し、Lambda で処理を加えることができます。今回のケースであれば、CloudFront へのビューアーリクエストに対して、クエリ文字列パラメータを標準化する処理を加えたうえで CloudFront にパラメータを渡してやれば良いのです。 ここでの「標準化」ですが、簡単に言うと、

  • パラメータ名をアルファベット順に並べ替える
  • すべて小文字に変換する

ということです。この処理によって、すべて同じクエリ文字列パラメータとして CloudFront に渡されます。

では、さっそくやってみましょう!

Lambda@Edge 関数の作成

Lambda@Edge 関数の作成手順については、前回のエントリーを参照くだい。(「Lambda@Edge 用のロール作成」 以降)

できた!S3 オリジンへの直接アクセス制限と、インデックスドキュメント機能を共存させる方法

使用するコードは、公式のガイドより拝借しつつ、置換されていることをログで確認したいので、console.log('Normalized Query String: ', request.querystring); だけ追加しています。(動作確認できたならば削除して良いでしょう) 以下をコピーして、Lambda のコードに貼り付ければ OK です。私は Node.js 8.10 で動作確認していますが、Node.js 6.10 でも問題ないと思います。

'use strict';

const querystring = require('querystring');

exports.handler = (event, context, callback) => {
    const request = event.Records[0].cf.request;

    console.log('Query String: ', request.querystring);

    const params = querystring.parse(request.querystring.toLowerCase());
    const sortedParams = {};

    Object.keys(params).sort().forEach(key => {
        sortedParams[key] = params[key];
    });

    request.querystring = querystring.stringify(sortedParams);

    console.log('Normalized Query String: ', request.querystring);

    callback(null, request);
};

CloudFront のトリガー設定で対象のディストリビューションを選択し、[CloudFront イベント]は ビューアーリクエスト を選択し、[トリガーとレプリケートの有効化] にチェックます。

どの CloudFront イベントを選択するべきかについては、以下のエントリーにある「正しいトリガーを選択」が参考になるかと思いますので、是非あわせて参照ください。

5分で読む!Lambda@Edge 設計のベストプラクティス

Lambda@Edge の登録ができたら、さぁ動作確認してみましょう!

Lambda@Edge があるときぃ〜

キャッシュ動作を確認します。(こちらも長くなるので、x-cache のみ抽出しています)

$ curl -I "https://d2845sipn2gl6v.cloudfront.net?monthnum=11&s=aws"
x-cache: Miss from cloudfront
$ curl -I "https://d2845sipn2gl6v.cloudfront.net?monthnum=11&s=aws"
x-cache: Hit from cloudfront
 > 完全一致しているので当然キャッシュヒット 

$ curl -I "https://d2845sipn2gl6v.cloudfront.net?s=aws&monthnum=11"
x-cache: Hit from cloudfront
 > パラメータ順序が違いますが、キャッシュヒットしてますね!

$ curl -I "https://d2845sipn2gl6v.cloudfront.net?s=aws&monthNUM=11"
x-cache: Hit from cloudfront
 > パラメータ名に大文字が含まれてますが、キャッシュヒットしてますね!

$ curl -I "https://d2845sipn2gl6v.cloudfront.net?s=aWs&monthNUM=11"
x-cache: Hit from cloudfront
 > パラメータ値に大文字が含まれてますが、キャッシュヒットしてますね!

すべて同じクエリ文字列パラメータとして処理されていますね!では、Lambda@Edge のログも確認してみましょう。 想定したとおりに置換されていることがログからも判断できました!

さいごに

いかがだったでしょうか? Lambda@Edge を使うことでウェブアプリケーション側の改修をせずとも、クエリ文字列パラメータを標準化し、キャッシュヒット率が向上することをご確認いただけたのではないでしょうか。もし、クエリ文字列に基づいたキャッシュを利用されていて、いまいちキャッシュヒット率が伸びなくて悩まれておられましたら、お試しいただければと思います!

また、Lambda@Edge には、まだまだ多くのユースケースがございますので、ご紹介できるようにブログがんばりまーす!以上!大阪オフィスの丸毛(@marumo1981)でした!

参考