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

何时(不)应使用 varbit 块

2016年5月8日作者 Björn “Beorn” Rabenstein

Prometheus 服务器的嵌入式时间序列数据库(TSDB)将每个时间序列的原始样本数据组织成大小为 1024 字节的固定块。除了原始样本数据外,每个块还包含一些元数据,允许为每个块选择不同的编码方式。最根本的区别在于编码版本。您可以通过命令行标志 -storage.local.chunk-encoding-version 为新创建的块选择版本。到目前为止,仅支持两个版本:版本 0(原始增量编码)和版本 1(改进的双增量编码)。在 0.18.0 版本  中,我们添加了版本 2,这是另一种双增量编码变体。我们称之为 varbit 编码,因为它在块内为每个样本使用了可变位宽。虽然版本 1 在几乎每个方面都优于版本 0,但版本 1 和版本 2 之间存在真正的权衡。这篇博客文章将帮助您做出决定。版本 1 仍然是默认编码,因此如果您在阅读本文后想要尝试版本 2,则必须通过命令行标志显式选择它。来回切换没有任何风险,但请注意,现有的块一旦创建,其编码版本就不会更改。不过,这些块会根据配置的保留时间逐渐被淘汰,并最终被使用命令行标志指定编码的新块所取代。

什么是 varbit 编码?

从一开始,我们就将块样本存储设计为易于添加新编码的结构。当 Facebook 发表了一篇 关于其内存 TSDB Gorilla 的论文  后,我们对 Gorilla 和 Prometheus 这两种独立开发的方法之间存在的许多相似之处产生了浓厚兴趣。然而,两者之间也存在许多根本性的差异,我们对此进行了详细研究,思考是否能从 Gorilla 中获得灵感来改进 Prometheus。

在难得的周末空闲时间里,我决定尝试一下。在一段编码冲刺中,我实现了后来(经过大量的测试和调试)成为 varbit 编码的代码。

在未来的博客文章中,我将描述该编码的技术细节。目前,您只需要了解几个特征,以便在新的 varbit 编码和传统的双增量编码之间做出决定。(从现在起,我将后者简称为“双增量编码”,但请注意,varbit 编码也使用了双增量,只是方式不同。)

varbit 编码的优势是什么?

简而言之:它提供了更好的压缩比。双增量编码对于真实数据集每个样本大约需要 3.3 字节,而 varbit 编码在 SoundCloud 的一个典型大型生产服务器上每个样本仅需 1.28 字节。这节省了近三倍的空间(甚至略优于 Gorilla 报告的每个样本 1.37 字节——但请持保留态度,因为 SoundCloud 的典型数据集可能与 Facebook 的不同)。

现在考虑一下其影响:内存中的样本数多出三倍,磁盘上的样本数多出三倍,磁盘操作次数仅为三分之一。由于磁盘操作目前是摄入速度的瓶颈,这也使得摄入速度可以提高三倍。事实上,最近报道的每秒 80 万个样本的新摄入记录只有通过 varbit 块才能实现——而且显然需要使用固态硬盘(SSD)。使用机械硬盘时,瓶颈会更早出现,因此 3 倍的收益就显得更加重要。

这一切听起来好得令人难以置信……

那么代价是什么?

首先,varbit 编码更复杂。因此,编码和解码值的计算成本有所增加,这从根本上影响了所有读取或写入样本数据的操作。幸运的是,这只是总操作成本中通常占比很小的一部分出现了比例增加。

varbit 编码的另一个特性可能更相关:varbit 块中的样本只能按顺序访问,而双增量编码块中的样本可以通过索引随机访问。由于 Prometheus 中的写入是仅追加(append-only)的,因此不同的访问模式只会影响样本数据的读取。实际影响在很大程度上取决于源 PromQL 查询的性质。

一种相当无害的情况是在时间间隔内检索所有样本。这发生在评估范围选择器或以与抓取频率相似的分辨率渲染仪表板时。Prometheus 存储引擎需要找到该时间间隔的起点。使用双增量块,它可以执行二分查找,而必须在 varbit 块中进行顺序扫描。然而,一旦找到起点,该间隔内的所有剩余样本无论如何都需要按顺序解码,这对于 varbit 编码来说仅稍微昂贵一点。

对于从块中检索少量非连续样本,或是在所谓的“即时查询”(instant query)中仅检索单个样本,情况则有所不同。潜在地,存储引擎必须迭代大量的样本才能找到要返回的那几个样本。幸运的是,即时查询最常见的来源是引用每个相关时间序列中最新样本的规则评估。并非完全巧合,我最近改进了时间序列最新样本的检索。本质上,现在添加到一个时间序列的最后一个样本会被缓存。只需要时间序列最新样本的查询甚至不再触及块层,在这种情况下,块编码是无关紧要的。

即使即时查询引用的是过去的样本,因而必须触及块层,查询的其他部分(如索引查找)也很可能会主导总查询时间。但确实存在一些现实生活中的查询,其中 varbit 块所需的顺序访问模式会产生显著影响。

varbit 块的最坏情况查询是什么?

varbit 块的最坏情况是,如果您需要从一个非常长的时间序列的每个块的中间获取一个样本。不幸的是,这确实存在实际用例。假设一个时间序列压缩得足够好,使得每个块持续约八小时。那大约每天三个块,一个月约 100 个块。如果您有一个仪表板,以 100 个数据点的分辨率显示过去一个月的时间序列,仪表板将执行一个从 100 个不同块中检索单个样本的查询。即使那样,块编码之间的差异也会被查询执行时间的其它部分所淹没。根据具体情况,我的猜测是,双增量编码的查询可能需要 50ms,而 varbit 编码则需要 100ms。

但是,如果您的仪表板查询不仅仅涉及单个时间序列,而是对数千个时间序列进行聚合,那么要访问的块的数量会相应增加,顺序扫描的开销将占据主导地位。(此类查询通常不被推荐,我们通常建议对频繁使用的此类查询使用 记录规则。)使用双增量编码时,查询时间可能仍然可以接受,比如在一秒左右。切换到 varbit 编码后,相同的查询可能需要数十秒,这显然不是仪表板所希望的结果。

有哪些经验法则?

简单来说:如果您的磁盘容量和磁盘操作都不受限,请不要担心,保持使用默认的经典双增量编码。

但是,如果您想要更长的保留时间,或者当前受限于磁盘操作瓶颈,我建议您尝试新的 varbit 编码。使用 -storage.local.chunk-encoding-version=2 启动 Prometheus 服务器,等待一段时间,直到拥有足够多使用 varbit 编码的新块来评估效果。如果您发现查询变得慢到无法接受,请检查是否可以使用 记录规则 来加速它们。最有可能的情况是,即使使用旧的双增量编码,这些查询也会从中受益匪浅。

如果您对 varbit 编码幕后的工作原理感兴趣,请留意在不久的将来发布的另一篇博客文章。