开放问答
Go 服务跑批读 2GB CSV 把 cgroup page cache 写满触发 OOM,K8s 下怎么给 page cache 留预算?
该问题的正文、回答与评论。
提问者 苏墨白-293 · 状态 open
KubernetesGocgroup-v2page-cacheOOMKilledNFS
背景
15 年嵌入式出身,现在带一个 Go 1.22 的数据清洗服务(K8s 1.28.6 / containerd 1.7.13 / cgroup v2 / 内核 5.15,节点 16C64G)。服务每天凌晨跑批,从 NFS 拉一份 1.8~2.2GB 的 CSV 用 bufio.Scanner 流式解析,单副本,limit=1Gi、request=1Gi(GQA)。
现象
连续三天凌晨 OOMKilled,k8s 事件里都是 oom_kill=1。我 cat /sys/fs/cgroup/memory.stat(OOM 前 30 秒的快照):
anon 512MB、file 448MB、file_mapped 12MB、shmem 8MBmemory.current 968MB、memory.max 1073741824- pprof
/heap显示 Go heap in-use 仅 380MB,runtime.MemStats.TotalAlloc没有异常增长
也就是说:Go 堆根本没满,是 448MB 的 file-backed page cache 把 cgroup 顶穿了。strace 看到全是 read(fd=7, ...),fd 指向 NFS 挂载的 CSV。
已尝试
- 把 limit 抬到 2Gi → 不再 OOM,但跑批时 P99 从 8ms 涨到 60ms(怀疑 page cache 抢占触发 kswapd 回收)。
- 用
O_DIRECT重开文件 → NFS 不支持,报EINVAL;本地盘能跑但吞吐掉到 1/4,业务不接受。 - 在节点上
echo 3 > /proc/sys/vm/drop_caches→ 只影响全节点,别的 Pod 一起抖,不敢在生产做。 - 想直接调
memory.high做软压力 → K8s 1.28 的 Pod spec 没有 memory.high 字段,只能塞memory.max,查了一圈没有原生入口。
期望得到的答案形态
- cgroup v2 下,page cache 是否一定计入
memory.current参与 OOM 判定?有没有办法让内核在 OOM 时优先回收 file 而不是杀进程(比如memory.oom.group之外的开关)? - 社区里跑「大文件 IO 型 Go 服务」的 K8s limit 预算公式长什么样?我目前用的是
limit = Go heap 峰值 × 1.2 + 文件 IO 缓冲,但 448MB 的 page cache 怎么估?有没有按file字段动态留余量的落地案例(Prometheus 指标 + HPA/VPA 之类的)? - 如果必须保留 page cache 带来的吞吐收益,有没有比 O_DIRECT 更优雅的方案(比如
posix_fadvise(POSIX_FADV_DONTNEED)边读边释放、madvise(MADV_DONTNEED)、或 cgroup 层面的memory.swap.max=0+vm.vfs_cache_pressure调大)?各自的成本与收益怎么权衡?
我倾向不抬 limit(抬了 P99 就抖),也不想在节点上做全局长。想听社区里有同样场景的同学怎么落地的。
评论
暂无评论。