何时使用(不使用)varbit 数据块

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

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

什么是 varbit 编码?

从一开始,我们就设计了分块样本存储,以便轻松添加新的编码方式。当 Facebook 发表了一篇关于其内存型时序数据库 Gorilla 的论文时,我们对 Gorilla 和 Prometheus 独立开发的方案之间的一些相似之处很感兴趣。然而,也存在许多根本性的差异,我们对此进行了详细研究,思考是否能从 Gorilla 中获得一些启发来改进 Prometheus。

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

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

varbit 编码的优势是什么?

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

现在考虑一下其影响:内存中可容纳三倍的样本,磁盘上可存储三倍的样本,磁盘操作(disk ops)只有原来的三分之一,而且由于磁盘操作目前是摄入速度的瓶颈,varbit 编码也将使摄入速度提高三倍。事实上,最近报告的每秒 800,000 个样本的新摄入记录,只有在使用 varbit 数据块的情况下才能实现——显然还需要配合 SSD。对于机械硬盘,瓶颈会更早出现,因此 3 倍的收益意义更大。

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

那么有什么缺点呢?

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

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

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

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

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

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

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

然而,如果你的仪表盘查询不仅涉及单个时序,而是聚合了数千个时序,那么需要访问的数据块数量将相应地成倍增加,并且顺序扫描的开销将变得主导。(此类查询不被推荐,我们通常建议对仪表盘中频繁使用的这类查询使用 记录规则。)但使用 double-delta 编码时,查询时间可能仍然可以接受,例如大约一秒。切换到 varbit 编码后,同样的查询可能持续数十秒,这显然不是你希望仪表盘出现的情况。

经验法则是什么?

简单来说:如果你在磁盘容量和磁盘操作方面都没有限制,那么不用担心,坚持使用经典的 double-delta 编码的默认设置即可。

然而,如果你希望更长的保留时间,或者目前受限于磁盘操作瓶颈,我建议你尝试新的 varbit 编码。使用 -storage.local.chunk-encoding-version=2 启动你的 Prometheus 服务器,并等待一段时间,直到你有足够多的采用 varbit 编码的新数据块来验证其效果。如果你发现查询变得无法接受地慢,请检查是否可以使用 记录规则 来加速它们。很有可能,即使使用旧的 double-delta 编码,这些查询也能从中获得很大的提升。

如果你对 varbit 编码的幕后工作原理感兴趣,敬请期待不久的将来发布的另一篇博文。