时序数据压缩:delta-of-delta、XOR 浮点编码与十进制数据上的失效

💡 原文中文,约14300字,阅读约需34分钟。
📝

内容提要

Gorilla的XOR压缩在时间戳等间隔、值不变或缓变时可达每点1.37字节,但毫秒抖动和十进制小数会使其失效。对比Prometheus、InfluxDB、VictoriaMetrics的实现,时间戳对齐比细分桶更有效;XOR在十进制数据上每值需50多比特,Chimp、ALP等后续工作通过十进制编码改善。

🔎

延伸解读

时间戳对齐比细分桶更有效

文章通过实验表明,在编码前将时间戳对齐到理想网格,能大幅减少非零delta-of-delta,从而降低时间戳编码开销。对齐后,非零D_i减少了80%,Gorilla和Prometheus XOR的每时间戳比特数分别从2.00和2.88降至1.20和1.38。相比之下,仅细化分桶对毫秒抖动带来的非零D_i改善有限,因为最小非零档仍需16比特。因此,对于抓取间隔固定的监控系统,优先实现时间戳对齐是更有效的优化手段。

XOR编码在十进制数据上的失效与改进

XOR编码利用浮点比特模式的高位和低位稳定性,但对十进制小数(如温度、价格)效果很差。因为十进制小数在二进制中尾数低位随机,异或后尾随零很少,有效位常达40-50位,导致每个值需50多比特。后续工作如Chimp、Chimp128和ALP通过不同策略改善:Chimp128在少量小数位时表现较好,ALP则采用十进制编码,每增加一位小数约增加3.3比特,更符合信息量。

生产实现的差异与选择考量

Prometheus、InfluxDB和VictoriaMetrics在压缩实现上各有特点。Prometheus的XOR2格式合并时间戳和值编码,在短块(如120样本)时比XOR节省6%-7%,但块超过127样本时因ST头处理反而可能增加开销。InfluxDB的时间戳列使用一阶差分加RLE或Simple-8b,适合等间隔数据;其值编码存在前导零掩码bug,导致大整数计数器压缩率下降。VictoriaMetrics先将浮点转十进制整数再zstd压缩,对监控数据高效但有损(超过12位有效数字可能精度降低)。选择时需权衡

❓

Q&A

Gorilla 的 XOR 压缩在什么条件下能达到每点 1.37 字节?

Gorilla 论文报告 Facebook 生产数据平均压到每个点 1.37 字节,但前提是时间戳近乎等间隔,值要么不变、要么是小整数或变化缓慢的量。

为什么 XOR 编码在十进制小数数据上会失效?

十进制小数如 20.37、3.99 存成 float64 时尾数是无限循环二进制小数截断后的 52 位,低位几乎随机。相邻值哪怕只差 0.01,异或的尾随零也接近 0,有效位常达 40 到 50 位,加上控制位每个值需 45 到 55 比特,比不压缩好不了多少。

Prometheus 如何解决抓取时间戳抖动问题?

Prometheus 在编码前通过 AlignScrapeTimestamps 默认开启,若实际时间与理想网格偏差不超过 ScrapeTimestampTolerance(2 ms 且不超过间隔的 1%),就把样本时间戳改写成网格时间。这能把非零 delta-of-delta 减少 80%,比换更细的分桶更有效。

VictoriaMetrics 如何压缩浮点数据?

VictoriaMetrics 不用 XOR,而是先把 float64 转成 int64 尾数加公共十进制指数,再按内容选类型:全相同用 Const,等差用 DeltaConst,像计数器用二阶最近差分 ZSTDNearestDelta2,否则用一阶 ZSTDNearestDelta,差分经 zigzag 变长整数后交给 zstd。节点数据上每值 4.10 比特,比 Gorilla 的 6.81 少 40%。

Chimp、ALP 等后续工作对 XOR 编码做了哪些改进?

Chimp 和 Chimp128 改进了 XOR 编码,在十进制数据上表现更好,例如 d≤2 时 Chimp128 约 18-22 比特。ALP 则按十进制思路编码,随小数位数平滑增长,每位小数约 3.32 比特,在真实数据集上常优于 XOR。

InfluxDB 的浮点编码存在什么缺陷?

InfluxDB v1.13.1 的浮点编码中,前导零钳位代码先做 leading &= 0x1F 再判断 >= 32,导致判断永远不成立。L∈[32,63] 被存成 L-32,解码端多读、编码端多写 32 位高位零,结果无损但浪费。在节点数据上,这使每值比特数从 6.81 增加到 10.63,增加 56%。

🏷️

标签

➡️

继续阅读