请参与 Prometheus 用户调研(2026 年 3 月版) ,帮助社区确定未来开发工作的优先级!

Prometheus 现代化:复合类型的原生存储

2026 年 2 月 14 日作者 Bartłomiej Płotka (@bwplotka)

在过去的一年里,Prometheus 社区一直在努力推动几项既有趣又雄心勃勃的变革,这些变革在以前可能被视为具有争议或不可行。虽然外界对这些变化知之甚少(例如,它并不是一个 OpenClaw Prometheus 插件,抱歉 🙃),但 Prometheus 的开发人员正在有机地将 Prometheus 推向一个明确且一致的未来。通过一点一滴的积累,我们出人意料地越来越接近那些作为开源项目曾经不敢想象的目标!

这篇文章希望能成为一系列博文的开篇,旨在分享一些可能让 Prometheus 新老用户和开发者感到兴奋的雄心勃勃的转变。在这篇文章中,我想重点谈谈复合类型的原生存储这一理念,它正在理顺许多随时间积累的挑战。请务必查看文中提供的链接,了解如何尽早采用这些变化或参与贡献!

注意免责声明:本文旨在作为一次有趣的概述,仅代表我作为 Prometheus 维护者的个人观点。文中提到的一些变化尚未获得 Prometheus 团队的正式批准;部分变化尚未在生产环境中得到验证。
注意本文由人类撰写;人工智能仅用于润色和语法修正。

经典表示:原始样本

众所周知,Prometheus 数据模型(即服务器、PromQL、协议)支持 gauges(仪表盘)、counters(计数器)、histograms(直方图)和 summaries(摘要)。OpenMetrics 1.0 进一步增加了 gaugehistograminfostateset 类型。

令人印象深刻的是,长期以来 Prometheus 的 TSDB 存储实现拥有一个极其简洁明了的数据模型。TSDB 允许存储和检索带有字符串标签的原始样本,这些样本仅包含 float64 值和 int64 时间戳。它完全不感知指标类型。

指标类型是在 TSDB 之上隐含的,供人类理解以及供 PromQL 工具尽力解析。为简单起见,我们将这种存储类型的方式称为经典模型或表示。在这种模型中:

我们有原始类型:

  • gauge 是“默认”类型,没有特殊规则,只是一个带有标签的浮点样本。

  • counter,名称中通常带有 _total 后缀,以便人类理解其语义。

    foo_total 17.0
    
  • info,指标名称需要 _info 后缀,且值始终为 1

我们有复合类型。这是乐趣开始的地方。在经典表示中,复合指标被表现为一组原始浮点样本:

  • histogram 是一组带有特定强制后缀和 le 标签的 counters

    foo_bucket{le="0.0"} 0
    foo_bucket{le="1e-05"} 0
    foo_bucket{le="0.0001"} 5
    foo_bucket{le="0.1"} 8
    foo_bucket{le="1.0"} 10
    foo_bucket{le="10.0"} 11
    foo_bucket{le="100000.0"} 11
    foo_bucket{le="1e+06"} 15
    foo_bucket{le="1e+23"} 16
    foo_bucket{le="1.1e+23"} 17
    foo_bucket{le="+Inf"} 17
    foo_count 17
    foo_sum 324789.3
    
  • gaugehistogramsummarystateset 类型遵循相同的逻辑——一组特殊的 gaugescounters 共同构成一个指标。

经典模型很好地服务了 Prometheus 项目。它显著简化了存储实现,使 Prometheus 成为最优化、最流行的开源时间序列数据库之一,并且在 Cortex、Thanos 和 Mimir 等项目中都有基于相同数据模型的分布式版本。

遗憾的是,总是有取舍。这种经典模型有一些局限性:

  • 效率:它往往会为复合类型带来开销,因为每一条新数据(例如新的桶)都会占用宝贵的索引空间(它是一个新的唯一序列),而样本的压缩率更高(变化较少,以时间为导向)。
  • 功能:它限制了存储数据的形状和灵活性(除非我们使用某种 JSON 编码的标签,但这有巨大的缺点)。
  • 事务性:复合类型的原始片段(独立的计数器)是独立处理的。虽然我们做了大量工作来确保抓取的写隔离和事务性,但当数据通过远程写入 (remote write) 或 OTLP 协议接收或发送时,事务性就会完全瓦解。例如,一个经典的 foo 直方图可能只发送了一部分,其 foo_bucket{le="1.1e+23"} 17 计数器序列可能被延迟或意外丢弃,从而引发误报。
  • 可靠性:TSDB 数据的消费者基本上必须猜测类型语义。没有任何机制阻止用户写入 foo_bucket 仪表盘或 foo_total 直方图。

