[简体中文] / English


AIO Ep21. 关于什么是 Linux swap, 以及你是否应该启用它

 

前言

由于近一年内存价格暴涨, 刚升级了平台的我买不起更多内存了. 我现在的 16G DDR5 内存在日常时勉强够用, 但在运行一些较重的任务时就会发生意料之外的 OOM kill.

我尝试开启 swap 来解决 OOM, 但 swap 配置过程中各种卡死/崩溃的问题; 上网搜索资料时, 我也发现人们对 swap 的理解千奇百怪, 有非常多误解和不够准确的资料. 故写此文记录.

什么是 swap

先引用两篇相对可信的资料: archlinux Wiki 和一篇名为 In defence of swap: common misconceptions 的博客.

简要概括一下: swap 是一个 Linux 下内存的临时后备存储空间, 它允许内核将较冷的匿名页(程序运行中动态分配的页)暂存到 swap, 从而将更有限和宝贵的物理内存让给当下需要内存的程序使用, 提高物理内存的利用率.

一个常见的说法是, swap 可以作为一种扩展物理内存的方法. 这并不是那么准确, 当你的 working set 大于物理内存时, 会发生 swap thrashing (内存需要不断的换入换出) 导致性能急剧下降. 极端情况下可能会导致系统整个卡死, 并不是 swap 推荐的用法.

swap 有个重要的参数叫 swappiness (/proc/sys/vm/swappiness). 其取值在 0-200 之间, 用于控制内核在遭遇内存压力时是优先丢弃文件缓存 (file pages), 还是优先将匿名页 (anonymous pages) 写入 swap 中. 具体如何设置这个参数取决于硬件配置. 例如, 在一个使用 SSD 当作系统盘的 Linux 上, 重读一个文件和从 swap 读取匿名页的开销类似, 所以可以取一个适中的 100 代表两者开销类似.

BTW, AskUbuntu 上的最高赞回答对 swappiness 解释就是过时和不准确的

我遇到的 swap 问题

然而事情并没有那么简单, 在开启 swap 后, 我的 Proxmox VE 遇到了不少问题, 也折腾了好一段时间.

Windows VM 内存问题

在 PVE 的 host 开启 swap 后, 出于一些不明的原因, 我的一台 Windows VM 开始出现随机的报错. 几乎所有程序, 从 LogonUI/taskmgr/explorer 到 Firefox/QQ/Navicat 都会在内存利用率较高时崩溃或弹出各种内存相关的报错弹窗, 整个系统也会常常莫名奇妙卡死, 而其他 Linux VM 和主机完全没有问题.

其中某次崩溃弹窗其中某次崩溃弹窗

我尝试了关闭 memory ballooning, 调大或调小 VM 内存, 更新 guest 驱动, 更新 host kernel, 开启 Windows 内部 page file 等各种操作, 均无果.

最终我通过编辑 /etc/pve/qemu-server/<VMID>.conf 配置文件, 在其中添加 QEMU 的 memlock 配置, 强制该 VM 的所有内存不进入 swap, 就解决了所有问题:

1
args: -overcommit mem-lock=on

PVE 主机卡死问题 (zram/zswap 问题)

一开始, 由于我的 SSD 使用了 ZFS, 而 ZFS 上的 swap 一直有一些问题, 所以我使用了 zRAM.

zRAM 作为一个块设备, 它会将其中的数据压缩后, 再存储在内存中. 一种常见的用法就是将 zRAM 本身作为 swap 使用, 内存中的冷数据就会被压缩, 原地放回内存中, 就不需要磁盘作为 swap. 在安卓上的”扩展内存”技术就常指 zRAM.

开始我使用了 100% 于内存大小 (即 16G) 的 zRAM 作为 swap. 初看没有什么问题, 系统运行正常且流畅.

然而, 一段时间后我发现, 这种情况下, 一旦有较大的内存压力或颠簸, 整个系统会极端卡顿.

卡顿到 PVE 默认安装中启用的 watchdog-mux.service 会因为连续 10s 来不及喂 watchdog, 导致整个系统直接重启. tmd 我本来是想解决 OOM kill 问题, 现在直接把整个系统干挂了. 这种情况下, 就算关闭 watchdog, 整个系统也会卡死完全无法恢复, ssh 可能等一个小时都无法连接.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 复现方法: 先开启 zram
modprobe zram
zramctl /dev/zram0 --algorithm zstd --size 16G
mkswap /dev/zram0
swapon -p 100 /dev/zram0

# 在主机观察内存压力 (PSI), 例如下面的 full 1.54 代表近 10s 内整个系统所有 process 有 1.54% 的时间在等待内存
watch -n0.5 cat /proc/pressure/memory
# some avg10=2.78 avg60=2.84 avg300=0.89 total=103627769
# full avg10=1.54 avg60=1.64 avg300=0.53 total=94869096

# 在不同的 VM 内制造一些内存负载
stress -c 10 --vm 2 --vm-bytes 4G --timeout 60s
# stress 制造的数据是规律的, 更好压缩, 但已经会导致很严重的卡顿, 如果用一些真实世界的负载, 情况会糟糕的多, PSI 可以到 80 以上 (80% 的时间在等待内存), 然后卡死

后来我抛弃 zram, 换用普通磁盘 swap 的时候, 发现 zswap 也有非常类似的问题. 在高负载下 cpu 的 system 占用非常非常高, 会导致 system 直接 hang 住死机.

