一个 swappiness=0 导致的 OOM:记一次 Vite 构建失败排查
作者:shizhishi |
发布时间: |
分类:性能优化 |
声明:个人技术博客 • 非官方 • 仅供技术学习交流
在阿里云 2C2G 服务器上部署前端项目,`pnpm build` 执行到 `vite build` 阶段必失败: vite v6.4.3 building for production... ✓ 2592 modules transformed. sh: line 1: 1227505 Killed [ELIFECY...
> 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
### 三层防护
对于小内存服务器的前端构建,建议三层防护:
* Swap(最关键):4GB+ swap + swappiness=60,提供虚拟内存空间
* NODE_OPTIONS(辅助):--max-old-space-size=1024,让 V8 GC 更积极
* 构建前释放内存(可选):pm2 stop 暂停后端 worker,构建后 pm2 start
三者中 swap 是根本解,其余是辅助。
### 总结
| 现象 | 根因 | 解法 |
| --- | --- | --- |
| vite build exit 137 | OOM Killer 杀进程 | 增加 swap |
| 有 swap 仍 OOM | swappiness=0,内核不用 swap | sysctl vm.swappiness=60 |
| NODE_OPTIONS 无效 | 只限 V8 堆,不限进程 RSS | 依赖 swap 而非堆限制 |
**教训**:在云服务器上跑前端构建,先检查 `cat /proc/sys/vm/swappiness`。如果是 0,再大的 swap 也是摆设。