缩短 Lambda 冷启动时间:迁移到适用于 JavaScript 的 亚马逊云科技 开发工具包

适用于 JavaScript 的 亚马逊云科技 开发工具包 (JS SDK) v3 是对 v2 的重写,它 采用模块化架构 和经常请求的功能,例如一 流的 TypeScript 支持 新的 中间件堆栈。

当我们的客户将其应用程序从 JS SDK v2 迁移到 v3 时,他们一直在要求使用可靠的基准测试来评估 SDK 在常见用例中的性能。为了响应这些请求,JS SDK 团队对 亚马逊云科技 Lambda 的冷启动时间进行了基准测试,因为这是客户的常见用例,也是很好的参考标准。我们的基准测试显示,与大多数常见用例中的 v2 相比,v3 缩短了冷启动时间。尽管这些基准测试侧重于 Lambda 冷启动时间,但无论您使用哪种计算服务,迁移到 v3 通常都能提高应用程序性能。

自发布以来,Lambda 已对其 Node.js 18 运行时进行了多项性能优化,本博客文章中的数据基于最新的运行时版本。如果您对 Lambda 冷启动时间很敏感,我们建议将您的 Lambda 函数捆绑在一起,该函数使用带有命令对象的准系统 SDK 客户端,包括 JS SDK v3。您应该为生产应用程序运行自己的基准测试,并使用这篇博客文章作为参考。您也可以参考之前关于 优化 Node.js 依赖关系 的博客文章, 该文章深入探讨了捆绑和缩小 Lambda 函数。

什么是基准测试?

我们使用 JS SDK v3 和 v2 对示例应用程序的 Lambda 冷启动时间进行了基准测试。很大一部分的 JS SDK 客户从 Lambda 发送请求,许多人对冷启动时间很敏感。

以下 Lambda 函数示例代码从 JS SDK v3 导入 STS 客户端,在函数外部创建客户端实例并返回 getCallerIdentity 的 响应。

import { STS } from "@aws-sdk/client-sts";

const client = new STS();
export const handler = async () => client.getCallerIdentity();

v2 中的代码将从 “aws-sdk” 包中导入,并在 API 调用时调用 promise (),如下所示:

import AWS from "aws-sdk";

const client = new AWS.STS();
export const handler = async () => client.getCallerIdentity().promise();

基准测试是针对三个常见用例获得的:

  • 按原样使用 Lambda 提供的软件开发工具包
  • 用户上传的 node_modules 中的 SDK 按原样
  • 使用 esbuild 捆绑销售

我们使用了 测量冷启动 的自定义分支 ,它可以同时获取多个相关 Lambda 函数的冷启动指标,并以可读的表格格式返回特定的指标和统计数据。它测量了 Lambda 在 CloudWatch 日志中记录的初始持续时间。每个基准测试都运行了 100 次调用。

这篇博客文章中分享的基准测试适用于以 ECMAScript 模块格式(又名 ESM)编写的 Lambda 函数。我们还收集了以 CommonJS 模块格式编写的函数的基准测试,它们是相似的。我们使用 ESM,因为它是打包 JavaScript 代码以供重复使用的官方标准格式。

按原样使用 Lambda 提供的软件开发工具包

Lambda 在其设置中提供了 SDK 版本,以方便开发人员构建更简单的函数或使用 Lambda 控制台进行开发。这允许客户跳过在 node_modu les 文件夹中提供 SDK 工件。虽然这是最方便的用例,但它并不是性能最高的用例。

在此基准测试设置中,应用程序仅使用函数源代码上传到 Lambda。它有两个文件系统节点:

  • package.json 包含项目清单
  • index.mjs 包含函数源代码

JS SDK v3 是在 Lambda 提供的带有 Lambda Node.js 18 的软件开发工具包中引入的。这就是为什么 v2 基准测试在 nodejs16.x 上运行,而 v3 基准测试在 nodejs18.x 上运行。

使用 Lambda 的 Node Runtime 14 和 16 时,从 NODE_PATH 导入的 ESM 不可用。为了测试 Lambda 在 Node.js 16 中提供的 JS SDK v2,我们在我们的测试设置中创建了一个指向 /v ar/runtime/ node_modules 的符号链接。有关详细信息,请查看 GitHub 讨论 aws/aws-sdk-js/ #4432

此设置的基准测试表明,使用 Lambda 提供的 SDK 时,使用 v3 的 Lambda 函数的冷启动时间比使用 v2 的函数 少了 100 毫秒以上

