5
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

AWS LLRT を使って lambda コールドスタートをメチャメチャ速くする件について

Last updated at Posted at 2025-12-10

こんにちは。Supership の @masahito-suzuki です。

この記事は Supershipグループ Advent Calendar 2025 の 11日目の記事です。

はじめに

AWS環境で、比較的よく使われる機能が aws lambda かと思います。

一方で「サーバーレス」と言う言葉とともに「古くから aws lambda + API Gateway を使ったWebアプリ環境」と言うものが存在します。

一方で「aws lambdaでのサーバーレス環境」は「コールドスタート問題」があり、そのための「改善対策」として

  • SnapStart

これが「2022年末頃にJava版が登場」しましたが、一方で「node.js」は現在もsnapStartは対応していません。

また node.js lambda でのコールドスタートがどれくらい遅いのかと言えば

  • aws sdk(v3)のs3 client を import or require して、S3のKey読み込みを実施した場合
  • nodejs(v22)で実行した結果

    REPORT RequestId: 828f62d0-ddf7-4f81-81d6-b3bd777bfd72 Duration: 4801.02 ms Billed Duration: 4802 ms Memory Size: 128 MB Max Memory Used: 97 MB Init Duration: 156.66 ms

    • Billed Duration: 4802 ms

こんな感じで「awssdk(v3)のs3clientで読み込み処理=約4.8秒」と言う結果になります。

今回これを「めちゃくちゃ改善できる」ものとして

これを利用する事で「えーと・・・コールドスタート?なにそれ美味しいの?」と言うレベルで「驚愕の体験」ができると思います(ワクワク)。

この記事でわかること

  • LLRTを使う事で、コールドスタートがほとんど気にならなくなる
  • LLRTを使う事で、利用メモリも少なくなる
  • 何故コールドスタートが遅いのか(nodejsなどがaws lambda に向かないのか)と言う事が理解できるようになる

対象読者

  • aws lamda の最低限のメモリ 128MB で 関数URLを使いたい人
  • 社内ツールを AWS Lambdaの関数URLで実装して、安価(ほぼ0円)で運用したい人

環境

項目 バージョン
実行環境 AWS Lambda
対象言語 LLRT-0.7.0Bata
比較言語 node.js v24

そもそも LLRT (Low Latency Runtime) とは

まず LLRTとはなんぞやについて、実際に使ってみての感想を踏まえて説明していきたいと思います。

LLRT を使った感想

ざっくりと使った感じとしては node.js 互換がある軽量なJavaScriptランタイムです。

内部のJavaScriptエンジンは、QuickJSというC言語で実装されたもので、一方で「Node.js部分」の実装は rquickjs と言うRust言語からの呼び出しができるライブラリを利用しており「Node.js部分のAPI実装等」はRust言語で行われているとの事。

また使った感じでは「最新のnode.jsで 非推奨」の関数や機能は LLRT では除外されていたり、httphttps のライブラリが利用出来ない、そして __dirname など(これも非推奨)も利用できないわけで、なのでどちらかと言えば「AWS Lambdaに特化したプログラム言語」であると感じました。

いつも HttpClient関連の実装は httpsライブラリ を使ってたので、最初どうやってHttpClient部分を実装するのこれ!!って思ったのですが、そもそも javascriptには

  • fetch関数

が標準関数として存在するので、その意味では https ライブラリが使えなくてもあまり問題ない感じかと思います。

また LLRT は LLRTのダウンロード先 から、以下のzipファイルをダウンロードして

  • llrt-lambda-arm64-full-sdk.zip: ARM系CPUの場合はこちら
  • llrt-lambda-x64-full-sdk.zip: Intel及びAMDはこちら

AWS Lambdaのレイヤー登録する事で aws lambda環境用でのaws-sdk(v3)が利用可能なランタイムとして利用できるので、大抵の場合は「普通にAWS Lambda上で LLRTが利用できる」と言えます。

ただ一方で Node.JS用の「npmのライブラリを AWS Lambdaで使おうとした場合」は「LLRT で除外された機能を使われてるケースがあり実行できない等がありえる」のと、一方で検証環境で LLRTを使うにしても、httpsライブラリが利用できないため、HTTPサーバ的な形での検証が難しい(結局ローカル実行ではnode.jsを使う事になるケースがある)など、検証部分をローカルで行うのが難しいなど、この辺が面倒かと思いました。

しかし最大の利点である「LLRTは lambda コールドスタートがめちゃめちゃ高速」であり、冒頭に書いた「AWS SDK(V3)のS3Clientライブラリを使ってS3のKey読み込み: 約4.8秒 」この処理と同じ内容をLLRT(0.5.1-beta)で実行した場合驚愕の 約0.26秒

