[レポート] AWSで高性能の.NETサーバーレスアーキテクチャを構築する #XNT401 #reinvent

2021.12.04

いわさです。

AWS re:Invent 2021で行われた、「Build high-performance .NET serverless architectures on AWS」のセッションレポートです。

この記事では、要点・見どころ・ポイントについてまとめてみました。

セッション概要

DESCRIPTION

Are your .NET serverless applications tuned for performance? In this session, learn AWS architectural best practices for .NET serverless applications that help you save development time, reduce costs, and increase efficiency. Also learn various serverless architectures and implementation design patterns for connecting NoSQL or relational data stores that will help optimize key performance capabilities, including cold-start times in .NET AWS Lambda functions.

SPEAKERS

  • Andy Hopper

SESSION LEVEL

  • 400 - Expert

レポート

アジェンダ

このセッションでは以下について学ぶことが出来ます。

  • Lambdaのインフラストラクチャーとコードが実行される仕組み
  • パフォーマンス測定方法
  • パフォーマンス改善方法
    • コードの最適化方法
    • コールドスタートの影響を最小化する方法
    • 適切なメモリ割り当て
    • Graviton2の選択

Lambdaのインフラストラクチャーとコードが実行される仕組み

現在、コードはZIPファイルでアップロードする方法とコンテナ化してアップロードする方法がありますが、どちらもLambdaインスタンスへデプロイされます。
そして現在様々なイベントソースをLambda関数のトリガーとして指定することが出来ます。

LambdaはFirecracker microVMを使って、高性能でセキュリティの高いマルチテナントインフラストラクチャを提供しています。
FirecrackerはAWSが開発したメモリフットプリントの少ないOSSのサーバーレス基盤です。

デプロイ方法の選択方法とトレードオフなど

様々なデプロイ方法を選択することが出来ます。

  • デプロイスピードを早く、管理範囲を最小限にしたければZIPファイル
  • マネージドランタイム以外であればカスタムランタイム
  • コードの再利用を行うためのLambdaレイヤー
  • 完全に制御したい場合はコンテナイメージ(サイズは最大10GB)

パフォーマンス測定方法

Lambdaのパフォーマンス測定方法として以下を紹介しています

  • CloudWatch(Logs, Lambda Insights, Synthetics)
  • AWS X-Ray
  • CodeGuru Profiler

CodeGuru Profilerは.NETを現在サポートしていないため、このセッションでは取り扱われていません。

その中でもAWS X-RayはLambdaを測定するのに優れており、デモの中で何度も利用しています。
また、有効化も簡単です。
さらに、.NETに関していうとSQL Serverを呼び出すためのX-Rayハンドラーも用意されています。

初期化の改善

LambdaハンドラーはLambdaを呼び出すたびに実行されます。
ここではコードのうち初期化コードに注目し、それらは一度だけ実行されるように変更しています。
方法としてコンストラクタやメンバーフィールドなどLambdaハンドラーの外へ移動します。

デモでは、初期化でSystemsManagerクライアント使ってパラメータストアにアクセス、SecretManagerクライアントを使ってシークレットを取得しています。
これらの利用シーンの多くはLambda関数を呼び出す毎に実行する必要はありません。

  • 改善前: 2.17秒
  • 改善後: 0.325秒

このデモでは平均レイテンシが約75%減少しました。

関数ハンドラーのスループットを最大化する

Lambdaではメモリ割り当て状況に応じてCPUサイズが変わります。
そして、.NETではasync/awaitキーワードを使って簡単に非同期実装を行うことでき、ネットワークやディスクのI/O、AWS SDKの呼び出しなどをバックグラウンドスレッドへオフロードし、マルチコアCPUを効率的に活用できます。

デモでは、4つの相互依存のないDynamoDBテーブルからデータフェッチしている処理を並行実行するデモを行っています。
改善前は1つフェッチが完了するごとに次のテーブルをフェッチしていましたが、Task.WhenAllで4つ全てを並列実行し全てのタスクの完了を待機するよう修正されています。

  • 改善前: 0.671秒
  • 改善後: 0.580秒

先程ほど劇的な改善は見られていませんが、それでも10%改善されています。

コールドスタート対策

一定時間呼び出しがなかった場合、Lambdaインスタンスはシャットダウン(削除)されます。
次に実行されるときには環境とコードのセットアップから行われ、コールドスタートと呼ばれ遅延の原因になります。
さらに、.NETに関していえば初回起動時にJIT処理プロセスが発生するため、更に遅延の原因になりえます。

このセッションでは解決策として、.NETのReadyToRunを使った事前コンパイルと、LambdaのProvisioned Concurrencyを紹介しています。

デモではEntityFrameworkのDbContextの初期化時に発生するレイテンシに注目し、Provisioned Concurrencyを使った事前ウォームアップでパフォーマンス改善を行っています。

  • 改善前: 4.12秒
  • 改善後: 0.047秒

そのAPIの初回利用者(あるいはシャットダウンによる影響を受ける利用者)からすると劇的な改善となります。

キャッシュの利用

同じデータセットから何度もフェッチしているコードはキャッシュを活用出来るか検討します。
方法としてElasticCache(Memcached/Redis)などのキャッシュストアあるいはインメモリキャッシュがあります。

ただし、インメモリキャッシュはLambdaの特定インスタンス固有のものになり同じキャッシュ(Lambda)にヒットしない可能性があるので注意も必要です。
とはいえキャッシュ戦略を何も取らないよりは効果的だとセッション内で紹介しています。(デモなし)

適切なメモリの割り当て

適切なメモリサイズを割り当てることにより、使用料金あるいはCPUリソースの効率化につながります。
非効率な状態だと、メモリサイズを節約しても逆に料金が高くなる場合があります。

ここでは AWS Lambda Power Tuningで適切なメモリ割り当てを導いて、コストパフォーマンスの改善を行うデモを紹介しています。

Graviton2の使用

数ヶ月前にLambdaでARM64プロセッサのGraviton2がサポートされました。
また、.NETはARM64プラットフォームでのパフォーマンス最適化がされています。

一般的にx86よりも約20%コストパフォーマンスが良いとセッション内では紹介しています。
対象のワークロードで削減効果が出るか評価する価値は十分にありと思います。
セッション内ではワークロードごとに必ずテストをするようにも述べられています。

まとめ

このセッションではLambdaで稼働させているワークロードのレイテンシ/コスト様々な点でパフォーマンスを改善する方法を学ぶことも出来ます。
Lambdaとして取ることで出来る汎用的なアプローチと、.NETで取ることが出来るアプローチと混在していますが、汎用的な部分についてはLambdaであればどの言語でも共通で参考になる情報だと思うので、是非動画を見て頂きたいと思います。

セッション内ではメモリ割り当ての最適化についてCompute Optimizerは言及されていませんでしたがこちらも有効だと思います。

[アップデート] AWS Compute Optimizer で Lambda 関数の最適なメモリサイズがレコメンド可能になりました! | DevelopersIO

参考