Linux 内存不够老 OOM?我对比 systemd-oomd 和 earlyoom 后,4G VPS 终于不杀 PostgreSQL 了

🔑 关键词:Linux OOM,systemd-oomd,earlyoom,内存不足,VPS

📖 摘要:一台 2 核 4G Ubuntu 22.04 VPS 上 PostgreSQL 被 systemd-oomd 误杀的排查记录:用 dmesg、/proc/pressure/memory、oomctl 定位,对比 earlyoom 的触发参数,给出禁用/替换/保护进程的完整命令和 30 天观察结果。

先说我那台机器的背景

图片

不是云厂商广告,就是一台 2 核 4G 的轻量云,Ubuntu 22.04.4 LTS,内核 5.15.0-105-generic。跑的东西也不复杂:PostgreSQL 15、Redis 7.0、一个 Go 写的 API,外加 Caddy。PostgreSQL 的 shared_buffers 给到 1GB,work_mem 8MB,Redis maxmemory 512MB,按说 4G 内存不应该天天出事。但 2 月有段时间,每天早上 9 点左右 API 就 502,上去一看 PostgreSQL 进程没了,dmesg -T | grep -i oom 里全是 Out of memory: Killed process ... postgres。最离谱的是,free -m 当时显示 available 还有 300 多 MB,不是那种彻底榨干。

我一开始以为是 PG 内存泄漏,查了 pg_stat_activitypg_stat_database,也没看出谁在跑大查询。后来才注意到 Ubuntu 22.04 默认开了 systemd-oomd,日志里有一句 Killed /system.slice/postgresql.service due to memory pressure。这玩意不看 available 具体多少,它看 PSI,也就是 /proc/pressure/memory。PG 做 checkpoint 的时候,脏页回写会让 IO 和内存压力短时间冲上去,PSI 的 avg10 一旦超过阈值,systemd-oomd 就可能直接动手。我的观点可能有点反直觉:在小内存单机上,OOM 管理器“聪明”不一定好事,可预测比智能更重要。

怎么确认是不是 systemd-oomd 干的

图片

先别急着改配置,先留证据。我用的命令就这几个,Ubuntu/Debian 基本通用:

# 看内核 OOM 记录,带时间
sudo dmesg -T | grep -i -E 'oom|killed process'



![图片](http://img2.baidu.com/it/u=3765216059,4194392685&fm=253&fmt=auto&app=138&f=JPEG?w=1026&h=472)


# 看 systemd-oomd 自己的日志
sudo journalctl -u systemd-oomd --since '24 hours ago' --no-pager

# 看内存压力,不是看 free 就完事
cat /proc/pressure/memory
# 当时我截到的是 some avg10=12.34 avg60=5.67 avg300=1.23 total=...
# 另一个关键文件
cat /proc/pressure/io

oomctl 也很有用,它会列出 monitored cgroups、swap 使用和最后一次杀进程原因。我的日志里反复出现 Memory pressure for /system.slice/postgresql.service is 62.5% for more than 20s,而 DefaultMemoryPressureDurationSec 默认就是 20s。这里有个坑:很多教程只让你 systemctl disable systemd-oomd,但如果你同时装了 earlyoom,两个 daemon 会抢着杀进程,日志会混在一起,根本分不清谁干的。所以要么留一个,要么把另一个 mask 掉。

图片

我最后的处理不是“加内存”这种正确但没用的建议,而是:禁用 systemd-oomd,换 earlyoom,并且明确保护 PostgreSQL、Redis、sshd。earlyoom 的触发逻辑简单到有点土:它每秒读 /proc/meminfo,当 MemAvailable 低于 -m 的第一个百分比,或者 SwapFree 低于 -s 的第二个百分比时,就按 oom_score 和正则名单选一个杀。你可以骂它粗暴,但在 4G 机器上,粗暴比误判强。配置我放在 /etc/default/earlyoom

EARLYOOM_ARGS="-m 8,4 -s 10,5 -r 60 --avoid '^(postgres|redis|sshd|caddy)$' --prefer '^(node|java|python3)$'"

-m 8,4 是内存可用低于 8% 警告、低于 4% 才杀;-s 10,5 是交换可用低于 10% 警告、低于 5% 才杀;-r 60 是每 60 秒报一次状态。--avoid 是尽量保护重要服务,注意是“尽量”,不是绝对免死;--prefer 是优先杀 node、java、python3 这类构建/脚本进程。装和切换的命令如下:

图片

sudo systemctl disable --now systemd-oomd
sudo systemctl mask systemd-oomd
sudo apt update && sudo apt install -y earlyoom
sudo systemctl edit earlyoom
# 或者直接改 /etc/default/earlyoom
sudo systemctl restart earlyoom
sudo systemctl status earlyoom --no-pager
journalctl -u earlyoom -f

效果和参数对比

图片

换完后 30 天,PostgreSQL 没再被系统杀过。唯一一次是 npm run build 把内存打到 156MB/3936MB,也就是 3.96%,earlyoom 才动手,日志大概是:mem avail: 156 of 3936 MiB (3.96%), swap free: 1024 of 2048 MiB (50.00%),然后 sending SIGTERM to process 5678 node uid 1000。这比 systemd-oomd 在 300 多 MB available 时杀 PG 更容易接受,至少我知道它为什么死、死在哪个阈值。

项目 systemd-oomd earlyoom
触发依据 PSI 内存/IO 压力 + cgroup /proc/meminfo 的 MemAvailable/SwapFree
默认阈值 20s 压力,具体看配置 内存 10%,5%;交换 10%,5%
可预测性 差,受 IO 影响 好,看数字
保护服务 通过 cgroup 配置,较绕 --avoid 正则,直接
适合 桌面/容器 cgroup 小内存 VPS/单机

另外我也调了下 sysctl,不是玄学,就是给内核留余地:vm.swappiness=10vm.vfs_cache_pressure=50vm.min_free_kbytes=65536。4G 机器给 64MB 最小空闲内存,别设太大,否则内核自己会紧张。vm.overcommit_memory=0 保持默认,Redis 如果设成 2 可能 fork 失败。最后一句不一定对所有人:如果你机器 16G 以上、跑 K8s,systemd-oomd 的 cgroup 粒度反而更合适;但 2C4G 这种单机,earlyoom 的参数你能一眼看懂,出事也能复盘。

🏷️ 标签: