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

AWS ブログに「Lambda@Edge Design Best Practices」の記事が掲載されておりましたので、忙しいエンジニアのみなさまのために、自分なりに要点をまとめつ、5分で読めるベストプラクティスとしてシェアしたいと思います。

このベストプラクティス記事の構成

以下、3つの観点で構成されています。

  • Lambda@Edge ソリューションを実装した一般的な使用例
  • Lambda@Edge 関数の起動タイミングを選択する方法
  • Lambda@Edge のパフォーマンスとコスト効率を最適化するための推奨事項

一般的な使用例

Lambda@Edge を使う利点を、次の4つのカテゴリで分類しています。

  1. パフォーマンス
  2. 動的コンテンツの生成
  3. セキュリティ
  4. オリジンの独立性

1. パフォーマンス

Lambda@Edge を使う最大の利点の1つは、コンテンツがオリジンから返されたときにキャッシュされる可能性を高めたり、既にキャッシュされたコンテンツの利便性を高め、キャッシュヒット率を向上させること。キャッシュヒット率を向上すると、キャシュミスによるレイテンシを回避し、アプリケーションパフォーマンスを向上させることができます。 以下、Lambda@Edge を使用してキャッシュヒット率を向上させる例になります。

  • レスポンスのキャッシュコントロールヘッダーを追加または変更する
  • オリジンからの 3xx レスポンスのリダイレクトに従って実装し、ビューワーのレイテンシを減らす
  • クエリ文字列またはユーザーエージェントの正規化を使用し、リクエストを可変的に減らす
  • リクエストヘッダー、Cookie、またはクエリ文字列の属性に基づいて、異なるオリジンに動的ルーティングする

2. 動的コンテンツの生成

Lambda@Edge では、リクエストやレスポンスの属性に基づいてカスタムコンテンツを動的に生成できます。例えば、次のようなことができます。

  • リクエスト属性に基づいて、イメージのサイズを変更する
  • Mustache などのロジックレス・テンプレートに基づいて、ページをレンダリングする
  • A/B テストを行う
  • 期限切れまたは古いリソースへのすべてのリクエストに対する 302/301 リダイレクト・レスポンスを生成する

3. セキュリティ

Lambda@Edge は、カスタム認証と承認を処理するためにも使用できます。以下、ユースケースの例になります。

  • アクセス制御を実装するカスタムオリジンへのリクエストに署名する
  • 例えば、JWT / MD5 / SHA トークンハッシュを使用し、ビューアトークン認証を設定する
  • bot 検出の設定
  • HSTS または CSP セキュリティヘッダーを追加する

4. オリジンの独立性

いくつかのシナリオでは、オリジンはリクエストとレスポンスのために追加のロジックを必要とします。これを元のサーバー上で実行されるコードで実装する代わりに、よりシームレスなソリューションのために、CloudFront で Lambda@Edge 関数を実行できます。例えば、ロジックを実装して次のことができます。

  • プリティー URL を作成する
  • 発信元リクエストの認証と承認を管理する
  • オリジナルのディレクトリ構造と一致する URL またはリクエストを操作する
  • カスタムロードバランシングとフェイルオーバーロジックの実装

正しいトリガーを選択

Lambda@Edge 関数を次の4つの異なる CloudFront イベントで実行するようにトリガーすることができます。

ビューワー オリジン
リクエスト リクエスト毎に実行
CloudFront のキャッシュがチェックされる前
キャッシュミスで実行
リクエストがオリジンに転送される前
レスポンス すべてのリクエストで実行
オリジンまたはキャッシュからのレスポンスを受け取った後
キャッシュミスで実行
オリジンからレスポンスを受け取った後


開発者ガイドには、実行したいことに基づいて Lambda@Edge トリガーを選択するのに役立つ一般的なガイダンスが提供されています。以下の質問は、どの Lambda@Edge トリガーを使用するかを決めるのに役立ちます。

質問 選択すべき Lambda@Edge トリガー
関数をキャッシュミスで実行したいか? オリジントリガー
すべてのリクエストで関数を実行したいか? ビューワートリガー
キャッシュキー(URL、Cookie、ヘッダー、クエリ文字列) を変更しますか? ビューワーリクエストトリガー
結果をキャッシュせずにレスポンスを変更するか? ビューワーレスポンストリガー
オリジンを動的に選択するか? オリジンリクエストトリガー
オリジンへの URL を書き換えますか? オリジンリクエストトリガー
キャッシュされないレスポンスを生成するか? ビューワーリクエストトリガー
レスポンスをキャシュする前に修正するか? オリジンレスポンストリガー
キャッシュ可能なレスポンスを生成するか? オリジンリクエストトリガー