╔════════════════════════════════════════════╤════════════════════╤════════╤════════╤════════╗
║                                            │ metric             │ p50    │ p90    │ stdDev ║
╟────────────────────────────────────────────┼────────────────────┼────────┼────────┼────────╢
║ node 16.20.1: Code (esm) with sts v2.1374.0│ init_duration (ms) │ 509.74 │ 529.76 │ 14.57  ║
║ (provided) (size: 687.00 B)                │                    │        │        │        ║
╟────────────────────────────────────────────┼────────────────────┼────────┼────────┼────────╢
║ node 18.16.1: Code (esm) with sts v3.188.0 │ init_duration (ms) │ 401.48 │ 438.57 │ 32.39  ║
║ (provided) (size: 566.00 B)                │                    │        │        │        ║
╚════════════════════════════════════════════╧════════════════════╧════════╧════════╧════════╝

版本 v2.1374.0 和 v3.188.0 是 Lambda 在撰写本文时提供的 SDK 版本。Lambda 提供的软件开发工具包版本会定期更新为最新的开发工具包版本。

用户上传的 node_modules 中的 SDK 按原样

与 Lambda 提供的 SDK 设置相比,我们针对用户上传的 node_modules 的基准测试设置有以下变化:

  • node_modules 目录中添加了 SDK 工件。
  • 这两个 Lambda 函数都在 nodejs18.x 上进行了基准测试
  • /var/runtime/node_modules 的符号链接已删除

此设置的基准测试表明,与使用 v2 的函数相比,使用 v3 的 Lambda 函数的冷启动时间 减少了大 约 140 毫秒。Lambda 函数的大小也减少了大 约 10 .8 MB。

╔════════════════════════════════════════════╤════════════════════╤════════╤════════╤════════╗
║                                            │ metric             │ p50    │ p90    │ stdDev ║
╟────────────────────────────────────────────┼────────────────────┼────────┼────────┼────────╢
║ node 18.16.1: Code (esm) with sts v2.1438.0│ init_duration (ms) │ 625.71 │ 659.2  │ 27.11  ║
║ (size: 12.63 MB)                           │                    │        │        │        ║
╟────────────────────────────────────────────┼────────────────────┼────────┼────────┼────────╢
║ node 18.16.1: Code (esm) with sts v3.391.0 │ init_duration (ms) │ 485.54 │ 519.42 │ 24.11  ║
║ (size: 1.81 MB)                            │                    │        │        │        ║
╚════════════════════════════════════════════╧════════════════════╧════════╧════════╧════════╝

对于这种设置,预计所有服务的改进将持续超过100ms。Lambda 函数的大小差异将取决于服务客户端,但在 v3 中会更小,与 v2 相比,v3 是模块化的。

使用 esbuild 捆绑销售

捆绑器是一种将多个模块或文件组合成单个文件的工具,通常用于优化 Web 应用程序的交付和执行。捆绑器通常用于前端开发,特别是基于 JavaScript 的项目,但它们也可以用于后端开发。

我们与 esbuild 捆绑在一起的基准测试设置使用 v2 和 v3 中的命名导入,并使用以下命令捆绑应用程序:

esbuild source.mjs --bundle --platform=node --format=esm --main-fields=module,main

要创建 v2 的 ESM 捆绑包,我们需要使用以下 esbuild 选项为需要提供 polyfill:

--banner:js="import { createRequire } from 'module';const require = createRequire(import.meta.url);"

有关为什么需要这样做的详细信息,请参阅 evanw/esbuild/issues/ 1921 #issuecomment -1403107887。

此设置的基准测试表明,与 esbuild 捆绑在一起时,使用 v3 的 Lambda 函数的冷启动时间比使用 v2 的函数 少了 400 毫秒以上。Lambda 函数的大小也有 2 MB 的差异。

╔════════════════════════════════════════════╤════════════════════╤════════╤════════╤════════╗
║                                            │ metric             │ p50    │ p90    │ stdDev ║
╟────────────────────────────────────────────┼────────────────────┼────────┼────────┼────────╢
║ node 18.16.1: Code (esm, bundled:esbuild)  │ init_duration (ms) │ 624.16 │ 657.88 │ 23.06  ║
║ with sts v2.1438.0 (size: 2.29 MB)         │                    │        │        │        ║
╟────────────────────────────────────────────┼────────────────────┼────────┼────────┼────────╢
║ node 18.16.1: Code (esm, bundled:esbuild)  │ init_duration (ms) │ 213.16 │ 229.9  │ 14.73  ║
║ with sts v3.391.0 (size: 91.53 KB)         │                    │        │        │        ║
╚════════════════════════════════════════════╧════════════════════╧════════╧════════╧════════╝