REPORT RequestId: 3851698e-8163-4f38-a9f6-3d943a064465 Duration: 190.13 ms Billed Duration: 258 ms Memory Size: 128 MB Max Memory Used: 31 MB Init Duration: 67.85 ms

  • Billed Duration: 258 ms

このように 約18.6倍高速 にコールドスタートが実行できるわけで、また利用メモリも少ない実行となります。

  • NodeJS: 91MB
  • LLRT: 31 MB(約3分の1圧縮)

このように、LLRT は色々制限や問題がある一方で「コールドスタートをかなり高速に実行できる環境」であり大変魅力的な環境であると言えます。

何故 LLRT はAWS Lambda での コールドスタート が高速に動作できるのかを解説

そもそも「何故 AWS Lambdaはコールドスタートが遅いのか」と言う点から話を考えてみたいと思います。
まず、AWS Lambdaの高速化において、過去AWSは Lambdaに SnapStart と言うものを導入しました。
そして SnapStartによってコールドスタートが速くなるのかと言えば

  • 初期で読み込むライブラリ等を読み込んだ時点の スナップショット を作成し、次のコールドスタートからこの スナップショット を利用してそこから再開する事で「コールドスタートでのライブラリ読み込み時間を端折る」これが SnapStartの機能であり、結果的に高速起動できる

つまり「コールドスタートでの弱点」それは「起動時のライブラリの読み込みに時間がかかる」と言う事で、冒頭でも示した「AWS-SDK(V3)のS3Clientライブラリの読み込みに 約4.8秒」の結果で示す通りなんですよね。

では何故「コールドスタート時に、ライブラリの読み込みに時間がかかるのか」と言えば、これはプログラム言語のイニシアティブ合戦=すなわち戦いの成果として「プログラムを如何に高速に実行できるか」の根幹である「プログラムを高速に動かすため初期実行時における、プログラムの最適化」が行われているが、それが「コールドスタートで時間がかかってしまう」結果になるわけなんですね。

じゃあこの「最適化」って何かって言うと、簡単に言えば「ネイティブ実行形式じゃないプログラム言語=インタプリタ型など」の場合、どうしても「プログラムを読み込んで、実行時にマシン語変換する」ので「速度はどうしても遅く」なります。

そのため「ある程度繰り返して利用されるもの=ライブラリなど」は「最適化として予めマシン語変換し、さらに最適化としてマシン語の内容を、さらに最適化」など「プログラム言語を動かすランタイム」による「高速化」の事を「最適化」と呼びます。

これは「通常のプログラム実行環境やサーバ型プログラム」では「大変有効な機能」なのですが、一方で「コールドスタート実行が前提のAWS Lambdaでは、この最適化は逆効果」となるわけなんですね。

つまり「AWS Lambdaでは、プログラム実行の最適化によって逆にコールドスタートが遅くなる要因」となり、その理由こそ「AWS Lambdaは一定期間実行しないとコールドスタート実行になる」ので、そのため「コールドスタート実行が前提で実行を考える必要があり、その結果最適化をしないプログラム言語のほうが高速に実行される」ことになってしまうわけなんですね。

そして、その結果生まれたのが、この「LLRT」であり、これにより「コールドスタートでも高速にプログラムが動く」ようになります。

次に「実際にAWS LambdaにLLRTを導入」して「実際に高速動作体験」を確認していきたいと思います。

AWS Lambda に LLRT 環境を導入する手順

まず導入にあたって、ざっくりとですが、以下の事を行っていきます。

  1. LLRTのサイト(githubリポジトリ)から aws lambda用の ランタイム(full)をダウンロード
  2. 項1で取得したランタイムを AWS Lambdaレイヤーに登録
  3. AWS Lambda 関数を作成する(Amazon Linux 2023で)
  4. 色々LLRT実行に必要な設定等を行う
  5. サンプルプログラムを実行してみる

こんな感じで進めていきたいと思います。

1. LLRTのサイト(githubリポジトリ)から aws lambda用の ランタイム(full)をダウンロード

まず、LLRT は以下のzipをダウンロード先 LLRTのダウンロード先 から、AWS Lambda 用のランタイムをダウンロードします。

  • llrt-lambda-arm64-full-sdk.zip

筆者的には arm64 の方が「高速に実行できる」と「AWS推し」なので、こちらをよく使いますので、これはどちらでも構わないです(AWS Lambda のCPU選択もこれに合わせれば)。

まずこれをダウンロードしてローカルに保存します。

2. 項1で取得したランタイムを AWS Lambdaレイヤーに登録