复合类型原生存储的一瞥

经典模型在引入 原生直方图 后受到了挑战。TSDB 进行了扩展,以存储除浮点数以外的 复合直方图样本 。我们倾向于称之为原生直方图,因为 TSDB 现在可以“原生”地将完整的(带有稀疏和指数桶的)直方图作为原子复合样本进行存储。

当时,普遍的共识是到此为止。这种旨在取代“经典”直方图的特殊高级直方图使用复合样本,而其余指标仍使用经典模型。让其他复合类型与新的原生模型保持一致对用户来说具有破坏性,且工作量和风险太大。一个常见的反驳理由是,用户最终会自然地迁移他们的经典直方图,而且鉴于原生直方图强大的分桶能力和更低的成本,摘要(summary)也变得不那么重要了。

遗憾的是,迁移到原生直方图需要时间,因为使用它们需要稍微修改 PromQL,并且需要客户端做出相应的更新(应用程序必须定义新指标或将现有指标编辑为新直方图)。此外,一些旧软件将长期存在且永远不会迁移。最终,这使得 Prometheus 无法废弃经典直方图,并且所有软件方案可能在未来几十年内都需要支持经典模型。

然而,原生直方图确实将 TSDB 和生态系统推向了新的复合样本模式。其中一些变化可以轻松适配到所有复合类型。原生直方图也让我们看到了这种原生支持带来的诸多好处。我们不禁自问:是否可以添加现有复合指标的原生对应物来取代它们,最好是透明地取代?

2024 年,为了事务性和效率,我们有机地引入了 原生直方图自定义桶 (NHCB)  的概念,它本质上允许以原生方式存储具有明确桶的经典直方图,重用了原生直方图的复合样本数据结构。

NHCB 已被证明比经典表示效率高出至少 30%,同时在功能上与经典直方图对等。然而,出现了两个实际挑战减缓了采用速度:

  1. 展开(Expanding),即将 NHCB 转换为经典直方图,相对简单,但组合(Combining),即将经典直方图转换为 NHCB,通常是不可行的。我们不想等待客户端生态系统的普及,同时考虑到难以更改的旧版软件,我们设想在抓取时将 NHCB 从经典表示中转换(即组合)。这被证明在抓取时开销较大。此外,当接收“推送”(例如带有经典直方图的远程写入)时,组合逻辑实际上是不可能的,因为你可能会通过不同的远程写入分片或顺序消息接收到同一个直方图样本的不同部分(例如桶和计数)。这种组合挑战也是为什么 OpenTelemetry 收集器用户在使用 prometheusreceiver 时会看到额外开销的原因,因为 OpenTelemetry 模型严格遵循复合样本模型。

  2. 消费(Consumption)略有不同,尤其是在 PromQL 查询语法方面。我们最初的决定是使用类原生直方图的 PromQL 语法来呈现 NHCB 直方图。例如对于以下经典直方图:

    foo_bucket{le="0.0"} 0
    # ...
    foo_bucket{le="1.1e+23"} 17
    foo_bucket{le="+Inf"} 17
    foo_count 17
    foo_sum 324789.3
    

    当我们将其转换为 NHCB 时,你不能再使用 foo_bucket 作为指标名称选择器。由于 NHCB 现在作为 foo 指标存储,你需要使用:

    histogram_quantile(0.9, sum(foo{job="a"}))
    
    # Old syntax: histogram_quantile(0.9, sum(foo_bucket{job="a"}) by (le))
    

    这还有另一个影响。它违反了我们在文本格式方面的 “所见即所查” (what you see is what you query)  规则,至少在 OpenMetrics 2  出现之前是这样。

    除此之外,其他 Prometheus 输出(联邦、远程读取和远程写入)也会出现类似问题。

注意有趣的事实:Prometheus 客户端数据模型 (SDKs) PrometheusProto 抓取协议已经在使用复合样本模型了!

透明的原生表示

