news 2026/6/30 15:48:58

TimescaleDB压缩算法实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
TimescaleDB压缩算法实现

压缩功能完全不需要 PG 内核修改

TimescaleDB 的压缩和列存功能是纯 PostgreSQL 扩展实现,不需要任何 PG 内核补丁。可以在原生的 PostgreSQL 14/15/16/17 上直接安装使用。


一、压缩功能如何独立实现?

1. 自定义数据类型 — 标准CREATE TYPE

压缩后的列值存储为一个自定义的变长类型compressed_data,完全通过标准 SQL 注册:

-- sql/pre_install/types.post.sql CREATE TYPE _timescaledb_internal.compressed_data ( INTERNALLENGTH = VARIABLE, STORAGE = EXTERNAL, -- 告诉PG:这个类型的数据存外部(TOAST里) ALIGNMENT = double, INPUT = ...compressed_data_in, -- 文字输入(用于COPY/INSERT) OUTPUT = ...compressed_data_out, -- 文字输出(用于SELECT) RECEIVE = ...compressed_data_recv, -- 二进制输入 SEND = ...compressed_data_send -- 二进制输出 );

这个自定义类型的 C 结构体就是CompressedDataHeader

// compression.h typedef struct CompressedDataHeader { char vl_len_[4]; // PG变长类型标准头部 uint8 compression_algorithm; // 算法标记 // 后面跟着算法特定的压缩数据... } CompressedDataHeader;

完全在 PG 标准的变长类型框架内工作— PG 通过vl_len_管理存储大小,通过 TOAST 机制自动处理超长数据的切分和压缩。

2. 压缩算法实现 — 纯 C 代码,不依赖 PG

所有压缩算法都在tsl/src/compression/algorithms/下独立实现:

算法实现依赖
DELTADELTAdeltadelta.c自实现的 delta-of-delta + zigzag + simple8b_rle
GORILLAgorilla.c自实现的 XOR 浮点数压缩
DICTIONARYdictionary.c利用 PG 的 hash/lookup 函数,字典编码完全自实现
ARRAYarray.c全自实现,压缩后数据交给 PG TOAST
BOOLbool_compress.c自实现的 bitmap + simple8b_rle

没有调用 PG 内核的任何压缩函数。压缩和解压都是纯数学运算,不需要 PG 提供任何特殊支持。

3. 优化器集成 — 标准 PG Hooks

TimescaleDB 通过 PG 标准的 Hook 机制注入压缩逻辑:

Hook作用
post_parse_analyze_hook检测查询是否涉及压缩表,触发后续处理
create_upper_paths_hook注入DecompressChunkPath自定义扫描路径
ProcessUtility_hook拦截 DDL 将其路由到压缩chunk

这些 Hook 都是 PG 官方文档中明确的扩展点,存在于原生 PG 中。

4. 执行器集成 — 标准 CustomScan

读取时的解压缩通过 PG 的CustomScan节点实现(exec.c):

// 这是一个标准的 CustomScan 状态节点 static CustomExecMethods decompress_chunk_state_methods = { .BeginCustomScan = decompress_chunk_begin, .ExecCustomScan = NULL, // 运行时二选一 (fifo/heap) .EndCustomScan = decompress_chunk_end, .ReScanCustomScan = decompress_chunk_rescan, .ExplainCustomScan = decompress_chunk_explain, };

子计划扫描压缩chunk的表,DecompressChunk节点对每个返回的压缩行(batch)解压,输出解压后的行给上层节点。


二、列存 (Hypercore) 功能

Hypercore 是 TimescaleDB 的列存引擎,同样不需要 PG 内核修改

通过 PG 的 Table Access Method API 实现

标准 SQL 注册:

-- sql/pre_install/tam.sql CREATE ACCESS METHOD hypercore TYPE TABLE HANDLER ts_hypercore_handler;

C 代码中注册标准TableAmRoutine

// tsl/src/hypercore/hypercore_handler.c static const TableAmRoutine hypercore_methods = { .type = T_TableAmRoutine, .scan_begin = hypercore_beginscan, .scan_end = hypercore_endscan, .tuple_insert = hypercore_tuple_insert, .multi_insert = hypercore_multi_insert, .tuple_delete = hypercore_tuple_delete, .tuple_update = hypercore_tuple_update, .relation_vacuum = hypercore_vacuum_rel, // ... 所有标准 TAM 回调 ... };

Table Access Method 是 PG 12+ 引入的标准 API,用于实现可插拔存储引擎。Hypercore 完全通过这个接口工作:

  • ALTER TABLE chunk SET ACCESS METHOD hypercore将 chunk 切换到列存引擎
  • 检测方式:rel->rd_tableam == hypercore_routine()— 比较函数指针,标准做法
  • 不需要修改 PG 任何内核代码

