Amazon Web Services ブログ

CloudFrontとCloudFront FunctionsによるECサイトのVisitor Prioritization

5年前に前回の記事(Visitor Prioritization on e-Commerce Websites with CloudFront and Lambda@Edge)を書いた時、Visitor Prioritization (訪問者の優先順位付け)は比較的新しい概念でした。それ以来、特にゲームやメディア業界ではトラフィックシェーピング、スロットリング、リクエストの優先順位付けに、大きなニーズがあることが分かりました。もちろん EC サイトでは今でも、日々の販売やキャンペーンのためにこの機能を必要としています。

2021年 5月に CloudFront Functions を発表した後、お客様からは以下のような要望や質問が多数寄せられました:

  • CloudFront Functions はこのユースケースに使えるのか、使えないのか
  • もし使えるとしたら、そのメリットとデメリットは何か
  • 価格体系や前提条件などはどうなっているのか

CloudFront Functions は単純なヘッダー操作だけでなく、エッジコンピューティングにも対応しているため、答えは Yes です。したがって、Visitor Prioritization のユースケースにも適しています。

また、AWS は 2021年 4月に AWS WAF Bot Control の提供を開始しました。これは、不要なトラフィックやアクセスを発生させる悪意のあるボットから、お客様のサイトを保護するものです。お客様からの質問には「CloudFront Functions でより良いユーザー体験を提供するために、AWS WAF Bot Control をどのように利用すればよいのか?」というものがありました。そこで、この機会に CloudFront Functions による Visitor Prioritization と、AWS WAF Bot Control との連携方法を改めてご紹介することにしました。

呼び出しのポイント Lambda@Edge/CloudFront Functions を起動する

CloudFront Functions はビューワーリクエストとレスポンスのフェーズでのみ動作しますが、Lambda@Edge はビューワーとオリジン両方のイベント時に動作します。そのためビューワーリクエストのフェーズで Visitor Prioritization を実装する必要があります。CloudFront Function の価格設定は Lambda@Edge よりずっと安価で、同時実行制約も Lambda@Edge より高くなっています(クォータのドキュメント参照)。一方で Lambda@Edge はオリジンへのリクエスト/レスポンス操作機能を提供しています。その上に CloudFront Functions と比較すると、より柔軟なコンピューティング能力を提供します(CloudFront Functions のブログを参照)。そのため、特に複雑なユースケースでは CloudFront Functions に加えて、Lambda@Edge を使用する必要があるかもしれません。

図1. Lambda@Edge と CloudFront Functions のイベントトリガーポイント

Visitor Prioritization ワークフローの概要(正規または通常の使用例)

次の図は、典型的な Visitor Prioritization のワークフローを表しています。CloudFront Functions は各リクエストに対して、そのリクエストが正当かどうかをチェックし、オリジンに通してよいかどうかを判断します。

図2. 正規ユーザーにおける Visitor Prioritization のワークフロー

  1. クライアントがキャンペーン中に EC サイトへのアクセスをリクエストする
  2. CloudFront Function が Visitor Prioritization 機能を実行する
  3. 正規のリクエストは、オリジン(この図では Amazon EC2)に転送され処理される

Visitor Prioritization ワークフローの概要(Waiting room)

Visitor Prioritization をオンにする必要がある場合、CloudFront Functions はリクエストをオリジンに転送するか、Waiting room に転送すべきかチェックします。上記で紹介したように、CloudFront Functions はビューアのフェーズでのみ動作するので、現在のオリジンを Amazon Simple Storage Service (Amazon S3) など他のオリジンに変更することはできませんが、Waiting room へルーティングするための URL パスを変更することができます。例えば、リクエストされたパスを「waitingroom.html」に書き換えることができますが、これは静的ファイルで構成されており、データベースやバックエンドリソースにリンクされているオリジナルのワークロードではありません。このアプローチは EC2 や Aurora データベースの負荷低減に大いに役立ちますが、元のワークロードを 100% オフロードすることはできません。

図3. Waiting room をトリガーした際の Visitor Prioritization のワークフロー

したがって、このようなケースでは HTTP リダイレクトを利用することでオフロードすることができます。CloudFront はパスの条件(ビヘイビア)に応じてオリジンを変更することができます。さらに、特定のパスに対して Amazon S3 のオリジンを設定すれば、Waiting room のトラフィックを全て Amazon S3 にルーティングすることができます。また、このリダイレクトされたパスに異なる TTL を設定することで、Waiting room のコンテンツを制御することができます。

図4. Waiting room のトラフィックを S3 に分離した場合の Visitor Prioritization ワークフロー

