Prometheus 中的未缓存 I/O
2026年3月5日作者 Ayoub Mrini (@machine424)
你是否发现自己一直在查找 container_memory_usage_bytes、container_memory_working_set_bytes 和 container_memory_rss 之间的区别?选错了,你的内存限制就会骗你,你的基准测试会误导你,你的容器会因内存不足而被 OOMKilled。
你不是一个人。甚至有一个长达9年的Kubernetes问题 ,它捕捉了用户的沮丧情绪。
解释很简单:RAM 的使用方式并非只有一种。最容易被忽视的一点是页面缓存 的语义。对于某些容器来说,页面缓存占用的内存可能构成报告使用量的大部分,即使这部分内存大多是可回收的,从而在这些指标之间造成惊人的差异。
注意这里讨论的功能目前仅支持 Linux。
Prometheus 会向磁盘写入大量数据。毕竟,它是一个数据库。但并非所有写入都受益于页面缓存。压缩写入是最好的例子:一旦一个块被写入,只有一小部分数据可能很快再次被查询,而且由于无法预测是哪一部分,所以缓存所有数据几乎没有回报。 use-uncached-io 功能标志正是为了解决这个问题而创建的。
对于这些写入绕过缓存可以减少 Prometheus 的页面缓存占用,使其内存使用更可预测且易于理解。它还能减轻共享缓存的压力,降低驱逐查询和其他读取实际依赖的热数据的风险。一个潜在的好处是减少了缓存分配和驱逐带来的 CPU 开销。整个过程中的硬性约束是避免 CPU 或磁盘 I/O 出现任何可衡量的性能下降。
此标志在 Prometheus v3.5.0 中引入,目前仅支持 Linux。在底层,它使用直接 I/O,这需要适当的文件系统支持和 v2.4.10 或更高版本的内核,不过你大可放心,因为该版本已发布近 25 年了。
如果直接 I/O 在这里有帮助,为什么不早点实施呢?为什么不将其应用于所有有益之处呢?因为直接 I/O 具有严格的对齐要求。与缓冲 I/O 不同,你不能简单地将任何内存块写入文件的任何位置。文件偏移量、内存缓冲区地址和传输大小都必须与底层存储设备的逻辑扇区大小(通常为 512 或 4096 字节)对齐。
为了满足这些限制,实现了一个类似 bufio.Writer 的写入器 directIOWriter。在 Linux 内核 v6.1 或更高版本上,Prometheus 通过 statx 获取精确的对齐值;在旧内核上,则使用保守的默认值。
directIOWriter 目前仅覆盖压缩期间的块写入,但仅此一项就占用了 Prometheus I/O 的很大一部分。结果是显而易见的:基准测试显示页面缓存使用量减少了 20–50%,这通过 container_memory_cache 来衡量。
container_memory_cache 随时间变化,基准线(上) vs. use-uncached-io(下)use-uncached-io这项工作尚未完成,欢迎贡献。以下是一些可以帮助该功能更接近通用可用性的领域
覆盖更多写入路径
直接 I/O 目前仅限于压缩期间的块写入。索引文件和 WAL 写入是下一个自然的候选,尽管它们需要一些额外的工作。
围绕 directIOWriter 建立更多信心
所有现有的 TSDB 测试都可以使用专用的构建标签针对 directIOWriter 运行:go test --tags=forcedirectio ./tsdb/。欢迎更多涵盖写入器本身边缘情况的测试,甚至有一个想法是正式验证它从不违反对齐要求。
试验 RWF_DONTCACHE
RWF_DONTCACHE 在 Linux 内核 v6.14 中引入,它启用了未缓存的缓冲 I/O,数据仍然通过页面缓存,但相应的页面随后被丢弃。值得测试这是否能在没有直接 I/O 对齐限制的情况下提供类似的优势。
Linux 之外的支持
目前仅支持 Linux。欢迎贡献以将其扩展到其他操作系统。

