视频

了解更多信息
英特尔和 Redis 正在合作探索 RedisTimeSeries 压缩/解压算法的潜在性能优化。以下是该探索的工作原理。
时间序列数据管理在数据分析中至关重要。许多应用程序需要存储带有时间戳的数据,例如股票价格、物联网传感器数据和 CPU 性能指标。根据定义,数据准确性是存储此信息的重要因素,存储和访问速度也是如此。
RedisTimeSeries 是一款时间序列数据库 (TSDB) 模块,可将大量流数据完全保留在内存中。得益于称为 Gorilla 的先进压缩/解压缩算法,可快速处理数据。在这篇博文中,我们将回顾 RedisTimeSeries 中的 Gorilla 算法实现,以及进一步优化的潜在思路。虽然我们此时选择保留现有的压缩/解压缩实现,但我们希望在对持续改进的共同追求中分享我们的探索过程。
Gorilla 算法是由 Meta 为其大规模分布式 TSDB 开发的。Meta 的一项关键 TSDB 要求是在内存中保留至少 26 小时的流数据。它必须可用于快速分析。此外,较新数据通常比旧数据更有价值,可用于检测事件、异常等。这使得压缩率和查询效率至关重要。
Gorilla 针对时间序列数据的特征定制压缩算法。特别是,其作者认识到顺序数据点的值密切相关,并且使用“delta”样式算法可以以较低的计算开销实现出色的压缩率。例如,数据点可能会在规则的时间间隔到达,例如每 30 秒一次。因此,时间戳之间的差值通常相同或类似。顺序浮点数据值之间的差值也应密切相关。
作者建议对整数时间戳使用“差分的差分”算法,对浮点值使用基于异或的差分编码。对编码的时间戳和数据值依次插入到内存缓冲区中。请参阅 原始论文,了解编码/解码算法的详细信息。
总体而言,Gorilla 算法实现了大约 10 倍的压缩率,其中超过 96% 的时间戳被压缩为一个比特,超过 59% 的浮点值被压缩为一个比特。
RedisTimeSeries 数据库实现了 Gorilla 算法,用于压缩和解压缩时间序列数据。
它可以更快。(话又说回来,在性能优化方面,它总是可以更快!这就是 Redis 存在的意义。)
虽然 Gorilla 可以实现优秀的压缩率和计算效率,但在某些情况下——例如对时间序列数据库进行计算过滤操作时——解压缩例程会占据大量时间。例如在下面的火焰图中,我们看到 decompressChunk 占用了很大一部分执行时间,在一个实例中高达 85%。
考虑到解压缩会占用大量的 CPU 资源,Redis 与英特尔进行了合作调查研究。我们对 RedisTimeSeries 进行了检测,将整数时间戳和浮点值分离到两个单独的内存缓冲区中。这让我们能够独立地研究它们的特征,并对每种数据类型独立地应用潜在的优化。
我们的第一个想法是探索对压缩/解压缩计算进行矢量化。可以在“用于加速压缩算法的通用基于 SIMD 的方法”中找到有关用于对压缩算法进行矢量化的技术的有益概述。一般来说,为了对多个元素并发进行操作,必须对用于对数值进行编码的位宽强制施加一些规则。这种强制施加的规则可能会导致牺牲压缩率。
为了进行评估,我们测试了在 TurboPFor:最快的整数压缩 中在线可用的不同压缩算法的矢量化实现,使用来自时间序列基准套件 DevOps 用例的输入作为数据。在我们的分析中,我们发现浮点值的解压缩最耗时(整数时间戳的解压缩速度快得多),因此我们把注意力集中在这一点上。我们从数据集提取了一批浮点值,并使用 TurboPFor 以及我们的 Gorilla 实现对它进行了测试。
在图 2 中,我们展示了 TurboPFor 库中在我们数据集上以压缩率作为衡量指标的性能最佳的算法。它显示 TurboFloat DFCM 是在压缩率方面最好的算法:数值被压缩到原始数值的 11.15%。但是,对于相同数据集,Gorilla 的压缩率仍然明显更好,平均每个 64 位浮点数为 9.3% 或 6.01 位。
由于 Gorilla 提供的压缩率要好于 TurboPFor,因此我们决定坚持使用现有的算法,并把注意力转向进一步对 Gorilla 的实现进行微调。
接下来,我们深入研究在我们数据集观察到的数据模式,以寻找其他可能的优化线索。
首先,我们在 RedisTimeSeries 中植入了代码以打印出用于在 4K 块中对每个整数时间戳和浮点值进行编码所用的比特位数。在大多数情况下,整数时间戳压缩为单个比特位,这与 Gorilla 论文中的发现一致。另一方面,用于对浮点值进行编码的比特位模式变化很大且不规则。
这些结果使我们得出结论,专注于通过矢量化优化双重差值浮点解压在性能和代码复杂度方面不会有回报。基于此认识,我们决定深入研究其他潜在的优化,这些优化不基于对浮点异或编码进行矢量化。
我们在测试数据集里打印出浮点值,并分析了它们的特征。这样做帮助我们发现,在很多情况下(Gorilla 论文中也提到了这一点),浮点值实际上是以双精度浮点数表示的整数(例如 72.0、68.0、71.0)。然而,与时间戳相比,这些整数值并不按顺序递增或递减。
此外,在生成不同值出现频率的直方图后,我们发现只有一个很少数值(少于 128)代表我们示例数据集中的绝大部分观测数据点。
基于这些观察,我们认为通过将 Gorilla 与字典和/或游程编码结合使用,可以实现更好的性能和/或压缩比率。但是,我们仍然需要针对更大范围的数据集进行进一步的分析。
浮点数据可能过于冗余或成为存储信息的低效方法。数据通常测量物理量(例如温度),而传感器具有给定的范围和精度。即便是股票价格也有正负一美分的分辨率。
因此,在创建时间序列时的另一种选择可能是指导用户指定范围(例如 0-100)和分辨率(例如 0.01)。
外部接口仍将是浮点数,但在内部,我们可以以最有效的方式(大小和性能)将测量值转换为整数。
基于我们对 Gorilla 和其他快速压缩算法的压缩比率的分析,我们的 Gorilla 算法当前是最优的压缩比率选项。我们还探索了典型时间序列数据集的特征,从中获得了关于我们如何进一步改进 Gorilla 实现的灵感。
我们的结论:到目前为止,一切顺利。我们决定不更改现有实现。然而,我们继续研究不同数据集的特性并评估增强思路,例如扩展矢量化的使用以利用 RedisTimeSeries 加速数据处理、减少我们的协议的序列化开销,甚至改进我们存储和访问数据结构的方式。
这只是 Redis 正在进行的性能探索的一个示例,这归功于英特尔与 Redis 之间的合作。如果您喜欢我们迄今为止的描述,我们鼓励您查看我们如何扩展 Redis 的能力以追求 Redis 核心性能回归并改进数据库代码效率,我们在 引入 Redis/英特尔基准测试规范,用于性能测试、分析和概要分析 中对此进行了详细介绍。