Hypercore 的列存优势

相比传统行存压缩:

  • 列级向量化谓词评估— 通过 Arrow C Data Interface 批量处理列数据
  • 更高效的批量解压— 按列解压,只解压查询需要的列
  • 透明的 DML 支持— 压缩/未压缩数据在同一 chunk 中共存

三、唯一的"非标准"要求:shared_preload_libraries

TimescaleDB 需要shared_preload_libraries = 'timescaledb'

# postgresql.conf shared_preload_libraries = 'timescaledb'

这是因为 TimescaleDB 的Loader 组件必须在 PG 启动时加载:

  • 设置post_parse_analyze_hook以拦截所有查询
  • 初始化后台 worker 调度器(用于自动压缩策略)
  • 初始化共享内存

这是 PG 扩展生态中非常常见的做法(如pg_stat_statementspg_cronpostgis都类似),不是内核修改,只是配置要求。


总结

┌─────────────────────────────────────────────────────────────────┐ │ TimescaleDB 压缩/列存 = 纯 PG 扩展 │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ PG 内核层 TimescaleDB 层 │ │ ┌──────────┐ ┌──────────────────────────────┐ │ │ │ CREATE │──────────│ compressed_data 自定义类型 │ │ │ │ TYPE │ │ (变长类型,TOAST管理存储) │ │ │ ├──────────┤ ├──────────────────────────────┤ │ │ │ CREATE │──────────│ hypercore Table Access Method │ │ │ │ ACCESS │ │ (标准 TAM 接口实现) │ │ │ │ METHOD │ ├──────────────────────────────┤ │ │ ├──────────┤ │ 压缩算法 (纯C实现,无PG依赖) │ │ │ │ Custom │──────────│ • DELTADELTA (整数类型) │ │ │ │ Scan │ │ • GORILLA (浮点类型) │ │ │ │ 节点 │ │ • DICTIONARY (文本类型) │ │ │ ├──────────┤ │ • BOOL (布尔类型) │ │ │ │ Planner │──────────│ • ARRAY (fallback) │ │ │ │ Hooks │ ├──────────────────────────────┤ │ │ ├──────────┤ │ DecompressChunk (CustomScan) │ │ │ │ standard │ │ 谓词下推到 min/max 元数据列 │ │ │ │ PG APIs │ │ Batch 解压 → 逐行输出 │ │ │ └──────────┘ └──────────────────────────────┘ │ │ │ │ 需要: shared_preload_libraries = 'timescaledb' │ │ 不需要: 任何 PG 内核补丁 │ └─────────────────────────────────────────────────────────────────┘

核心结论:TimescaleDB 的压缩和列存是对 PG 扩展 API 的深度利用,体现了 PG 扩展框架的强大能力,但完全不需要修改 PG 内核源码。同样的技术栈也用于其他 PG 扩展(如 Citus 的分布式查询、pg_partman 的分区管理)。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/30 15:47:57

【一线工程实操:晶振内部污染物全流程检测】

晶振内部的污染物是很多设备“莫名掉频”“间歇性不起振”的隐形元凶,它不像外观破损那样一眼就能发现,很多时候要等设备在现场出了故障,才会追溯到是内部残留的碎屑、水汽或者腐蚀物在作怪。不同于网上零散的检测技巧,我们从一线…

作者头像 李华
网站建设 2026/6/30 15:47:48

Logisim实战:从零构建32位MIPS ALU运算器

1. 认识32位MIPS ALU运算器 第一次接触计算机组成原理实验时,我对ALU(算术逻辑单元)这个概念既好奇又困惑。直到在Logisim上亲手搭建了一个32位MIPS ALU运算器,才真正理解了CPU核心部件的工作原理。简单来说,ALU就是C…

作者头像 李华
网站建设 2026/6/30 15:41:01

Claude Code 会话续写与分叉,一条时间线,两种命运

今天这张 Session continuity 图,其实把 Claude Code 的会话机制讲得很直观。上半部分是一条绿色时间线,标着 session-abc123,右边的命令是 claude --continue 和 claude --resume。箭头不是指向一条新线,而是回到原来的时间线,这说明继续会话时,新消息会追加到同一个 se…

作者头像 李华
网站建设 2026/6/30 15:38:13

从零到一:使用Wix为WPF应用打造专业Windows安装包

1. 为什么选择Wix打包WPF应用? 第一次接触WPF应用打包时,我试过各种打包工具,从Visual Studio自带的InstallShield Limited Edition到第三方的Advanced Installer,最后发现Wix才是Windows平台打包的终极解决方案。Wix作为微软官方…

作者头像 李华