为了减小 v2 中的函数大小,一些客户使用了 STS 客户端的深度导入,如下所示:

import STS from "aws-sdk/clients/sts.js";

当我们在深度导入 STS 客户端时运行基准测试时,v3 中的冷启动时间仍然缩短了大约 10 毫秒。尽管具有深度导入功能的捆绑v2函数比具有全局导入功能的版本小,但捆绑的v3功能仍几乎是其大小的一半。

╔════════════════════════════════════════════╤════════════════════╤════════╤════════╤════════╗
║                                            │ metric             │ p50    │ p90    │ stdDev ║
╟────────────────────────────────────────────┼────────────────────┼────────┼────────┼────────╢
║ node 18.16.1: Code (esm, bundled:esbuild)  │ init_duration (ms) │ 225.7  │ 246.41 │ 17.51  ║
║ with sts v2.1438.0 (size: 173.31 KB)       │                    │        │        │        ║
╟────────────────────────────────────────────┼────────────────────┼────────┼────────┼────────╢
║ node 18.16.1: Code (esm, bundled:esbuild)  │ init_duration (ms) │ 213.16 │ 229.9  │ 14.73  ║
║ with sts v3.391.0 (size: 91.53 KB)         │                    │        │        │        ║
╚════════════════════════════════════════════╧════════════════════╧════════╧════════╧════════╝

通过使用带有命令对象的准系统客户端,也可以进一步减小 v3 中的包大小。在幕后,这将导入一个轻量级客户端,并且仅导入您的应用程序需要调用的操作。此结构甚至可以用于 Lambda 函数之外的案例。

import { STSClient, GetCallerIdentityCommand } from "@aws-sdk/client-sts";

const client = new STSClient();
export const handler = async () => client.send(new GetCallerIdentityCommand({}));

在 v3 中使用 STS 客户端的命令导入运行基准测试时,捆绑的应用程序大小减少了约 2KB。冷启动时间缩短了约 3 毫秒。

╔════════════════════════════════════════════╤════════════════════╤════════╤════════╤════════╗
║                                            │ metric             │ p50    │ p90    │ stdDev ║
╟────────────────────────────────────────────┼────────────────────┼────────┼────────┼────────╢
║ node 18.16.1: Code (esm, bundled:esbuild,  │ init_duration (ms) │ 209.03 │ 223.93 │ 7.68   ║
║ import:command) with sts v3.391.0 (size:   │                    │        │        │        ║
║ 89.81 KB)                                  │                    │        │        │        ║
╚════════════════════════════════════════════╧════════════════════╧════════╧════════╧════════╝

我们可以从这些基准测试中学到什么?

就常见用例中的 Lambda 冷启动时间而言,JS SDK v3 比 v2 更快。

您实现应用程序的方式会影响性能。如果您在 Lambda 上使用 JS SDK 并且对冷启动时间很敏感,请使用带有准系统客户端和命令对象的 JS SDK v3,并在部署到 Lambda 之前捆绑您的应用程序。此设置的冷启动时间较短,因为 Node.js 只需要读取一个文件,其中包含应用程序的完整源代码。无需花费时间进行模块解析或读取多个文件。当您的应用程序使用 v3 时,包大小会更小,因为 v3 是模块化的。

JS 开发工具包团队建议使用 亚马逊云科技 云开发套件 (CDK) 来管理您 的 Lambda 函数。你可以使用 CDK NodejsFunction 构造 来捆绑应用程序源代码。只要记得在 bun dling.externalModules 配置中传递一个空数组,这样它就可以捆绑开发工具包了。有关详细信息,请查看 aws/aws-cdk/ #25492

反馈

要开始使用 JS SDK v3,请访问我们的 入门 页面。我们非常重视您的反馈,因此,如果您有任何问题、意见、疑虑或想法,请 在 GitHub 上开启 讨论

Trivikram Kamat

Trivikram Kamat

Trivikram 是 Node.js 和浏览器中适用于 JavaScript 的 亚马逊云科技 开发工具包的维护者。Trivikram 还是 Node.js Core 的合作者,过去曾通过 QUIC 实现为 HTTP、HTTP/2 和 HTTP/3 做出了贡献。他编写 JavaScript 已有十多年了。你可以在推特 @trivikram 和 GitHub 上找到他 @trivikr