次にAWS にログインして Lambda → レイヤー → (レイヤーの作成) ボタンを押下し、以下のように登録し
image.png

次に

  • 先程ダウンロードしたzipファイルを (ファイルを選択) ボタンを押下して選択

それが終わったら

  • 「一番下にある」(作成)ボタン

を押下してレイヤー登録を行います。

また「互換性のあるアーキテクチャー・オプション」の部分は「先ほどダウンロードした LLRT のCPU」に合わせた選択を行ってください。

3. AWS Lambda 関数を作成する(Amazon Linux 2023で)

次に「AWS Lambda環境を作成」します。
Lambda → 関数 → (関数を作成) ボタンを押下し、以下のように登録し (関数を作成) ボタンを押下します。
image.png

関数名などは「わかりやすい名前」をつけてもらえればと思います。
また「CPUは先程の llrmランタイムと同じもの」を選択してください。

一旦これで作成はできたので、次に環境の設定を個別に行います。

4. 色々LLRT実行に必要な設定等を行う

4.1. レイヤーの登録

まず最初に「先ほど登録したレイヤーをこのLambda関数に適用する必要」があります。

さきほど作成した Lambda関数を開くと「メニュー」の中

コード | テスト | モニタリング | 設定 | エイリアス | バージョン

これの「コード」が選択されているので、その「コード」の「下にスクロール」にすることで

  • レイヤー

image.png

と言う「項目」があるので、そこの (レイヤーの追加) ボタンを押下します。

image.png

  • レイヤーソース: カスタムレイヤー
  • カスタムレイヤー: llrt
  • バージョン: 1

先程登録したレイヤーを選択し (追加) ボタンを押下することで、レイヤーの登録が完了します。

4.2. 実行ハンドラーの設定

次に指定した「レイヤー」に対する「実行ハンドラー」の設定を行います。

これは先程の「レイヤー」設定と同じく「コード」の「下にスクロール」にすることで

  • ランタイム設定

image.png

と言う「項目」があるので、そこの (編集) ボタンを押下します。
image.png

ここで編集が必要なのが

  • ハンドラー: index.handler

の部分で、上のように index.handler と指定する必要があります。

そのあと (保存) ボタンを押下する事で、環境周りの設定は完了します。

あと補足として「lambdaの初期値」では

  • タイムアウト: 0 分 3 秒

と「短い」ので、これを「適当な長さにしておく」と良いかもしれないです。

5. サンプルプログラムを実行してみる

まず、初期の Amazon Linux 2023 の Lambda 環境だと

image.png

shのサンプルやREADME.md が存在するので、これらを「削除」します。

次に「サンプルとなるプログラム」として以下を AWS Lambda に追加します。

◆ index.cjs

const s3client = require("@aws-sdk/client-s3");

exports.handler = async function(event, context) {
    const value = await getS3("test-minto", "test/hogehoge");
    const message = "hello LLRT: " + await value.Body.transformToString();
    console.log(message);
    return {
        statusCode: 200,
        body: message
    };
}

const getS3 = async function(bucket, key) {
    const s3 = new s3client.S3Client({
        region: "ap-northeast-1"
    });
    return await s3.send(
        new s3client.GetObjectCommand({
            Bucket: bucket,
            Key: key
        }));
}

(テスト環境 S3Bucket: test-minto, key: test/hogehoge で中身は: testHogehoge)

単純に「s3Clientライブラリを使って実際のS3内容を取得して文字列で出力」の実装で、これをためしに実行してみます。

その前に (deploy (Ctrl+Shift+U) )ボタンを押下して、プログラムのデプロイを行い、そのあと「メニュー」にある (テスト) を押下し、以下の内容のようにテストを作成して (保存) ボタンを押下します。

image.png

これで「テスト実行が可能」となったので「メニュー」にある (コード) に戻って

  • Test (Ctrl+Shift+I)

このボタンを押下することで、Lambdaの実行が行われます。

導入した LLRT Lambda環境の実行検証.

次に実行可能になった AWS Lambda での LLRT 実行検証を行います。

  1. LLRT環境で Lambda を実行してみる
  2. 項2の比較として、同様の内容を node.js(V24)で動かしてみる
  3. 実行結果の比較(Lambda関数メモリ: 128MB)
  4. 実行結果の比較(Lambda関数メモリ: 512MB)
  5. llrt-no-sdk + AWS Signature(version4) + S3SDKを比較

1. LLRT環境で Lambda を実行してみる

(llrt-0.7.0-bata-aws-sdk-full)
image.png

  • Billed Duration: 161 ms
  • Init Duration: 71.39 ms
  • Duration: 88.93 ms
  • Memory Used: 32 MB

2. 項1の比較として、同様の内容を node.js(V24)で動かしてみる

次に同じ内容を NodeJS-24.x 環境で実施
(NodeJS-24.x)
image.png

  • Billed Duration: 2033 ms
  • Init Duration: 559.31 ms
  • Duration: 1473.46 ms
  • Memory Used: 102 MB

このような結果となりました。

3. 実行結果の比較(Lambda関数メモリ: 128MB)

まず「コールドスタート」では以下の結果となりました。

実行対象 llrt-0.7.0-bata-aws-sdk-full NodeJS-24.x LLRTとの差
合計 160.32 ms 2,032.77 ms 12.68倍
Init Duration 71.39 ms 559.31 ms 7.83倍
Duration 88.93 ms 1,473.46 ms 16.57倍
Memory Used 32 MB 102 MB 3.19倍

最新の環境で比較した結果が上の内容であり、この時点で「LLRTが12.6倍ぐらい高速」で、メモリ効率も「3倍ぐらい少なくて済む」結果となりました。

ちなみに「ウォームスタート」では以下の結果となりました。

実行対象 llrt-0.7.0-bata-aws-sdk-full NodeJS-24.x LLRTとの差
合計 47.28 ms 341.24 ms 7.22倍
Init Duration 24 ms 171 ms 7.13倍
Duration 23.28 ms 170.24 ms 7.31倍
Memory Used 33 MB 101 MB 3.06倍

ウォームスタートでも 分母が小さいですが、約7倍の速度差となり、正に LLRTの完全勝利となりました。

またそれぞれ「コールドスタートとウォームスタートの差」についても見ていきたいと思います。

(LLRT)実行対象 コールドスタート ウォームスタート ウォームとの差
合計 160.32 ms 47.28 ms 3.39倍
Init Duration 71.39 ms 24 ms 2.97倍
Duration 88.93 ms 23.28 ms 3.82倍
Memory Used 32 MB 33 MB 0.97倍
(Node.js)実行対象 コールドスタート ウォームスタート ウォームとの差
合計 2,032.77 ms 341.24 ms 5.96倍
Init Duration 559.31 ms 171 ms 3.27倍
Duration 1,473.46 ms 170.24 ms 8.66倍
Memory Used 102 MB 101 MB 1.01倍

比較すると「LLRTと比べて Node.js の方がコールドスタートとウォームスタートの格差が激しい事がわかります。

次に「Lambda のメモリを 512MB」に設定してみて同じ事を試してみたいと思います。

4. 実行結果の比較(Lambda関数メモリ: 512MB)

コールドスタート:

実行対象 llrt-0.7.0-bata-aws-sdk-full NodeJS-24.x LLRTとの差
合計 101.62 ms 750.8 ms 7.39倍
Init Duration 66.88 ms 404.15 ms 6.04倍
Duration 34.74 ms 346.65 ms 9.98倍
Memory Used 35 MB 112 MB 3.2倍

コールドスタート実行差が、128MBが 12.68倍 に比べて 512MBでは 7.39倍なので、NodeJSの方がメモリを増やす事で速度アップが図れるようです。

ウォームスタート:

実行対象 llrt-0.7.0-bata-aws-sdk-full NodeJS-24.x LLRTとの差
合計 33.56 ms 111.82 ms 3.33倍
Init Duration 17 ms 56 ms 3.29倍
Duration 16.56 ms 55.82 ms 3.37倍
Memory Used 35 MB 117 MB 3.34倍

ウォームスタートの場合も同様で 128MB で 7.22倍 これが 512MB では 3.33倍なので、こちらも同じぐらいNodeJSの方がメモリを増やす事で速度アップが図れるようです。

コールドスタートとウォームスタートの差:

(LLRT)実行対象 コールドスタート ウォームスタート ウォームとの差
合計 101.62 ms 33.56 ms 3.03倍
Init Duration 66.88 ms 17 ms 3.93倍
Duration 34.74 ms 16.56 ms 2.1倍
Memory Used 35 MB 35 MB 1倍
(Node.js)実行対象 コールドスタート ウォームスタート ウォームとの差
合計 750.8 ms 111.82 ms 6.71倍
Init Duration 404.15 ms 56 ms 7.21倍
Duration 346.65 ms 55.82 ms 6.21倍
Memory Used 112 MB 117 MB 0.96倍

コールドスタートとウォームスタート実行差に対するメモリ差:

条件 128MB 512MB
LLRT 3.39倍 3.03倍
Node 5.96倍 6.71倍

