我和我的团队在使用jest 29.x和node 18.x时遇到了内存泄漏问题。测试套件会运行,出现内存泄漏,并在内存使用量达到2GB时导致node崩溃。增加分配给node的RAM内存不是一个选项,因为测试套件的测试数量会增加,最终导致失败,而且2GB已经太多了。
实际解决方案
这个答案并不是直接解决问题的方法,但它可以将问题潜在地变得无害,并且我们花费了很多时间才找到它。
你需要做的第一件事是在你的jest.config.js
文件中设置以下配置:
module.exports = {
...
workerIdleMemoryLimit: '512MB',
...
}
我选择了512MB,但是你可以根据需要选择一个留有一些空间的值,比如
"test_suite_ram_startup + 10 * memory_leak_increase"
。当内存使用量达到这个值时,jest会重新启动工作进程并清除内存,对测试运行所需的总时间影响微乎其微。
仅凭这一点就足以防止测试套件崩溃,问题基本上已经解决了。
降低RAM总使用量
现在,与不让Node崩溃一样重要的是使用合理的RAM量来运行测试。从现在开始,每个考虑因素都会减少测试套件完整运行时所使用的RAM总量。
1. 使用以下配置运行jest:--expose-gc --no-compilation-cache:
node --expose-gc --no-compilation-cache ./node_modules/jest-cli/bin/jest.js --logHeapUsage
"logHeapUsage"会记录内存使用情况,以便您可以检查改进情况。
2. 如果您正在使用TypeScript,请使用SWC作为转译器将其转译为JS,而不是使用tsjest。这样做速度更快,节省了大量内存,因为转译过程是我们最耗费内存的过程。
3. 在测试套件的所有测试完成后,强制调用垃圾回收器。
afterAll(() => {
global.gc && global.gc()
})
原则上来说,这不应该是必要的,但不知何故 jest 并没有像它应该做的那样进行得那么多。使用此方法,我们减少了每个测试套件执行时所需的总内存量。
4. 找到带有 import * as ... 的库
一些库(如 aws-sdk v2)会尝试导入所有内容,即使你只使用了其中一小部分代码。如果你查看库中导入发生的位置,你会发现所有内容都被导入到一个文件中,然后你从该文件中导入,而所有无用的东西都会存在于内存中。要解决这个问题,只需追踪你想要的资源确切位置,并直接从那里导入。在 aws-sdk 的情况下,你还可以使用 aws-sdk v3 来解决这个问题。
5. 使用小型库。
有时候我们不能只是替换库,但是将 class-validator 替换为像 zod 这样的小型库会有所帮助。
6. 更新你的库。
在我们的案例中,将 Prisma ORM 从 4.10 更新到 5.0 取得了巨大的效果。新版 Prisma 使我们的测试套件运行速度提高了 2.6 倍,并且占用的内存更少。
最终结果
只是为了让您对一些数字有所了解。我们的测试套件在启动时需要1.1GB的RAM,每个测试套件都会产生内存泄漏,增加总内存量100MB。每个测试套件运行需要15-10秒。执行10个测试后,节点将崩溃。
- 实施1可以轻微减少总RAM量。
- 实施4将启动内存使用降至400MB。
- 实施3将内存泄漏增加降至每个测试套件80MB。
- 实施2将启动内存降至30MB,并将内存泄漏降至每个测试套件60MB。
- 我没有测量或记住5。
- 实施6将内存泄漏增加降至每个测试套件14MB。
最终,每个测试套件运行时间为2-3秒。
我们从一个崩溃的测试运行开始,每个测试套件的RAM使用量从1.1GB逐渐增加100MB,到最后变成了一个从未崩溃的版本(这要归功于我在jest配置中提出的解决方案),每个测试套件的RAM使用量从30MB开始逐渐增加14MB。如果达到了512MB,它会立即降低到30MB,并再次缓慢增加。而且一切都变得更快了。
我真心希望这对那些遇到类似问题的人有所帮助。
xlsx
包;你是在为测试加载文件吗?文件引用是否跨测试被保留? - Babak Naffas