コスト効率を最適化する

Lambda@Edge は次の2つの要因に基づいて請求されます。(投稿時点の内容になります。最新は、公式サイトをご確認ください)

  • リクエスト数
    • リクエスト 100 万件あたり 0.60 USD (リクエスト 1 件あたり 0.0000006 USD)
  • 関数の所要時間
    • 1 GB-秒の使用につき 0.00005001 USD
    • 例えば、Lambda@Edge 関数の実行ごとに 128 MB のメモリを割り当てる場合、使用時間に対する課金は 128 MB-秒の使用につき 0.00000625125 USD になります。
    • Lambda@Edge 関数は 50 ミリ秒単位で計測されます。

Step1. 関数呼び出しの最適化

最も限定的な CloudFront Behavior に対して Lambda@Edge をトリガーすることにより、関数呼び出しを最適化します。例えば、Lambda@Edge がビューワーを認可するために使用されるこのソリューションでは、Lambda@Edge 関数はプライベートコンテンツに対してのみトリガーされます。この使用例では1、CloudFront のキャッシュ Behavior で private/* パスパターンを指定することによって、オリジンのプライベートコンテンツが識別される。

Step2. 正しいトリガーの選択

一部の Lambda@Edge ロジックは、HTTP レスポンスに HSTS ヘッダーを追加するときなど、オリジンまたはビューワーのトリガーを使用して実装できます。これらのシナリオでは、ビューワートリガーではなく、オリジントリガーを選択して、Lambda@Edge の呼び出しを最適化し、CloudFront キャッシュを活用します。

Step3. 関数のリソース割り当てを最適化

ビューワートリガーのリソース割り当ては 128MB に制限されていますが、オリジントリガーには最大 3,008MB を割り当てることが可能です。メモリサイズが増加すると、使用可能な CPU が同等に増加します。これは、関数の所要時間を最適化する上で必要不可欠です。ロジックに必要なものと予算に応じて、これらの要素のバランスをとるために最適なメモリサイズ構成を選択する必要があります。

パフォーマンスを最適化する

Lambda@Edge 関数は、CloudFront ネットワーク上の全世界の AWS ロケーションで実行されます。ビューワーリクエストがエッジロケーションにヒットすると、リクエストはエッジで終了し、Lambda@Edge はビューワーに近い AWS ロケーションで関数を実行します。

Lambda@Edge を使用する場合と使用しない場合の CloudFront ディストリビューションを比較すると、ユーザーが感じるレイテンシーは異なります。この相違点は、CloudFront ディストリビューション構成、トリガーの種類、エッジの場所、関数コード、アプリケーションロジック など、いくつかの要因によって起きます。ここではいくつかの例を以下に示しています。(サービスのパフォーマンスは絶えず改善しているため、下記、待ち時間の数値は、時間とともに変化します)

  • 例1.API Gateway の背後にある Lambda 関数を持つ us-east-1 リージョンのオリジンの場合:
    アプリケーションは、260 ミリ秒(us-east1 リージョンへのネットワーク FBL:160ミリ秒 + オリジン FBL:100ミリ秒)の平均ファーストバイトレイテンシー(FBL)で、グローバルビューワー用の 3xx リダイレクトを動的に生成する。リダイレクトロジックが Lambda@Edge 関数に移行すると、平均アプリケーション FBL は 110 ミリ秒(ビューワーリクエストトリガーでは CloudFront FBL:80 ミリ秒 + Lambda@Edge 関数呼び出し時間:30 ミリ秒)に低下する。

  • 例2.キャッシュヒット率 95% の CloudFront での静的 html ファイル配信の場合:
    Lambda@Edge を使用して HTTP セキュリティヘッダーを追加する方法を検討する。Lambda@Edge がキャッシュミスでのみ実行するように設定されている場合、平均 FBL は 0.5 ミリ秒(オリジンリクエストトリガーで 5% x 呼び出し時間:10 ミリ秒)増加する。

各ユースケースについて、Lambda@Edge の実装を最適化することによって、ビューワーのエクスペリエンスを向上させることができます。そのためには、Lambda@Edge 関数の所要時間を短縮し、サービスによって設定された機能とスケーリング制限内で、関数が実行されるように検討してください。

所要時間の短縮

まず、以下を実施して関数の所要時間を短縮します。

パフォーマンスのために関数コードを最適化する

例えば、実行コンテキストを再利用し、すべての呼び出しで変数とオブジェクトの再初期化を制限します。これは Lambda@Edge コードがローカルで検索、格納、および参照する外部化された構成を持っている場合に特に重要です。代わりに、静的な初期化またはコンストラクタ、グローバル変数、静的変数、およびシングルトンを使用できるか検討します。

関数が使用する外部ネットワーク呼び出しを最適化する

  • TCP Keep Alive を有効にし、以前の呼び出しで確立した接続(HTTP、データベースなど)を再利用する
    • さらに可能であれば Lambda@Edge 機能が実行されているのと同じ地域のリソースをネットワークコールして、ネットワークのレイテンシーを短縮する。これを実行できる方法の1つは、DynamoDB グローバルテーブルを使用すること。
  • 関数内で必要なリソースのみに対して外部リクエストを行う
    • 例えば、Aurora または S3 Select でクエリフィルタを使用できる。
    • また、リクエストした外部変数が頻繁に更新されず、ビューワーごとに変化せず、すぐに伝播する必要がない場合は、コード内で定数を使用し、変数が変更されたときのみに関数を更新することを検討する。

デプロイパッケージを最適化する

例えば、自分で書くことができる単純な関数の外部パッケージへの依存を避けます。外部リソースを使用する必要がある場合は、軽量パッケージを選択します。さらに、 minifybrowserify などのツールを使用して、デプロイパッケージをコンパクトにします。

積極的に上限緩和申請する

次に Lambda@Edge 関数の制限スケーリングの制限に注目し、必要に応じて積極的に制限を増やすことをリクエストします。以下を例に、関数が必要とする限界をどのように見積もるかを説明します。

  • 仮定
    CloudFront で 5,000 件のリクエスト(RPS)とキャッシュヒット率が 90% の静止画を配信しており、HTTP セキュリティヘッダーを追加するためにオリジンリクエストトリガーを持つ Lambda@Edge 関数を使用している
    • Lambda@Edge は 10%(キャッシュミス率) x 5,000 RPS = 500 RPS のレートで呼び出される
      • これは単純な関数なので、1 ミリ秒の平均実行時間を見積もることが出来ます。定常状態での同時実行数を計算するには、平均実行時間に 10 ミリ秒を加算し、これを事前に計算した RPS で乗算する。
      • この例では、計算は 500 RPS x(10 ミリ秒 + 1ミリ秒)= 55 同時実行であり、これはアカウントあたりの同時実行のデフォルト制限値 1,000 を大幅に下回る。
    • トラフィックプロファイルで Lambda@Edge 関数が数秒から数百回の同時実行でバーストする必要がある場合の考慮
      • 突発的なバーストの間、Lambda@Edge は同時実行機能を直ちに所定の量に制限される。
      • さらに、スケールアウトすると、コールドスタートは単純な関数よりも桁違いに実行時間を増加させる。

元記事のまとめ

CloudFront と Lambda@Edge を DynamoDB グローバルテーブルのような他の AWS サービスと共に使用することで、用途にあった高性能な分散型サーバーレス Web アプリケーションを構築できます。いくつかの一般的な Lambda@Edge ユースケースを例に、Lambda@Edge 実装のパフォーマンスを向上させるためのベストプラクティスを推奨しながら、予算にあったものであることを確認します。

丸毛の所感

最近、Lambda@Edge についてご相談をいただくことが増えていたので、ドンピシャのタイミングで本家よりベストプラクティス記事が投稿されたので、とても参考になった!また、Lamdba@Edge の使いどころ、正しいトリガーを選択するための9つの質問、そして最適化するための推奨事項など、いくつかのサンプルを含めて説明されており、非常に理解しやすい内容でした!今後、Lambda@Edge を検討する際に何度も見返すベストプラクティス記事になりそう!

以上!大阪オフィスの丸毛(@marumo1981)でした!

関連記事

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

【新機能】Lambda@Edge で Node.js 8.10 がサポートされました!