メモリを増やした場合の、それぞれのコールドスタートとウォームスタートの実行速度差は、大体比例している感じのようです。

5. llrt-no-sdk + AWS Signature(version4) + S3取得で比較

もっと LLRT を軽量化できないかと言う事で、比較的巨大なライブラリである AWS Sdk(V3) これを使わずに、以下を使って検証(128MBと512MBで)してみたいと思います。

  • llrt-lambda-arm64-no-sdk.zip
  • AWS Signature(version4) + S3取得

なお AWS Signature(version4) + S3取得 この辺の実装は、筆者が作った以下

ここでの2つのソースコード

  1. asv4.js
  2. s3client.js

これらを利用して、以下の実装で試してみる。

global.$loadLib = function(name) {
    return require(name);
}
const s3cl = require("./s3client.js");

exports.handler = async function(event, context) {
    const s3 = s3cl.create();
    const value = await s3.getObject({
        Bucket: "test-minto",
        Key: "test/hogehoge",
        resultType: "text"
    });
    const message = "hello LLRT(nosdk): " + value;
    return {
        statusCode: 200,
        body: message
    };
}

実行結果(コールドスタート)がこちら

  • 128MB でのコールド実行結果

    REPORT RequestId: 2b9af37b-1688-4b1d-922d-9c4fc6fd907c Duration: 62.64 ms Billed Duration: 105 ms Memory Size: 128 MB Max Memory Used: 25 MB Init Duration: 42.11 ms

    • Billed Duration: 105 ms
    • Init Duration: 42.11 ms
    • Duration: 62.64 ms
    • Memory Used: 25 MB
       
  • 512MB でのコールド実行結果

    REPORT RequestId: f8d68a4c-dc37-4c19-a579-4f3a11eb9ce9 Duration: 53.70 ms Billed Duration: 102 ms Memory Size: 512 MB Max Memory Used: 28 MB Init Duration: 48.01 ms

    • Billed Duration: 102 ms
    • Init Duration: 48.01 ms
    • Duration: 53.70 ms
    • Memory Used: 28 MB

たしかにfull SDK版と比べて高速で、メモリ使用量も減ってはいるけど、

  • (full SDK版) Billed Duration 160.32 ms
  • (full SDK版) Memory Used 32 MB
  • (no SDK版との差) 60 ms 速く 7MB メモリ軽減程度の差

この程度の差なので full SDK版を使っても良さそうな感じがします。

まとめ + 感想

【結果】

項目 実行条件 Init+Duration Memory Used
LLRT-NO-SDK(128MB) コールドスタート 105 ms 25 MB
LLRT(128MB) コールドスタート 160.32 ms 32 MB
NodeJS-24.x(512MB: warm) ウォームスタート 111.82 ms 117 MB

(no-sdk版は参考値って事で)

こんな感じで「LLRTの128MBのコールドスタート実行結果と、NodeJSの512MBのウォームスタート」と比べて、ほとんど実行時間が変らない」わけで、正に結果として「LLRTによってコールドスタート問題がなくなった」と言えます。

あと LLRT だと「AWS Lambda の最低環境=128MB メモリ設定」でも、メモリ使用量も少ない事から、普通に「Lambda最低限の 128MB環境でも十分利用可能」であると言えるので、LLRTでLambda関数URLを用いてウェブアプリを作成すれば「普通に超安価(ほぼ0円)なシステムが作る事」も可能で「GASの代わり」として「社内ツール的なアプリ」を作るなどが可能となります。

また筆者は以下の内容を個人的に「AWS Lambda + LLRT による 超軽量 Webアプリフレームワーク」を作成しています。

こちらは LLRT を使った AWS Lamda の関数URLを利用する場合の参考になるかと思うので、興味のある人は閲覧してみていただければと思います。

サンプルプログラム(jhtml = 動的html出力):
◆ test.js.html

<%
const hogehoge = "abc"
%>

${hogehoge}:${rand()}

コールドスタートminto関数URLの実行結果(FireFox):
image.png

ClowdWathのログ内容:

  • Billed Duration: 61 ms
  • Init Duration: 57.90 ms
  • Duration: 2.32 ms
  • Memory Size: 128 MB
  • Max Memory Used: 24 MB

minto LLRT(128MB) + 関数URL からのレスポンス: 391 ms これがコールドスタート結果なので、普通にストレスなく利用できるWebアプリ環境であると言えますね。

最後まで閲覧頂いてありがとうございました。

最後に宣伝です。
Supershipではプロダクト開発やサービス開発に関わる人を絶賛募集しております。
ご興味がある方は以下リンクよりご確認ください。
Supership 採用サイト
是非ともよろしくお願いします。

5
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?