ページベースではなくもう一つのリダイレクト技法として、ドメインベースがあります。緊急の理由でオリジンを閉じたい場合、完全に独立した Waiting room があると助かります。シナリオに応じて Waiting room を切り替えるために、両方を検討・実装することをお勧めします。CloudFront Function はこれらの要件に対応しています。以下のサンプルコードでは既にエッセンスを実装していますので、こちらを拡張して要件を実装することができます。

図5. Waiting room のトラフィックを別ドメインに分離した場合の Visitor Prioritization ワークフロー

AWS WAF Bot Control との連携

AWS WAF Bot Control は、悪意のある Bot トラフィックからお客様のサイトを保護します。特に Visitor Prioritization ソリューションを使用する場合、この種のトラフィックを最小限に抑えることに役立つため、AWS WAF と Bot Control を使用することをお勧めします。Bot Control をリリースする前には、AWS WAF にて IP リストベースの Bot 緩和機能を提供していました。現在、以下 2つの IP ベースのマネージドルールセットが利用可能です:

これらのルールを使用することで、悪意のあるリクエストをブロックすることができます。また、これらの IP ルールに加えて、独自の IP リストやパートナー提供のルールセットを使用することができます。
AWS WAF Bot Control は、これらの IP ベースのルールと連携することができ、ボットトラフィックに対してより細かい制御を行うことができます。一般的に、検索エンジンのアクセスは歓迎されます。しかし、Visitor Prioritization を有効にしたい場合、正当な検索エンジンのトラフィックもブロックしたいかもしれませんが、通常は機械的に生成されたトラフィックのみを常にブロックしたいケースが多いでしょう。AWS WAF Bot Control を Visitor Prioritization ソリューションと併用することで、ボットベースのトラフィックを制御することができます。そうすれば、トラフィックが多い状況下でも、より優れたユーザー体験を提供することができます。

図6. AWS WAF Bot Control と Visitor Prioritization ワークフローを組み合わせる

  1. 悪意のあるボットが EC サイトへのアクセスを要求する
  2. AWS WAF はそのリクエストをチェックし、このリクエストが悪意のあるボットから来たものかどうかを検出します。次に Bot Control の設定に基づいたレスポンス(例:403 エラーのステータスコード)を返します
  3. CloudFront は Bot Control の設定に基づいたレスポンスを返します

なお、AWS WAF はカスタムレスポンスにも対応しています。この設定に基づき、AWS WAF は 403だけでなく、200を含む他の適切なレスポンスコードを返します。

ソリューション展開の前提条件

以下が前提条件です:

  • 新しい AWS アカウントを作成するか、既存のアカウントを使用します
  • 新しい CloudFront ディストリビューションを作成するか、既存のディストリビューションを使用します
    オリジン上に少なくとも 2つの HTML ファイルを設置し、パス変更の動作を確認します

    • この例ではデフォルトページとして「/index.html」、Waiting room ページとして「/waitingroom.html」を使用します

CloudFront Functions の新規作成

ステップ1:関数コードのカスタマイズ

  • CloudFront Function の Visitor Prioritization 関数は、以下の JavaScript コードとして提供されます:
/*
 * A flag indicating whether the origin is ready to accept traffic.
 * Unlike Lambda@Edge, CloudFront Functions doesn't support network call.
 * So if you want to change this value, you need to modify then re-deploy
 * this function.
 */
var originAcceptingTraffic = true;

/*
 * The origin hit rate (a value between 0 and 1) specifies a percentage of
 * users that go directly to the origin, while the rest go to
 * a "waiting room." Premium users always go to the origin.  if you want to
 * change this value, you need to modify then re-deploy this function.
 */
var originHitRate = 0.3;

/*
 * Waiting Room Redirect URL
 */

var FullClose = `https://FullCLOSE SITE` // Change the redirect URL to your choice