我试过调节 swappiness, 使用 cgroup 保护 system.slice 的内存/禁止 system.slice 使用 swap 等方法, 并且我的 270K Plus 已经是目前最强的消费级平台 Intel CPU, 均无果, 会导致严重的卡死, 难以实际使用.

最后的解决方法就是买了两条 16G 的 Optane 作为 swap, 不使用 zram/zswap, 这时候同样的压力测试下, PSI 能保持在 30 左右.

swap 颠簸问题

在不开启 zram/zswap 的情况时, 只用 SSD 作为 swap, 至少在内存压力下, 不会直接死机了.

然而启用 swap 会改变系统 OOM killer 的行为, 如果有个程序使用了远超物理内存的内存量, kernel 仍要坚持 swap 直至 物理内存 + swap 都填满, 才会触发原本的 OOM kill. 这个行为在大部分时候可能是负面的, 会延长系统处于卡顿状态的时间.

由于 kernel 的 OOM killer 没有用于调整其何时进行 kill 的参数, 这个问题的解决方法只能是某些用户态的 OOM 检测和 kill 机制, 在达到一定内存压力 (PSI) 或内存/swap 使用量时提前介入, 主动 kill 掉 oom score 最高的进程.

已经有不少类似的程序, 例如: systemd-oomd, oomd, earlyoom, nohang

不过我在 HomeLab 上尝试了一下, 这些 oomd 的效果并不是最理想, 有些配置项不够灵活, 有些在高内存压力下本身也会卡顿, 无法及时介入. 我暂时没有使用.

swap 加密

最后还有个小问题是, 我的系统是全盘加密的, 然而, 内存中的敏感数据有可能被交换到 swap 中, 且 swap 关机时也不会清空.

这个不难解决, 只要给 swap 也加上一个全盘加密就行, 每次启动时使用 /dev/uramdon 创建随机密钥进行加密, 参考 archlinux wiki.

1
2
3
4
5
6
7
8
9
10
# /etc/crypttab
# <target name> <source device> <key file> <options>
swap1 /dev/disk/by-id/nvme-INTEL_MEMPEK1J016GAD_PHBT83XXXXXX016N /dev/urandom swap,cipher=aes-xts-plain64,size=512,sector-size=4096
swap2 /dev/disk/by-id/nvme-INTEL_MEMPEK1J016GAD_PHBT83XXXYYY016N /dev/urandom swap,cipher=aes-xts-plain64,size=512,sector-size=4096

# /etc/fstab
# <file system> <mount point> <type> <options> <dump> <pass>
/dev/mapper/swap1 none swap defaults 0 0
/dev/mapper/swap2 none swap defaults 0 0
# ... 其他挂载略

你是否应该启用 swap

讲了这么多我遇到的神秘问题, 最后的讨论是, 你是否应该使用 swap 呢?

网络上的解答众说纷纭, 但我赞成 archlinux wiki 的说法, 这其实是一个见仁见智的问题:

The biggest drawback of using swap when running out of memory is its lower performance, see section #Performance. Hence, enabling swap is a matter of personal preference: some prefer programs to be killed over enabling swap and others prefer enabling swap and slower system when the physical memory is exhausted.

如果你有十分确定的 workload, 你可以参考对应的官方文档, 例如 ElasticSearch 文档 建议彻底关闭 swap, Redis 文档 建议开启与内存等大的 swap 作为应急的同时指定 maxmemory, MongoDB 文档 建议关闭 swap 或尽可能降低 swappiness; 或者也可以直接进行压力测试来选择最合适的配置.

如果你是一个 workload 完全不确定的 PC 用户, 或者和我一样在 HomeLab 上部署十几个服务的开发者, 尽管有些激进人士认为你始终应该启用 swap 获得更好的内存利用率, 我的个人观点仍是, 如果你有充裕的 RAM (例如 32G 以上), 你完全可以不使用 swap: 虽然 swap 在近年来确实有改善, 但并非完全”免费”的内存优化, swap 仍会导致访问冷数据时系统响应迟缓, 造成用户不喜欢的卡顿. 同时其会改变 OOM killer 行为, 可能导致系统完全 freeze. swap 还因为不明原因导致了我的 Windows VM 表现十分异常.

此外 swap 还会大量写磁盘, 之前我也遇到过 swap 导致磁盘掉盘, 整个系统直接此时会卡死. 如果你没有质量较好的高速 SSD, 你几乎不应该使用磁盘 swap.

不过在这个特殊时期, 如果确实买不起大量内存, 或者是 workload 日常的 RAM 占用就较高, 可以考虑开启 swap, 确实缓解很多 OOM kill 以及内存分配失败的问题. 我的经验是 swap 的设置不要整太多花活: 最朴素的 swap 分区或 ext4 上的 swap 文件一般能工作相对良好, 反而是开启 zram/zswap 可能导致系统 freeze. swap on ZFS 也是已知会导致随机 deadlock 的常见情况, 应该避免.

在开启 swap 后, 几台 VM 的大量内存都能进入 swap, 让我能 16G RAM 跑起原来需要 32G 的负载在开启 swap 后, 几台 VM 的大量内存都能进入 swap, 让我能 16G RAM 跑起原来需要 32G 的负载

本文采用 CC BY-NC-SA 4.0 许可协议发布.

作者: lyc8503, 文章链接: https://blog.lyc8503.net/post/21-swap-setup/
如果本文给你带来了帮助或让你觉得有趣, 可以考虑赞助我¬_¬