一个 swappiness=0 导致的 OOM:记一次 Vite 构建失败排查

时之世 发布于 2 小时前 5 次阅读 预计阅读时间: 4 分钟 最后更新于 2 小时前 963 字 无~


2G 内存云服务器上 vite build 频繁被杀,exit code 137 。排查发现不是内存不够,而是内核根本不用 swap 。

现象

在阿里云 2C2G 服务器上部署前端项目,pnpm build 执行到 vite build 阶段必失败:

vite v6.4.3 building for production...
✓ 2592 modules transformed.
sh: line 1: 1227505 Killed
[ELIFECYCLE] Command failed with exit code 137.

exit code 137 = 128 + 9 (SIGKILL)——进程被系统强制杀死。

部署日志显示构建前内存使用率已达 80%+:

host metrics | cpu: 77.3% | ram usage: 80.2%

第一反应:内存不足

服务器总内存 1870MB,部署时:

  • PM2 cluster 2 个 worker:~300MB
  • 系统进程:~500MB
  • pnpm install 缓存:~200MB
  • 可用物理内存:~870MB

Vite build 峰值需要 ~1.5GB,物理内存确实不够。

尝试 1:限制 Node.js 堆内存

export NODE_OPTIONS="--max-old-space-size=1024"
pnpm build

结果:仍然 OOM 。

原因--max-old-space-size 只限制 V8 堆 (JS 对象),不限制进程总内存 (RSS) 。 Node.js 总内存 = V8 堆 + C++ 层 + esbuild/rollup 原生缓冲区 + 线程栈。 OOM Killer 看的是 RSS,不是 V8 堆。

尝试 2:创建 swap

dd if=/dev/zero of=/swapfile2 bs=1M count=4096
chmod 600 /swapfile2
mkswap /swapfile2
swapon /swapfile2

验证:swapon --show 显示 5GB swap,free -h 显示 Swap 总量 5.0Gi 。

结果:仍然 OOM!

这就诡异了。 5GB swap + 1.87GB 物理内存 = 6.87GB 虚拟内存,vite build 峰值 1.5GB 远够用,为什么还被杀?

真凶:swappiness=0

检查内核参数:

cat /proc/sys/vm/swappiness
# 0

swappiness=0——这就是根因。

什么是 swappiness

vm.swappiness 是 Linux 内核参数,控制内核使用 swap 的积极程度:

行为
0几乎不使用 swap,优先回收文件缓存页,物理内存耗尽直接 OOM
1-60平衡使用 swap
60默认值,适度使用 swap
100积极使用 swap

为什么云服务器默认 swappiness=0

阿里云、腾讯云等云服务商默认将 swappiness 设为 0,理由是:

  • swap 在磁盘上,速度比物理内存慢 100-1000 倍
  • 对性能敏感的生产服务 (数据库/API),使用 swap 会导致严重延迟
  • 宁可 OOM 重启进程,也不要拖慢整个系统

这个策略对常驻服务 (Nginx/MySQL/API) 合理,但对短时高内存任务(如 vite build) 是灾难——build 只需要 1-2 分钟峰值内存,完全可以用 swap 换过去,但 swappiness=0 让内核宁愿杀进程也不用 swap 。

修复

# 立即生效
sysctl vm.swappiness=60

# 持久化 (重启后仍生效)
echo 'vm.swappiness=60' >> /etc/sysctl.conf

修复后 vite build 正常完成,swap 使用约 800MB,构建时间比纯物理内存慢约 30%,但不再被杀。

完整排查路径

vite build exit 137 (OOM)
  │
  ├─ 物理内存不足?→ 是 (2G 服务器,build 峰值 1.5G)
  │
  ├─ 限制 V8 堆 → 无效 (OOM Killer 看 RSS 不看 V8 堆)
  │
  ├─ 创建 swap → 无效 (swappiness=0,内核不用 swap)
  │
  └─ 设置 swappiness=60 → ✅ 成功

部署脚本优化

deploy.sh 中添加 swap 依赖说明和堆内存限制:

# 前端构建
export NODE_OPTIONS="--max-old-space-size=1024"
pnpm build
unset NODE_OPTIONS

并在文档中标注前提条件:

前提swappiness=60(否则 swap 不生效),见 /etc/sysctl.conf

三层防护

对于小内存服务器的前端构建,建议三层防护:

  1. Swap(最关键):4GB+ swap + swappiness=60,提供虚拟内存空间
  2. NODE_OPTIONS(辅助)--max-old-space-size=1024,让 V8 GC 更积极
  3. 构建前释放内存 (可选)pm2 stop 暂停后端 worker,构建后 pm2 start

三者中 swap 是根本解,其余是辅助。

总结

现象根因解法
vite build exit 137OOM Killer 杀进程增加 swap
有 swap 仍 OOMswappiness=0,内核不用 swapsysctl vm.swappiness=60
NODE_OPTIONS 无效只限 V8 堆,不限进程 RSS依赖 swap 而非堆限制

教训:在云服务器上跑前端构建,先检查 cat /proc/sys/vm/swappiness 。如果是 0,再大的 swap 也是摆设。