TimescaleDB 如何压缩时序数据
速览
TimescaleDB 通过先进的压缩技术显著降低时序数据的存储成本。该机制在保持查询性能的同时,有效优化了数据写入效率。这对于处理大规模物联网和监控数据至关重要。
AI 深度解读
TimescaleDB 压缩技术深度解析:Hypercore 引擎与列式存储如何实现 98% 压缩率
背景
在物联网(IoT)和时序数据(Time-series data)领域,数据量往往呈指数级增长。传统的通用数据库压缩算法(如 OLTP 数据库中常用的算法)难以高效处理这类具有特定模式的数据。虽然 PostgreSQL 内置了 TOAST(The Oversized-Attribute Storage Technique,超大属性存储技术)机制,但其设计初衷是解决单个字段值过大(如长字符串、JSONB、Bytea)的问题,通过压缩或拆分数据以适应固定的页大小(通常为 8 kB)。
然而,时序数据的痛点不在于单行数据的体积,而在于跨行的数据模式。TimescaleDB 作为基于 PostgreSQL 构建的时序数据库,引入了名为 Hypercore 的混合行-列存储引擎,旨在解决这一根本不同的问题。Hypercore 能够利用时序数据的特性,实现高达 98% 的压缩率,显著降低长期数据保留的存储成本,并提升分析查询的性能。
核心内容
Hypercore 引擎与列式存储架构
TimescaleDB 的压缩机制核心在于 Hypercore 引擎。这是一个混合的行-列存储引擎,其工作流程如下:
- 写入阶段(行式存储):新写入的数据首先以基于行的块(chunks)形式存储在 PostgreSQL 中,以支持快速的 INSERT 和 UPDATE 操作。
- 转换阶段(列式压缩):随着数据变旧,这些块会自动转换为列式、压缩后的格式。
- 读取阶段(列式扫描):分析性查询读取压缩后的数据时,只需读取必要的字节,从而大幅减少 I/O 并提高查询速度。
与传统行式存储按行顺序存储不同,列式存储按列组织和压缩数据。这意味着查询可以批量获取所需字段,而无需扫描整个行。
数据压缩的具体过程
当对块进行压缩时,系统会将行分组为最多 1000 行的批次(batch)。每个批次在压缩表中变为单行,其列存储为数组。
- 列主序格式:批次内部采用列主序(column-major)格式,将同一列的值聚集在一起。这使得查询可以选择单个列而无需读取整个批次。
- 高级压缩技术:每个批次应用多种列级压缩技术,包括游程编码(Run-Length Encoding, RLE)、差值编码(Delta Encoding)和 Gorilla XOR 压缩。
具体压缩算法详解
TimescaleDB 并非使用“一刀切”的压缩算法,而是根据列的数据类型智能选择算法:
-
整数、时间戳、布尔值及类整数类型:
- 使用 差值编码(Delta Encoding)、差分之差编码(Delta-of-Delta)、Simple-8b 和 游程编码(RLE) 的组合。
- 差值编码:仅存储当前值相对于前一个值的变化量。对于变化微小的数值,这能显著减小存储大小。
- 差分之差编码:当时序数据具有恒定间隔(如每 5 秒采集一次)且值重复时,差分之差为 0,可用极少的比特位存储。Facebook 的 Gorilla 算法也采用了类似的时间戳处理策略。
- Simple-8b:将差分之差产生的小数值物理打包,每个值仅需几位比特。
-
浮点数(如温度、振动测量):
- 使用基于 Gorilla 的 XOR 压缩,辅以字典压缩。
- 当相邻浮点数相似时,XOR 运算结果会产生大量前导和尾随零。此时只需存储中间具有意义的比特位,而非完整的 64 位。
-
JSONB 类型:
- 采用双层策略:首先尝试字典压缩(当值重复时);若无重复,则回退到 PostgreSQL 的 TOAST 机制(默认为
pglz,若配置则使用lz4)。
- 采用双层策略:首先尝试字典压缩(当值重复时);若无重复,则回退到 PostgreSQL 的 TOAST 机制(默认为
-
其他类型(字符串、非常规类型):
- 主要使用 字典压缩(Dictionary Compression)。
- 字典索引本身也会经过 Simple-8b 和 RLE 处理,形成两级压缩。
案例对比:
- 高重复性数据:如
machine_id或sensor_type。若 5 行均为MACHINE_001,RLE 仅存储一次该值及计数器,节省大量空间。 - 单调递增数据:如时间戳,压缩后几乎为零字节。
- 高熵数据:如每行唯一的 UUID。由于字典无法有效压缩唯一值,TimescaleDB 会检测此情况并禁用字典压缩,否则压缩效果极差。
关键配置参数:segmentby 与 orderby
压缩效果极大程度上取决于数据如何被分组。两个关键参数决定了行如何被聚合成批次:
-
segmentby:- 指定在批次内共享值的列(如
machine_id或sensor_id)。 - 该列的值在每个批次中仅存储一次,而非数组。
- 查询优化:查询规划器利用
segmentby的元数据,可以直接跳过不符合WHERE子句的整个批次,无需读取数据本身。 - 注意事项:
segmentby的基数(Cardinality)不能过高。如果每个传感器在块中只有几行,会导致批次填充不足,压缩失效。官方建议每个段在块中至少包含 100 行,最优情况下每个块有 100–10,000 个唯一的segmentby值。
- 指定在批次内共享值的列(如
-
orderby:- 指定批次内部的排序顺序,通常为
time DESC。 - 按时间排序能使差值编码和差分之差编码发挥最大优势,因为相邻值非常接近,差异极小,易于压缩。
- 指定批次内部的排序顺序,通常为
配置示例:
ALTER TABLE iot_sensor_data SET (
timescaledb.orderby = 'time DESC',
timescaledb.segmentby = 'machine_id'
);
在此配置下,查询 WHERE machine_id = '...' AND time BETWEEN ... 的速度可比未配置 segmentby 时快一个数量级,因为规划器基于元数据跳过了其他机器的数据批次。
关键要点
- 压缩原理差异:TimescaleDB 压缩针对跨行模式优化,而 PostgreSQL TOAST 仅针对单行大字段优化,两者互补。
- 混合存储引擎:Hypercore 引擎结合行式写入(快速)和列式存储(高效压缩与分析),实现高达 98% 的压缩率。
- 算法自适应:
- 数值型/时间戳:Delta + Delta-of-Delta + Simple-8b。
- 浮点数:Gorilla XOR 压缩。
- 重复字符串:游程编码(RLE)。
- 高熵唯一值:禁用字典压缩,避免无效压缩。
- 配置至关重要:
orderby设为时间降序以最大化差值压缩效果。segmentby用于分组和查询剪枝,但需避免基数过高导致批次稀疏,影响压缩效率。
- 性能提升:合理的压缩配置不仅节省存储,还能通过减少 I/O 和元数据剪枝显著提升分析查询速度。
意义与影响
TimescaleDB 的压缩技术解决了时序数据长期存储的成本痛点。通过实现高达 98% 的压缩率,企业可以以更低的成本保留更长时间的历史数据,这对于需要长期趋势分析、故障回溯和合规性审计的场景至关重要。
此外,列式存储与智能压缩的结合,使得在 PostgreSQL 生态系统中进行大规模时序数据分析成为可能。它打破了传统关系型数据库在处理海量时序数据时的性能瓶颈,允许用户在不牺牲写入性能的前提下
