看到khugepaged出现在采样里,就把 THP 全关掉,很容易把排障变成猜参数。compact_stall在一张截图里很大,也可能只是机器已经运行了很久。没有时间窗口,累计值不能告诉我们这一分钟发生了什么。
这篇用构造日志讲排查步骤,不写成一次发生过的生产事故。真实系统的证据应包含同一时间窗的延迟、CPU、内存压力、内核栈与计数器变化,而不是先给一个惊险故事,再替故事找命令。
从两个快照开始
Linux 的 THP 文档还讨论了 multi-size THP;不是所有透明大页都只能按“2MB 对 4KB”解释。可用大小与启用方式取决于实际系统。文档也提醒,某些MADV_COLLAPSE行为不被普通 sysfs 的 never 设置完全覆盖,因此“写入 never 就永久关闭一切 THP”过于绝对。官方说明
先记录内核版本与/proc/vmstat的两个快照,把差值除以间隔得到速率。若发生计数回退,可能是重启或输入不一致;应中止当前比较,而不是把负数当成性能改善。
什么证据能支持 THP 假设
内存规整计数上涨只是线索。还需要看延迟尖峰是否同窗、采样栈是否落在相关路径、压力是否来自其他内存回收。然后在可以回滚的对照环境里改变一个条件。样例不执行任何内核写操作,也不提供脱离版本条件的调参套餐。
图把累计计数、增量与速率分开。两个快照中的数值是教学输入,不代表服务器的真实基线,也不能单独证明 THP 导致了请求停顿。
def parse_vmstat(text): result = {} for line in text.splitlines(): fields = line.split() if len(fields) != 2 or fields[0] in result: raise ValueError("invalid or repeated counter") value = int(fields[1]) if value < 0: raise ValueError("negative counter") result[fields[0]] = value return result def rates(before, after, seconds): if seconds <= 0: raise ValueError("positive observation interval required") result = {} for key in before.keys() & after.keys(): delta = after[key] - before[key] if delta < 0: raise ValueError("counter reset: " + key) result[key] = delta / seconds return result before = parse_vmstat("compact_stall 100\nthp_collapse_alloc_failed 8") after = parse_vmstat("compact_stall 130\nthp_collapse_alloc_failed 10") r = rates(before, after, 10) assert r == {"compact_stall": 3.0, "thp_collapse_alloc_failed": 0.2} try: rates(after, before, 10) except ValueError: pass else: raise AssertionError("reset was interpreted as improvement") print("compact_stall=3.0/s; collapse_failed=0.2/s; reset rejected")例子跑通以后,还要验证什么
这段代码已在本地 macOS、CPython 3.9.6 下运行,实际输出为compact_stall=3.0/s; collapse_failed=0.2/s; reset rejected。它验证的是例子中的数据约束与分支,不包含线上服务的吞吐或延迟测量。
本地仅运行文本解析与差分逻辑,没有读取宿主机 Linux 指标、执行 perf 或更改 sysfs。真正定位需要业务延迟与内核证据的时间对齐,以及适合该发行版的观测手段。关闭功能后的改善也要检查吞吐、内存占用和后续回归,不能只看一次 P99。
延伸阅读
- Linux Transparent Hugepage Support:THP 大小、控制方式与行为边界的依据。