何时(不)使用 varbit 分块

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

什么是 varbit 编码?

从一开始,我们就设计了分块样本存储,以便轻松添加新编码。当 Facebook 发表了一篇关于其内存 TSDB Gorilla 的论文时,我们对 Gorilla 和 Prometheus 这两个独立开发的方法之间的许多相似之处感到好奇。然而,也存在许多根本性的差异,我们详细研究了这些差异,思考是否可以从 Gorilla 中获得一些启发来改进 Prometheus。

在难得的空闲周末,我决定试一试。在一阵编程狂热中,我实现了后来(经过大量的测试和调试)成为 varbit 编码的功能。

在未来的博文中,我将详细介绍这种编码的技术细节。目前,您只需要了解几个关键特性,以便在新 varbit 编码和传统 double-delta 编码之间做出决定。(从现在起,我将后者简称为“double-delta 编码”,但请注意 varbit 编码也使用 double delta,只是方式不同。)

varbit 编码有哪些优势?

简而言之:它提供了更好的压缩比。虽然 double-delta 编码对于实际数据集大约需要每样本 3.3 字节,但在 SoundCloud 的一个典型大型生产服务器上,varbit 编码可以低至每样本 1.28 字节。这几乎是三倍的空间效率(甚至略优于 Gorilla 报告的每样本 1.37 字节——但请注意,SoundCloud 的典型数据集可能与 Facebook 的典型数据集不同,所以这一点仅供参考)。

现在想想这意味着什么:内存中可以存储三倍的样本,磁盘上也可以存储三倍的样本,磁盘操作(disk ops)只有三分之一,而且由于磁盘操作目前是摄入速度的瓶颈,它也能让摄入速度快三倍。事实上,最近报道的每秒 80 万样本的新摄入记录,只有使用 varbit 分块——以及 SSD——才有可能实现,这很明显。对于机械硬盘,瓶颈会更早达到,因此三倍的增益意义更大。

所有这些听起来都好得令人难以置信…

那么缺点在哪里?

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

varbit 编码的另一个特性可能更为重要:varbit 分块中的样本只能顺序访问,而 double-delta 编码分块中的样本可以通过索引随机访问。由于 Prometheus 中的写入是仅追加的,因此不同的访问模式仅影响样本数据的读取。实际影响在很大程度上取决于原始 PromQL 查询的性质。

一个相对无害的情况是检索一个时间间隔内的所有样本。这发生在评估范围选择器(range selector)或渲染与抓取频率(scrape frequency)分辨率相似的仪表盘时。Prometheus 存储引擎需要找到间隔的起始点。对于 double-delta 分块,它可以执行二分查找,而对于 varbit 分块,它必须顺序扫描。然而,一旦找到起始点,间隔内所有剩余的样本无论如何都需要顺序解码,这在 varbit 编码下只会稍微贵一些。

对于从分块中检索少量非相邻样本,或在所谓的即时查询(instant query)中简单检索单个样本,权衡是不同的。存储引擎可能需要遍历大量样本才能找到需要返回的少数样本。幸运的是,即时查询最常见的来源是规则评估,它们引用每个相关时间序列中的最新样本。并非完全巧合的是,我最近改进了对时间序列最新样本的检索。本质上,添加到时间序列的最后一个样本现在被缓存了。只需要时间序列最新样本的查询甚至不再需要访问分块层,在这种情况下,分块编码是无关紧要的。

即使即时查询引用的是过去的样本,因此必须访问分块层,查询的其他部分(如索引查找)很可能也会主导总查询时间。但确实存在一些实际查询,其中 varbit 分块所需的顺序访问模式将变得非常重要。

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

varbit 分块的最坏情况是,您需要从一个非常长的时间序列的每个分块的中间某个位置获取一个样本。不幸的是,这种情况确实存在实际用例。假设一个时间序列压缩得很好,使得每个分块能持续大约八小时。这大约是每天三个分块,或每月大约 100 个分块。如果您有一个仪表盘,以 100 个数据点的分辨率显示过去一个月的时间序列,该仪表盘将执行一个从 100 个不同分块中检索单个样本的查询。即使在这种情况下,分块编码之间的差异也主要由查询执行的其他部分决定。根据具体情况,我猜测 double-delta 编码下的查询可能需要 50 毫秒,而 varbit 编码下可能需要 100 毫秒。

然而,如果您的仪表盘查询不仅涉及单个时间序列,而是聚合数千个时间序列,则需要访问的分块数量也会相应增加,顺序扫描的开销将变得占主导地位。(这种查询不受欢迎,我们通常建议对那些经常使用的查询,例如在仪表盘中使用的这类查询,使用记录规则来加速它们。)但在 double-delta 编码下,查询时间可能仍然可以接受,比方说一秒左右。切换到 varbit 编码后,相同的查询可能持续数十秒,这显然不是您希望仪表盘出现的状况。

经验法则是什么?

简单来说:如果您的磁盘容量和磁盘操作(disk ops)都没有限制,那么就不用担心,坚持使用经典的 double-delta 编码作为默认选项。

然而,如果您想要更长的保留时间,或者您目前受到磁盘操作(disk ops)的瓶颈限制,我建议您尝试新的 varbit 编码。启动您的 Prometheus 服务器时带上 -storage.local.chunk-encoding-version=2 参数,然后等待一段时间,直到您有足够多的使用 varbit 编码的新分块,以便评估效果。如果您发现某些查询变得慢得令人无法接受,请查看是否可以使用记录规则来加速它们。很可能这些查询即使在使用旧的 double-delta 编码时也能从中获得很大的提升。

如果您对 varbit 编码在幕后如何工作感兴趣,请继续关注不久的将来发布的另一篇博文。