function handler(event) {
    var request = event.request;
//    var response = event.response;
    var uri = event.request.uri;
    var cookies = event.request.cookies;
    var premiumUserCookieValue = 'some-secret-cookie-value';


    if(!originAcceptingTraffic) {
        console.log("Origin is not accepting any traffic. " +
                    "All requests go to the Full close waiting room.");
        var response = {
                 statusCode: 302,
                 statusDescription: 'Found',
                 headers:
                         { "location": { "value": FullClose } }
                     }
        return response;
    }

    // Check Whether Cookie is available or not.
    // in this sample it checks premium-user-cookie. This name is case
    // sensitive, so if you use upper charactor, please modify name parameter.
    if(cookies.hasOwnProperty("premium-user-cookie") && cookies["premium-user-cookie"].value === premiumUserCookieValue){
        console.log(`Verified Permium user cookie, this request goes to Origin cause it has Cookie with a valid secret value of "${premiumUserCookieValue}".`);
        return request;
      }

    // Lotterly to check go to origin
    if (Math.random() >= originHitRate) {
        console.log("An unlucky user goes to the waiting room.");
        request.uri = '/waitingroom.html';
        return request;
    }
    console.log("A lucky user goes to the origin.");
    return request;
};
  • テキストエディターでファイルを開き、コード内の以下のフィールドを要件に合わせて更新してください:
    • originAcceptingTraffic:オリジンがトラフィックを受け入れる準備ができていない場合は、これを false に設定します。デフォルトは true です
    • originHitRate:新規および通常の顧客リクエストのうち、ウェブサイトへのアクセスを許可する割合を示します。0から 1の間の数値です
    • FullClose:オリジンが利用できない場合に使用するリダイレクト URL です( originAcceptingTraffic = false の場合)

ステップ2:CloudFront Function の作成

  • CloudFront コンソールで CloudFront Function を作成し、Step1 で編集した関数コードをコピーします。そして、コンソールのコードエディタに貼り付け、エディタ内のデフォルトコードを置き換えます
  • Save changes ボタンを選択し、開発ステージにデプロイします

関数のテスト

開発段階で関数がデプロイされると、関数が動作しているかどうかを試すことができます。CloudFront Function Test コンソールをベースに、検証が必要な様々なテストシナリオをシミュレートできるようになっています。

テストケース1:プレミアム Cookie を使ったリクエスト

  • 前のステップで作成した関数のページで、テストタブを選択します
  • この関数はビューアーリクエストフェーズでトリガーされるはずなので、イベントタイプフィールドでビューアーリクエストを選択します
  • コードは現在開発ステージに配備されているので、ステージフィールドで Development を選択します
  • リクエストパラメータを以下のように入力します:
    • HTTP Method: GET
    • URL パス: デフォルトページのパスを設定します(例: /index.html )
    • IP アドレス: 空欄のまま
  • 以下のように Cookie を追加します:
    • Name: premium-user-cookie
    • Value: some-secret-cookie-value
    • Attributes: 空欄
  • Test function ボタンを選択します
  • Output セクションの uri 値が常にデフォルトのページパスになっていることが確認できます

図7. プレミアム Cookie でリクエストした場合の CloudFront Function のレスポンス

テストケース2:プレミアム Cookie を使用しないリクエスト

  • テストケース1の Cookie の設定のみを削除した後、再度テストを実行します
  • Output セクションの uri 値が、デフォルトページのパスか /waitingroom.html のいずれかにランダムに変更されていることが確認できます。コード内の originHitRate の値を調整することで、各ページの反応率をコントロールすることができます。

図8. プレミアムクッキー無しでリクエストした場合の CloudFront Function のレスポンス

また、コード内の様々な変数の値を調整することで、機能が意図したとおりに動くかどうかを確認することができます。コードを更新しても、開発ステージにしか影響しないことに注意が必要です。ライブステージでコードを更新するには、関数を公開する必要があります。

CloudFront のディストリビューションに関数を関連付ける

様々なシナリオをテストして関数を使用する準備ができたら、いよいよ関数を公開します。

ステップ1:関数をライブステージに公開する

  • 関数のページで Publish タブを選択します
  • Publish function ボタンを選択すると、開発ステージでテストしたコードをライブステージに公開することができます

関数を公開した後、その関数を CloudFront ディストリビューションの 1つ以上のキャッシュビヘイビアに関連付けることができます。

ステップ2:関数を関連づける

  • Add association ボタンを選択します
    • Distribution: CloudFront のディストリビューション ID を選択
    • Event type: Viewer request を選択
    • Cache behavior: Default(*) または CloudFront Function を適用するその他のビヘイビアを選択します
    • Add association ボタンを選択します
  • CloudFront に関数が完全にデプロイされるのを待ちます

これで CloudFront Function が CloudFront ディストリビューションに設定したビヘイビアと関連付けられ、グローバルに AWS のロケーションへ公開されました。

デプロイメントをテストする

デプロイが完了したら、ライブステージで CloudFront の関数が正常に動作するかどうかを確認します。

まず、index.html と waitingroom.html の ETag ヘッダを確認する必要があります。

