老胡茶室
老胡茶室

AI 项目性能优化:在 AWS EC2 上用 Bun 替代 Node Runtime

冯宇

TL;DR

最近我们把一台线上 EC2 实例上的使用 Node.js 运行时的 Next.js AI 项目迁移到了 Bun 运行时,效果非常好,并且资源占用也下降了。

背景

AWS 对新用户有一系列的优惠活动,包括免费使用 EC2 实例。我们利用这些优惠活动,作为出海的尝试,部署了一个 Next.js AI 项目。

免费使用的实例类型是 t2.micro,具有 1 个 vCPU 和 1 GiB 的内存,资源非常有限。按照我们的实践,知识库的 indexing 是在后台进程异步处理(无废话 RAG:实践篇),这导致本就有限的资源有点捉襟见肘,甚至时不时得使用“神之一指”。

于是我们开始着手解决这个问题,在不增加额外成本的前提下,尽可能尝试各种方案减少资源占用。

解决过程与踩坑

我们仔细观察过资源占用情况,next.js 本身的资源占用相对稳定,大约占据了一半不到 500 MB 的内存。indexing 脚本使用 ts 编写,运行的时候需要使用 tsx 运行,这玩意资源占用就非常高了,而且启动时间还很长,尤其是在 AWS 免费实例这种本身资源就非常有限的情况下,光启动起来就得等上十几秒。

于是我第一个想到的就是将 ts 编译成 js 运行,这样就可以直接用 node.js 运行了,无需使用 tsx 。我们尝试使用 tsc 编译,结果由于项目使用的是 esm,tsc 编译出来的 js 无法直接使用 nodejs 运行,而且 tsc 无法仅仅编译指定文件以及相关调用链,必须在项目根目录下使用 pnpm tsc 命令编译所有的 ts / tsx 文件,这样不但速度特别慢,而且在服务器上还遇到了内存不足的问题。

后来我们尝试改用 tsup 代替 tsc 来构建,由于 tsup 支持设置入口文件,以及支持 esm 和 cjs 编译目标,相关调用链也会自动 bundle,总算是可以正常运行了。在服务器上使用 nodejs 运行编译后的 js 文件,启动速度有所改善,而且由于不需要加载额外的 typescript loader,内存占用也下降了一些。

Bun 运行时

虽然减少了部分资源占用,也提升了 indexing 过程的启动速度,但是由于这个过程使用了很多 langchain 的依赖库,导致内存占用也不小,indexing 大型知识库依旧会导致比较明显的性能下降。所以我们决定继续寻找方案。

此时 Bun 就成为了一个备选项。Bun 是一个新的 JavaScript 运行时,声称在性能和资源占用上优于 Node.js。我们决定尝试将 indexing 过程迁移到 Bun 上运行。

作为一支积极向上的技术团队,我们很久以前就注意到 Bun,曾经也小规模尝试过,但由于当时版本有很多兼容性问题,还是暂时放弃了 Bun,继续使用 nodejs。

但是现在再回看 Bun 的时候,发现它已经大幅提升了 nodejs 的兼容性,它实现了绝大部分 nodejs 的 API (关于 bun 的兼容性,可以参考官方文档: Node.js compatibility ),因此理论上大部分 nodejs 项目应该可以无缝迁移到 bun 运行时,更重要的是, bun 官方就宣称可以支持 Next.js: Build an app with Next.js and Bun,这让我们兴奋不已。

而且 bun 号称启动速度更快,内存占用更低,于是我们决定试一试。

最终效果

最终尝试过后效果喜人,我们在不修改任何配置文件,也不改动任何开发流程的情况下,bun 可以直接运行本地使用 nodejs 编译后的 Next.js Standalone 应用,并且的确如官方宣称的那样,启动速度更快,而且内存占用的确相比 Node.js 有所下降。

我将之前使用 tsup 构建的应用在 bun 上运行,效果非常好,内存占用进一步降低,启动速度也有明显提升,于是我将 tsup 也从依赖中去掉了,直接使用 bun build 编译 indexing 程序脚本,最终成功在 bun 上运行。

现在及时在 t2.micro 这种小机上同时运行 indexing 和 Next.js 应用,资源占用也不会太高,Web 服务也没有出现明显的性能下降问题。

不过需要注意的是,bun 似乎对于 wasm 的依赖 bundle 支持不太好。由于项目用到了 mupdf 的 wasm 来增强对于 PDF 的处理(详见无废话 RAG:理论篇无废话 LangChain.js),因此修改脚本单独将 wasm 一并拷贝到输出目录中,否则就会出现找不到这个文件的错误。

bun build --target=bun --outdir=dist --minify scripts/task.ts
cp -fv ./node_modules/.pnpm/mupdf@*/node_modules/mupdf/dist/mupdf-wasm.wasm dist/