AWS LambdaのコールドスタートとLLRTによる改善について
コールドスタートとは
Lambdaはイベント駆動型のサーバーレスコンピューティングサービスです。
API GatewayやEventBridge等様々なサービスから起動されます。
Lambdaが呼び出されると、AWSはLambdaを実行するために必要なインフラストラクチャを自動的に構成・管理を行います。
関数が初めて呼び出される場合や長時間呼び出されていなかった場合、新しいコンテナを起動する必要があります。これを「コールドスタート」と呼び、時間がかかり、遅延が発生することがあります。
コールドスタートの影響について
コールドスタートによる遅延は様々な影響が考えられます。
特にリアルタイム性が求められるようなシステムでは、この遅延で大きな障害や想定していなかった事象が発生することがあります。
例えばAPIのレスポンス時間が重要なアプリや、IoTデバイスからのイベント連携処理等の数100ms以下のレスポンスが重要となるものの場合、前述したコールドスタートの影響を考慮した設計・実装をする必要があります。
コールドスタートの対処方法
プロビジョニング済み同時実行
プロビジョニング済み同時実行(Provisioned Concurrency)を使用すると、Lambdaのインスタンスを事前に起動しておくことができ、常に実行可能な状態とすることができます。
そうすることでコールドスタートを回避することができます。
ここでお気づきの方もいると思いますが、実行可能な状態で待機させておく必要があるので通常よりもコストがかかってしまいます。
こちらを採用する場合には事前にトラフィックの予測をしたうえで設定する必要があります。
つまりトラフィックが確実に予測できる場合に有効な手段となります。
ちなみに同時実行数を0にするとLambdaを実行させないようなことも可能です。
例えば設計・実装ミスにより無限再帰ループが発生した場合、こちらを0にすることで強制的にループを停止することも可能です。
もしもの時にはこういった回避手段もあることを覚えておくといいかもしれません。
ランタイムの選択
コールドスタートの時間は、使用するランタイムによって異なります。
例えばNode.jsやPythonのような軽量なランタイムはJavaや.NET Coreに比べてコールドスタートの時間が短くなります。
この実装時点でのランタイムの選択もコールドスタートを抑えるために重要な要素とななります。
ランタイムごとのコールドスタートのベンチマークは以下のサイトが参考になると思います。
関数の最適化
関数の初期化プロセスを最適化することで、コールドスタートの時間を短縮することができます。
例えば初期化時に行う処理を最小限に抑えたり、必要なリソースをキャッシュすることで初期化時間を短縮できます。
コストのかかる処理はできるだけ減らす事が重要です。
デプロイパッケージのサイズを最小限にする
外部モジュールの展開は起動時のコストが大きいため、処理時間に大きく影響を与えます。
可能な限り依存ライブラリを減らすことが重要となります。
メモリを増やす
Lambdaの性能=メモリの容量となります。
メモリの容量を増やし性能を上げることで処理時間の改善につなげることができます。
ただしコストも上がってしまうので注意が必要です。
EventBridgeでLambdaを定期的に実行する
こちらはプロビジョニング済み同時実行(Provisioned Concurrency)のリリースに伴い使われることは少なくなってきたかもしれませんがお手軽に対策可能な手段です。
設定は非常にシンプルでEventBridgeで時間ベースのスケジュールを組んで定期実行するだけですね。
基本的にはこれらをベースに検討いただくことになります。
新規作成時はどれを採用してもいいとは思いますが、すでに動いているものの改修はそれなりにリスクがあるのでソース修正以外の手段を取るのがいいかと思います。
今回はランタイムでの対応LLRT(Low Latency Runtime)を取り上げたいと思います。
LLRTによるコールドスタートの対策
LLRT(Low Latency Runtime)はLambdaのコールドスタートを抑えたり処理速度を改善することができるオープンソースのランタイムとなります。
LLRT ( Low Latency Runtime) は、高速で効率的なサーバーレスアプリケーションに対する高まる需要に対応するために設計された軽量の JavaScript ランタイムです。LLRT は、AWS Lambdaで実行される他の JavaScript ランタイムと比較して、最大10 倍以上の起動速度と最大2 倍の全体的なコスト削減を実現します。これは Rust で構築されており、JavaScript エンジンとして QuickJS を利用して、効率的なメモリ使用と迅速な起動を保証します。
LLRTの導入手順
今回は手っ取り早くレイヤーを使用して導入してみます。
様々な方法があるのでお好みの方法で導入いただければと思います。
- LLRT リリースをダウンロード
llrt-lambda-arm64.zipかllrt-lambda-x64.zipのお好みの方をダウンロードしてください。 - Lambdaを作成する
Amazon Linux 2023のランタイムでLambda関数を作成します。
- レイヤーを作成する
下記のようにダウンロードしたzipファイルをアップし、ランタイムとしてCustom Runtime on Amazon Linux 2023を選択します。
- Lambdaにレイヤーを設定
動作検証
今回はお手軽にDynamoDBに書き込む処理で試してみました。
LLRTのレイヤー版と通常のNodejs 20ランタイムで作成したLambdaで実験してみます。
それぞれ下記の同じソースで実験しました。
import {DynamoDBClient} from '@aws-sdk/client-dynamodb';
import {DynamoDBDocumentClient, PutCommand } from '@aws-sdk/lib-dynamodb';
const client = new DynamoDBClient({});
const docClient = DynamoDBDocumentClient.from(client);
export const handler = async(event) => {
try {
let param = {};
let id = crypto.randomUUID();
let data = crypto.randomUUID();
// dynamoDBに書き込み
await docClient.send(
new PutCommand({
TableName: "testApp",
Item: {
"testId": id,
"data": data,
},
})
);
return {
statusCode: 200,
body: JSON.stringify({
message: '正常',
data: param
})
};
} catch (error) {
console.error('失敗', error);
return {
statusCode: 500,
body: JSON.stringify({
message: '失敗',
error: error.message
})
};
}
};
検証結果
コールドスタート
LLRT
REPORT RequestId: 517d677e-682f-4193-8659-e5d9359967c8 Duration: 26.07 ms Billed Duration: 80 ms Memory Size: 128 MB Max Memory Used: 25 MB Init Duration: 53.26 ms
REPORT RequestId: 31af0b69-0ac2-4666-ac24-10af6ef9c4ba Duration: 24.88 ms Billed Duration: 78 ms Memory Size: 128 MB Max Memory Used: 25 MB Init Duration: 52.48 ms
REPORT RequestId: 2722f73b-6ebe-4180-8755-a67581edc4f6 Duration: 23.39 ms Billed Duration: 75 ms Memory Size: 128 MB Max Memory Used: 25 MB Init Duration: 51.40 ms
REPORT RequestId: 6f6fa945-cd7d-4008-95e0-0815ef439451 Duration: 30.80 ms Billed Duration: 89 ms Memory Size: 128 MB Max Memory Used: 25 MB Init Duration: 57.59 ms
REPORT RequestId: 9bdaf393-4778-4084-a53b-d04b7bb75d6f Duration: 25.72 ms Billed Duration: 82 ms Memory Size: 128 MB Max Memory Used: 25 MB Init Duration: 56.24 ms
Node.js
REPORT RequestId: 3c1d55f4-820f-429a-b8f9-8ef689dfd2cd Duration: 1023.51 ms Billed Duration: 1024 ms Memory Size: 128 MB Max Memory Used: 90 MB Init Duration: 363.07 ms
REPORT RequestId: f45c49c5-e18e-4039-813e-130bc024ff10 Duration: 1014.80 ms Billed Duration: 1015 ms Memory Size: 128 MB Max Memory Used: 90 MB Init Duration: 327.48 ms
REPORT RequestId: a40b85ef-6972-45c0-b619-03e4f7c52b7e Duration: 1046.83 ms Billed Duration: 1047 ms Memory Size: 128 MB Max Memory Used: 89 MB Init Duration: 331.08 ms
REPORT RequestId: 9d4be9e5-2ef7-48a1-b207-98136334e8f8 Duration: 1036.71 ms Billed Duration: 1037 ms Memory Size: 128 MB Max Memory Used: 89 MB Init Duration: 326.05 ms
REPORT RequestId: ea9d165c-f608-4328-bcc0-db2996a3747d Duration: 1056.10 ms Billed Duration: 1057 ms Memory Size: 128 MB Max Memory Used: 89 MB Init Duration: 339.84 ms
ウォームスタート
LLRT
REPORT RequestId: 5c3607c2-cbff-461f-a161-f69693e82aa2 Duration: 6.59 ms Billed Duration: 7 ms Memory Size: 128 MB Max Memory Used: 25 MB
REPORT RequestId: 7754b6ad-a102-4cae-8bb9-72de9dee7624 Duration: 14.36 ms Billed Duration: 15 ms Memory Size: 128 MB Max Memory Used: 25 MB
REPORT RequestId: accbe6f9-d158-4b0f-b0cc-62005327cdd4 Duration: 10.57 ms Billed Duration: 11 ms Memory Size: 128 MB Max Memory Used: 25 MB
REPORT RequestId: bcf48540-dd78-48f1-a434-b200a350f391 Duration: 6.49 ms Billed Duration: 7 ms Memory Size: 128 MB Max Memory Used: 25 MB
REPORT RequestId: cd1e63ad-c35d-4576-8502-4ffe98eb20e6 Duration: 14.76 ms Billed Duration: 15 ms Memory Size: 128 MB Max Memory Used: 25 MB
Node.js
REPORT RequestId: 2be73753-a002-494b-a9e4-7b0341ea0925 Duration: 40.10 ms Billed Duration: 41 ms Memory Size: 128 MB Max Memory Used: 91 MB
REPORT RequestId: 92ba140a-0918-45d0-a1f3-e611f7b25923 Duration: 50.47 ms Billed Duration: 51 ms Memory Size: 128 MB Max Memory Used: 91 MB
REPORT RequestId: 2efebd24-2023-4c74-a811-5cba2bb7f7cd Duration: 29.51 ms Billed Duration: 30 ms Memory Size: 128 MB Max Memory Used: 91 MB
REPORT RequestId: e0f28504-83e4-4249-af31-8bc629deb820 Duration: 37.08 ms Billed Duration: 38 ms Memory Size: 128 MB Max Memory Used: 91 MB
REPORT RequestId: 7da9f168-740c-4b58-b6fe-9302cca070c6 Duration: 75.73 ms Billed Duration: 76 ms Memory Size: 128 MB Max Memory Used: 91 MB
LLRTはやい!!!
結果としてコールドスタート、ウォームスタートともにLLRTで処理時間、メモリ使用量ともに大幅に改善されることが分かりました。
これだけ改善できると選択肢としてはかなり有力ですね…
LLRTの注意事項
今回の検証にてLLRTのメリットが確認できました。
ただし、LLRTをにはいくつか注意事項があります。
・ LLRT自体が検証中のものであり将来的に変更が入る可能性がある。
・ LLRTはNode.js APIの一部のみをサポートしているため必要な機能が使えない場合もある。
・ 大規模なデータ処理や反復タスク等に弱い。(下記参照)
There are many cases where LLRT shows notable performance drawbacks compared with JIT-powered runtimes, such as large data processing, Monte Carlo simulations or performing tasks with hundreds of thousands or millions of iterations. LLRT is most effective when applied to smaller Serverless functions dedicated to tasks such as data transformation, real time processing, AWS service integrations, authorization, validation etc. It is designed to complement existing components rather than serve as a comprehensive replacement for everything. Notably, given its supported APIs are based on Node.js specification, transitioning back to alternative solutions requires minimal code adjustments.
まとめ
Lambdaのコールドスタートはリアルタイム性が求められるシステムにとって大きな課題となります。
コールドスタートの対策としてはプロビジョニング済み同時実行やランタイムの選択、関数の最適化、EventBridgeでの定期実行などさまざまな手段があります
その中でも今回紹介したLLRTを活用することでコールドスタートを抑えるとともにLambda自体のパフォーマンスを向上させることも可能です。
AWSの様々なサービスと連携させることができ、アプリエンジニアでも比較的触ることの多いLambdaですが、採用するにあたっては性能要件、イニシャルコストやランニングコスト含めて様々な観点から検討が必要となります。
コールドスタート問題は比較的ぶつかることが多い課題となりますのでしっかりと見極めたうえで便利に使っていきたいですね。
Discussion