TimescaleDB 压缩:Hypercore 与列式存储
TimescaleDB 通过使用 hypercore 引擎,实现了时间序列数据最高可达 98% 的压缩率。不同于通用压缩,hypercore 采用混合行‑列方式,利用针对时间序列数据数学特性(如单调性和重复性)定制的专用算法。
Hypercore 与 PostgreSQL TOAST 的对比
TimescaleDB 的压缩是对 PostgreSQL 内置 TOAST(Oversized‑Attribute Storage Technique)的一种补充,而不是替代。TOAST 处理单个超大值(例如长字符串或 JSONB),当其超过特定阈值时进行存储;而 hypercore 优化 跨行模式。
| 功能 | TOAST(原生 PostgreSQL) | TimescaleDB hypercore |
|---|---|---|
| 设计目标 | 单个值 > 2 KB | 时间序列中的跨行模式 |
| 触发条件 | 行超过 TOAST_TUPLE_THRESHOLD |
按块策略(例如,超过 7 天的旧块) |
| 支持类型 | 仅可变长度(text、jsonb 等) |
所有数据类型 |
| 算法 | pglz、lz4 |
Delta、Delta‑of‑Delta、Simple‑8b、RLE、基于 XOR、字典 |
| 粒度 | 按值 | 按批次(约 1000 行) |
| 数据结构 | 将值视为不透明字节 | 利用数值结构和重复性 |
| 传感器浮点比例 | ~1.0× | 10‑20× |
| 时间戳比例 | ~1.0× | 50‑100× |
| 文本比例 | 2‑3× | 5‑10× |
列式压缩的工作原理
Hypercore 将较旧的数据块从面向快速 INSERT 的行式格式转换为列式格式。在此过程中,行被分组成最多 1,000 行的批次。每个批次作为压缩表中的单行存储,列以数组形式呈现。
专用压缩算法
TimescaleDB 根据列的数据类型选择压缩算法,以实现最高效率:
- 整数、时间戳和布尔值:使用 增量编码(存储值之间的差异)、增量的增量(存储差异的变化,对规则间隔为 0)、simple‑8b 和 游程编码 (RLE) 的组合。
- 浮点数(如温度/振动):采用 基于 XOR 的压缩(基于 Gorilla 算法)。通过对相邻浮点数进行 XOR,发动机只存储有效位,忽略长串零位。
- JSONB:采用两层方式:首先使用字典压缩重复值,若不存在重复则回退到 PostgreSQL TOAST。
- 字符串及其他类型:使用 字典压缩,字典索引本身再通过 simple‑8b 与 RLE 进一步压缩。
示例:Delta 与游程编码
对于在多行中重复的 machine_id,RLE 只存储一次该值并附带计数(例如 MACHINE_001 × 5),而不是重复五次字符串。对于规则间隔的时间戳,增量的增量编码可以将每个值的存储需求降至几乎为零字节。
使用 segmentby 与 orderby 优化压缩
有两个参数对行如何分批以及压缩效果至关重要:
segmentby:定义在一个批次内共享的列(例如machine_id)。该列的值在每个批次只存储一次。查询规划器利用此元数据跳过不满足WHERE条件的整批数据。orderby:定义批次内部的排序顺序(通常为time DESC)。按时间排序可最大化 delta 与 delta‑of‑delta 编码的效果,因为相邻值更可能相似。
配置示例:
ALTER TABLE iot_sensor_data SET (
timescaledb.orderby = 'time DESC',
timescaledb.segmentby = 'machine_id'
);
最佳实践: 每个 segment 在每块中应至少包含 100 行,最佳范围为每块 100‑10,000 个唯一的 segmentby 值。
对查询性能的影响
对于大多数时间序列工作负载,压缩 通过将 I/O 需求降低 10‑20 倍,提升查询速度。
性能提升
- 基于时间范围的扫描 并伴随聚合(
SUM、AVG、MAX)。 - 在
segmentby列上过滤的查询。 - 大范围的顺序扫描。
性能权衡
- 单行的 点查询 可能会变慢。
- 对压缩块的 UPDATE/DELETE 需要先解压‑修改‑再压缩的循环。
- 对高基数列且 没有
segmentby过滤 的查询可能出现性能下降。
实际基准
在一次使用 MQTT 传感器数据的生产测试中,按 id 和窄时间范围的点读取实现了 28 倍的执行时间加速(10.2 ms 降至 0.36 ms)以及 42.8 倍的压缩率(308 MB 降至 7.2 MB),从行存储迁移到列存储块后得到的结果。
为什么列存储更快
- 稀疏 MinMax 索引:TimescaleDB 自动在
(segmentby_col, _ts_meta_min_1, _ts_meta_max_1)上构建索引。该索引使引擎能够在不读取实际数据的情况下剔除整批。 - 原生过滤:由于具有相同
id的行在物理上已分组,引擎能够立即命中正确的 segment,无需在(id, time)上建立大型 B‑tree 索引。 - 向量化执行:对时间范围的操作以 1,000 行为单位批处理,而非逐行处理,降低 CPU 开销。