$ curl -i https://[your origin endpoint]/index.html 
HTTP/1.1 200 OK 
x-amz-id-2: N4+xzqRSqQY+ZuAJkmRL6xAAesBVjsg20TVGYlzzeMmbJ4pdejuGh/pVItKvIshcpSOcthC2zMc= 
x-amz-request-id: X6TM23EJY639X5R5 
Date: Fri, 13 Aug 2021 06:25:26 GMT 
Last-Modified: Tue, 20 Jul 2021 08:16:11 GMT 
ETag: "d85834344bc3cb3267806005e1f9bf79" 
Accept-Ranges: bytes 
Content-Type: text/html 
Server: AmazonS3 
Content-Length: 10 

$ curl -i https://[your origin endpoint]/waitingroom.html 
HTTP/1.1 200 OK 
x-amz-id-2: c8J1YAkV4n79eVxA0OImCec+KSnUDFHnjTCBE9hZukYRDwKnpRePeNHlsw/jfHDFd2upiItANrU= 
x-amz-request-id: 5QKH9WDW0R453FH5 
Date: Fri, 13 Aug 2021 06:26:18 GMT 
Last-Modified: Tue, 20 Jul 2021 08:16:12 GMT 
ETag: "87a8e81f406d0e3252d1e045d6c247f9"

そして、CloudFront Test Console で実行したテストシナリオをもう一度試してみてください。

テストケース1:プレミアム Cookie を使用したリクエスト(30回試行)

$ for i in `seq 1 30`; do echo $i; curl -i --cookie "premium-user-cookie=some-secret-cookie-value" https://[CloudFront endpoint]/index.html 2>&1 | grep etag >> curlResultWithCookie.log; done; cat curlResultWithCookie.log |sort |uniq -c;

30 etag: "d85834344bc3cb3267806005e1f9bf79" <<< index.html's etag

プレミアム Cookie が含まれている場合、index.html は Waiting room にリダイレクトされることなく、すべてのリクエストに応答していることが分かります。

テストケース2:プレミアム Cookie を含まないリクエスト(30 回試行)

$ for i in `seq 1 30`; do echo $i; curl -i https://[CloudFront endpoint]/index.html 2>&1 | grep etag >> curlResultWithoutCookie.log; done; cat curlResultWithoutCookie.log|sort|uniq -c

  21 etag: "87a8e81f406d0e3252d1e045d6c247f9" <<< waitingroom.html's etag
   9 etag: "d85834344bc3cb3267806005e1f9bf79" <<< index.html's etag

逆に Cookie が含まれていない場合は、レスポンスから index.html と waitingroom.html が混在していることが分かります。Waiting room へのリダイレクトの比率は、CloudFront Function のコードに設定した originHitRate という変数で制御することができます。

AWS CloudFormation テンプレートと AWS Cloud Development Kit (CDK)

すぐにテストしたい場合はこのテンプレートを使って、AWS CloudFormation のスタックを作成することができます。

このテンプレートでは

  • Origin Access Identity (OAI) を持つ S3 バケット
  • 上記の Amazon S3 オリジンを利用する CloudFront Distribution
  • CloudFront Functions

もしあなたが AWS Cloud Development Kit (AWS CDK) に慣れているのであれば、Github リポジトリにある AWS CDK のサンプルを活用してください。

この Visitor Prioritization Switcher のサンプルでは、1分あたりのリクエスト数など Amazon CloudWatch のメトリクスに応じて、Waiting room に転送するかどうかを、自動的にオン/オフする方法を紹介しています。

図9. Visitor Prioritization Switcher のアーキテクチャ

結論

CloudFront Functions は Visitor Prioritization ソリューションとして、Lambda@Edge と同等かそれ以上の機能を提供します。何故なら CloudFront Functions は膨大な量のトラフィックを、低レイテンシーかつ低価格で処理できるからです、一方で Lambda@Edge は外部ネットワーク統合、ライブラリサポート、はるかに長いコンピューティング能力など、より複雑なユースケースをサポートするのに適しています。また、CloudFront Functions と Lambda@Edge の両方を利用するケースもあるかもしれません。AWS WAF と Bot Control は、不要なトラフィックの防止を含む、追加の Bot 緩和機能を提供します。これらのサービスは Visitor Prioritization 以外のユースケースにおいても、ウェブサイトを助けることができます。セキュリティと可視性は常にサイトの助けとなり、継続的な監視はサイトの可用性に役立ちます。このブログのガイドラインが、お客様のサイトの可用性、拡張性、およびパフォーマンスの向上に役立つことを願っています。

本ブログは Visitor Prioritization on e-Commerce Websites with CloudFront and CloudFront Functions を翻訳したものです。翻訳は SA 森が担当しました。