让我们开门见山。在有机发展中,Prometheus 社区似乎在以下两个理念上达成了一致:

  • 鉴于所有这些好处,我们希望最终迁移到存储层的全复合样本模型
  • 用户需要能够在不破坏消费层的情况下,在存储中从经典形式切换(例如抓取时)到原生形式。本质上,为了帮助解决非平凡的迁移痛点(查找谁在使用什么、双重写入、同步),避免 棘手的双模式协议变更 ,并为了 Prometheus 代码库的可持续性尽快废弃经典模型,我们需要确保最终的消费层迁移(例如 PromQL 查询)——与存储层解耦。

让我们看看这一方向的证据,同时也展示了你可以贡献或尽早采用的工作!

  1. 我们正在讨论“原生” 摘要 (summary) 状态集 (stateset) ,以完全消除所有复合类型的经典模型。欢迎加入并协助这项工作!

  2. 我们正在制定 OpenMetrics 2.0 ,以整合和改进拉取协议场景并应用新的经验。核心变化之一将是转向文本中的复合值 ,这使得文本格式对于原生支持复合类型的存储来说变得易于解析。这解决了组合挑战。请注意,目前默认情况下,所有复合类型在抓取时仍会“展开”为经典格式,因此对用户来说没有重大破坏性变更。欢迎加入我们的工作组提供帮助或反馈。

  3. Prometheus 的接收和导出协议已更新。远程写入 2.0 (Remote Write 2.0) 允许以“原生”形式传输直方图,而不是经典表示(仍支持经典表示)。在未来的版本(如 2.1)中,我们可以轻松遵循类似的模式并添加原生摘要和状态集。欢迎贡献力量以使远程写入 2.0 稳定 

  4. 我们正在试验消费兼容模式,将存储为复合样本的复合类型转换为经典表示。这并不简单,存在边缘情况,但可能比我们最初预期的更可行(而且必要!)。参见:

    在 PromQL 中,对于曾经是经典直方图的 NHCB,它可能按如下方式工作:

    # New syntax gives our "foo" NHCB:    
    histogram_quantile(0.9, sum(foo{job="a"}))
    # Old syntax still works, expanding "foo" NHCB to classic representation:
    histogram_quantile(0.9, sum(foo_bucket{job="a"}) by (le))
    

    我们也正在讨论其他替代方案,例如特殊标签或注解 

一旦实现,应该可以透明地将指标收集管道的不同部分完全切换到原生形式。

总结

将 Prometheus 迁移到原生复合类型世界并不容易,需要时间,特别是在编码、测试和优化方面。值得注意的是,它将指标负载的性能特征从统一、可预测的样本大小切换为取决于类型的样本大小。另一个挑战是代码架构——维护不同的样本类型已经被证明非常冗长(我们需要并集,Go! )。

然而,最近的工作揭示了一条非常清晰且可行的道路,它将在不久的将来在功能性、事务性、可靠性和效率方面带来明确的益处,这非常令人兴奋!

如果您对这些变化有任何疑问,请随时:

  • 在 Slack 上私信我。
  • 访问 #prometheus-dev Slack 频道分享你的问题。
  • 评论相关问题,创建 PR,并审核 PR(这是最有影响力的工作!)

Prometheus 社区也将参加在阿姆斯特丹举办的 KubeConEU 2026!请务必:

我希望能在未来的博文中分享我们在社区中看到的其他重要的、正交的转变。不敢保证(欢迎协助!),但有很多内容可以涵盖,例如(随机顺序,并非完整列表):

  1. 我们的原生 开始时间戳特性之旅 ,它简洁地为原生 增量时间性 (delta temporality)  扫清了障碍,无需使用“黑客”手段,如重用仪表盘、单独的指标类型层或 __temporality__ 之类的标签注解。
  2. 可选的 Prometheus 指标模式化 (schematization) ,试图解决指标命名和形状方面的大量稳定性问题;在 OpenTelemetry 语义约定 (semconv) 之上构建。
  3. 我们的 元数据存储之旅 ,试图改善 OpenTelemetry 实体和资源属性的存储与消费体验。
  4. 随着近期 OpenMetrics 归属权的转移,我们正在组织和扩展 Prometheus 抓取拉取协议。
  5. 一个令人难以置信的 TSDB Parquet  工作,来自三个 LTS 项目组(Cortex, Thanos, Mimir)的共同努力,试图改进高基数场景。
  6. 关于 PromQL 扩展的有趣实验,如 带有管道和变量的 PromQL  以及一些新的 SQL 转译想法。
  7. 治理变更。

开源社区见!