TDengine 数据压缩机制深度解析:存储压缩、有损压缩与传输压缩的实现原理
2026/9/14 17:31:06 网站建设 项目流程

TDengine 数据压缩机制深度解析:存储压缩、有损压缩与传输压缩的实现原理

【免费下载链接】TDengineHigh-performance, scalable time-series database designed for Industrial IoT (IIoT) scenarios项目地址: https://gitcode.com/GitHub_Trending/tde/TDengine

TDengine 作为面向工业物联网(IIoT)的高性能时序数据库,在数据的存储与传输两个环节都内建了压缩技术,用以降低存储成本、减少网络带宽消耗。本文基于官方文档 数据压缩 展开,并结合 压缩核心实现、压缩接口定义、运行时加速压缩模块 与 compressBench 微基准工具 等源码,完整讲清 TDengine 的两级存储压缩体系(含各数据类型的一级编码算法)、可插拔硬件加速二级压缩、TSZ 有损压缩以及compressMsgSize传输压缩配置,帮助读者在理解压缩原理的同时掌握编译开关、环境变量与配置参数的实操方法。

为什么时序数据适合高压缩比

TDengine 在存储架构上采用列式存储:在存储介质中数据以列为单位连续存储,这与以行为单位的行式存储不同。列式存储与时序数据的特性天然契合——同一列的值类型相同、变化平稳,是压缩算法最愿意“讨好”的输入。

为进一步挖掘冗余,TDengine 还采用差值编码:不直接存储原始值,而是存储相邻数据点之间的差异。对平稳变化的时序序列(如温度、水位、转速),差值往往远小于原始值,信息量大幅缩减。差值编码之后,再叠加通用压缩算法做二次压缩,两级配合达成高压缩率。对设备采集的稳定时序数据,压缩后体积通常可控制在原始数据的 10% 以内。

从源码结构看,这套“差值/重编码 + 通用压缩”的两段式设计落实在 source/util/src/tcompression.c 中:文件头部的算法说明注释明确给出了各类型的编码策略,且压缩流程通过一张“算法字典表”(compressL1Dict[]/compressL2Dict[])统一分发,为后文的可插拔硬件加速能力奠定了基础。

压缩标记的位域编码

include/util/tcompression.h 定义了两套压缩标记编码,是理解整个压缩体系的关键:

  • 32 位标记格式:高 8 位为一级压缩算法(L1)、中间 16 位为二级压缩算法(L2)、低 8 位为压缩等级(level);
  • 8 位标记格式(compressMsgSize相关的旧版标志):低 3 位 L1、中 3 位 L2、高 2 位 level。

此外头文件还定义了压缩模式常量NO_COMPRESSION = 0ONE_STAGE_COMP = 1TWO_STAGE_COMP = 2,以及 zigzag 编解码宏 ZIGZAG_ENCODE / ZIGZAG_DECODE。存储层在 tdataformat.c 中依据这些模式分派:TWO_STAGE_COMP时先做 L1 再走 L2,且非压缩数据需要额外预留COMP_OVERFLOW_BYTES(2 字节)标记空间。

一级压缩:按数据类型定制的重编码

时序数据自设备采集后遵循 TDengine 的数据建模规则:每台采集设备构建为一张子表,该设备产生的所有时序数据记录在同一张子表中。数据存储以块(SMA/数据块)为单位分块进行,每个数据块只包含一张子表的数据;压缩同样以块为单位,对子表中的每一列分别压缩,压缩后的数据仍按块落盘。

时序数据的平稳性是其主要特征之一——采集的大气温度、水温等通常在一定范围内波动。利用这一特性,TDengine 按数据类型选择编码方式。各类型的一级压缩策略与源码实现一一对应:

数据类型编码策略源码实现
时间戳只记录相邻时间点的差值(采集频率固定、差值小)DELTAItsCompressTimestampImp2
布尔位打包,1 bit 表示一个布尔值,1 字节存 8 个;另有 RLE 模式BIT-PACKINGtsCompressBoolImp2
数值(tinyint/smallint/int/bigint)差值 + zigzag 编码 + simple 8B 变长编码SIMPLE-8BtsCompressINTImp2
浮点 float/doubledelta-delta(XOR 相邻值,按前导零/尾随零数量选择存储字节方向)DELTADtsCompressFloat/DoubleImp2
字符串字典/字典压缩思想,用短标识替换高频长串tsCompressString*系列

L1 算法字典在 tcompression.c 第 285-289 行 中登记为五个具名编码器:

{"PLAIN", NULL, tsCompressPlain2, tsDecompressPlain2}, {"SIMPLE-8B", NULL, tsCompressINTImp2, tsDecompressINTImp2}, {"DELTAI", NULL, tsCompressTimestampImp2, tsDecompressTimestampImp2}, {"BIT-PACKING", NULL, tsCompressBoolImp2, tsDecompressBoolImp2}, {"DELTAD", NULL, tsCompressDoubleImp2, tsDecompressDoubleImp2},

这与 include/util/tcompression.h 中TCmprL1Type枚举(L1_SIMPLE_8BL1_XORL1_RLEL1_DELTADL1_BSS等)共同构成一级压缩的分发体系。几个值得注意的实现细节:

整数与时间戳:先算差值,再用 zigzag 编码把有符号差值映射为无符号值(把补码最高位移到低位,负数除符号位外其余位取反),使有效数据位集中、前导零增多;随后用 simple 8B 变长编码(每段数据用 selector 声明“每值占用位数”,如 {0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 15, 20, 30, 60} bit)。tcompression.c 的算法注释 还指出一个边界:bigint 实际只用 59 位,允许的数据范围为 -(2^59) 到 (2^59)-1。

布尔:提供两种方法——直接 1 bit 编码(压缩率 1/8),以及适合连续同值较多的 RLE 游程编码。

浮点数:采用与 Akumuli 相同的 delta-delta 方法:对相邻浮点值做 XOR 后,比较前导零与尾随零的数量,若前导零更多则记录 XOR 结果的末尾若干字节,否则记录开头的对应字节。这一技巧依赖浮点数“小数部分变化小、高位稳定”的平稳特性。

字符串:源码注释注明字符串一级压缩走 LZ4(见 tcompression.c 第 38-39 行),同时保留了字典式压缩的接口。

此外,tcompression.h 还暴露了tsDecompressFloatImpAvx2tsDecompressDoubleImpAvx2tsDecodeDoubleBssAvx2tsDecompressTimestampAvx512等 SIMD 加速解码入口,说明解压路径同样做了向量化优化(实现见 tdecompressavx.c)。

二级压缩:通用压缩算法与等级选择

一级压缩专注于利用数据类型特性的“局部精简”之后,TDengine 再把结果视为无差别的二进制数据,用通用压缩算法做二次压缩。二级压缩的侧重点在于消除数据块内部编码后仍残留的信息冗余——例如大量相同的游程、重复的零字节。两级相辅相成,共同实现高压缩率。

从源码看,二级压缩的分发表 compressL2Dict[] 登记了五种 codec:

  • lz4:高吞吐、低延迟,压缩率较低;
  • zlib:经典的 deflate 压缩;
  • zstd:可配等级,压缩率与速度均衡性好;
  • tsz:浮点有损压缩(下文详述);
  • xz:基于 fast-lzma2 实现的更高压缩率方案。

每个 codec 支持低/中/高三档等级,等级映射表 compressL2LevelDict 给出了具体取值,例如 zstd 的三档对应 level 1 / 11 / 22,xz 对应 1 / 6 / 9。用户可以在压缩率与写入速度之间按场景权衡选择。压缩与解压的实际调用路径位于 tcompression.c 的压缩分派宏:按 L1 类型解码/编码后,若为TWO_STAGE_COMP再经compressL2Dict[l2].comprFn执行二级压缩,并输出 trace 日志记录所用算法与等级。

使用硬件加速的二级压缩库(可选)

二级压缩默认链接打包在 TDengine 中的静态 zlib/zstd/lz4。如果部署环境提供了硬件加速的 ABI 兼容替代品(例如 Intel QAT/IAA 加速版 zlib、ISA-L 的 libz、ARM 上经过 SVE 优化的 zstd 等),可以让 taosd 在启动时把这些替代品挂入二级压缩 dispatch table,从而在不修改 SQL 的前提下获得加速;如果替代品不可用,自动回退到打包的静态实现。

该能力仅在 Linux 平台、并且编译时显式开启BUILD_WITH_ACCEL_COMPRESS时生效(开关定义见 cmake/options.cmake,默认 OFF)。编译方式:

# 编译 mkdir build && cd build cmake -DBUILD_WITH_ACCEL_COMPRESS=ON .. make -j$(nproc)

启动时通过环境变量告诉 taosd 从哪里加载替代库:

环境变量取值说明
TAOS_COMPRESS_ACCEL目录路径 / 不设置设为目录则按惯例从<dir>/libz.so<dir>/libzstd.so<dir>/liblz4.so加载;不设置(或设为空)时使用内置实现
TAOS_COMPRESS_ACCEL_ZLIB.so完整路径单独覆盖 zlib 路径,优先于TAOS_COMPRESS_ACCEL
TAOS_COMPRESS_ACCEL_ZSTD.so完整路径同上,覆盖 zstd
TAOS_COMPRESS_ACCEL_LZ4.so完整路径同上,覆盖 lz4

这一机制的完整实现在 source/util/src/tcompression_accel.c:启动时tcompressionAccelInit()在 tsCompressInit 内被无条件调用(未开启编译开关时该文件编译为 no-op 桩,调用方无需特判),随后对每个 codec 依次dlopen动态库、dlsym取出函数指针,最后把compressL2Dict[L2_ZLIB/ZSTD/LZ4].comprFn/decomprFn替换为加速版包装函数(如 accelCompress_zlib)。

符号约定:替代库必须导出与上游一致的公共符号,TDengine 启动时会dlsym它们:

  • libz:compress2uncompress
  • libzstd:ZSTD_compressZSTD_decompress
  • liblz4:LZ4_compress_defaultLZ4_decompress_safe

ABI 必须与上游相同(参数顺序、返回值语义)。绝大多数硬件加速版本都是 drop-in 替换,无需关心。

失败回退:任一步失败(环境变量未设、文件不存在、dlopen失败、缺符号),都不会中断 taosd 启动;该 codec 继续使用静态打包的实现,并在日志中输出UTL WARN accel <codec>: ...(对应源码中的 uWarn 回退分支)。

确认加载成功:taosd 启动日志会显示一行类似:

UTL INFO accel zlib: loaded from /opt/qat-zlib/libz.so, L2_ZLIB dispatch patched UTL INFO accel zstd: loaded from /opt/qat-zlib/libzstd.so, L2_ZSTD dispatch patched

如果完全没设环境变量,则会看到:

UTL INFO accel compression: TAOS_COMPRESS_ACCEL{,_ZLIB,_ZSTD,_LZ4} unset; using stock L2 implementations

评估加速效果:与BUILD_TOOLS=ON一起编译会得到compressBench,可以直接对二级压缩 dispatch table 做微基准(绕开 SQL、网络、WAL,结果只反映压缩本身):

# Stock 基线(不设 TAOS_COMPRESS_ACCEL) ./build/bin/compressBench --codec all --size 1 --iters 30 --warmup 5 \ --shape mixed --label stock --csv result.csv # 切换到加速库再跑一次,对比同 codec 的 throughput export TAOS_COMPRESS_ACCEL=/opt/qat-zlib ./build/bin/compressBench --codec all --size 1 --iters 30 --warmup 5 \ --shape mixed --label accel --csv result.csv

compressBench.c 的实现对这些参数给出了准确含义:

  • 工具直接调用compressL2Dict的函数指针进行压/解压计时,因此测得的就是 taosd 运行时实际使用的那份 dispatch table(stock 或 accel);
  • --shape提供random / repeating / sequential / mixed四种数据形态:随机高熵数据、64 字节重复模式、单调递增的 1 毫秒间隔纳秒时间戳、以及三者等量混合,可分别评估高熵、低熵、单调时间戳和混合负载下的表现;
  • --size支持 0.004(4 KiB)、0.0625(64 KiB)、1(1 MiB)等 MiB 计值,覆盖真实列块的常见尺寸;
  • 输出每个 codec 的 mean / p50 / p95 / stdev 以及 MB/s 吞吐和压缩率,CSV 行尾标注backend=stock|accel(工具通过比较函数指针是否等于静态实现来判定后端)以便对照。建议至少跑 30 轮 measure + 5 轮 warmup,并在不同空闲时段重复两次以排除瞬时干扰。

注意事项

  • 替代库会被dlopen一次并常驻整个 taosd 生命周期,因此磁盘上别在运行期间替换或删除该文件;
  • 如果替代库本身又依赖其他动态库(例如 QAT 用户态驱动),需要确保它们也能被dlopen找到——常规手段(/etc/ld.so.conf.d/LD_LIBRARY_PATH)都适用;
  • TSZ(浮点有损压缩)和 XZ 不在替换范围内:前者是 TDengine 内部实现,后者使用的是 fast-lzma2 而非主流 xz,没有通用的 drop-in 加速版。

有损压缩:TSZ

TDengine 引擎为浮点数类型数据提供了无损压缩和有损压缩两种模式。浮点数的精度通常由其小数点后的位数决定。在某些情况下,设备采集的浮点数精度较高,但实际应用中关注的精度却较低,此时采用有损压缩可以有效地节约存储空间。

TSZ(TDengine SZ)的算法基于预测模型,核心思想是利用前序数据点的趋势来预测后续数据点的走势,按用户声明的误差区间量化编码,压缩率显著高于无损模式。相关实现位于 contrib/TSZ(含sz/zstd/子模块及 CMakeLists.txt),引擎侧入口为 tcompression.h 中的 TSZ 接口:

  • tsCompressInit(char *lossyColumns, float fPrecision, double dPrecision, uint32_t maxIntervals, uint32_t intervals, int32_t ifAdtFse, const char *compressor):按列名启用 float/double 有损模式,并传入误差精度、最大区间数等参数,内部调用tdszInit(见 tsCompressInit 实现);
  • tsCompressFloatLossyImp / tsCompressDoubleLossyImp及其解压函数:有损编解码的具体入口;
  • 二级压缩字典中tsz独立占一个 slot(compressL2Dict 中{"tsz", ...},等级映射为 1 / 2 / 3),与无损 L2 并列,说明有损压缩也是以“二级压缩”的形式挂入同一套分发框架的。

启用有损压缩需要用户在建模/建库时显式指定哪些浮点列允许有损及精度参数;对精度敏感的场景应坚持默认的无损模式。

传输压缩:compressMsgSize 与 REST/WebSocket

TDengine 在数据传输过程中同样提供压缩,以减少网络带宽消耗。使用原生连接(如 taosc)向服务器传输数据时,可通过配置文件 taos.cfg 中的compressMsgSize选项开启压缩传输,可配置的值含义如下:

  • 0:所有数据包都压缩(原文档表述为“对所有数据包进行压缩”);
  • -1:禁用压缩传输;
  • 其他正值:仅对大于该阈值(字节)的消息体压缩。

配置项在 packaging/cfg/taos.cfg 中默认注释为# compressMsgSize -1,服务端解析逻辑见 source/common/src/tglobal.c:

/* * 0: all data are compressed * -1: all data are not compressed * other values: if the message payload size is greater than the tsCompressMsgSize, the message will be compressed. */ int32_t tsCompressMsgSize = -1;

即默认值-1表示不做消息压缩。从源码注释看,该开关同时作用于“客户端向服务端提交(写入)消息”与“服务端向客户端返回查询结果、metricmeta、多表查询响应”两个方向;阈值比较发生在 tglobal.c 的配置注册处(取值范围 -1 至 100000000,客户端与服务端两侧均生效,CFG_SCOPE_BOTH)。另外,传输层的性能测试工具 cliBench.c 与 svrBench.c 也直接暴露了tsCompressMsgSize命令行参数,可用于评估不同压缩阈值下的压测表现。

在使用 RESTful 和 WebSocket 连接与 taosAdapter 通信时,taosAdapter 支持行业标准的压缩协议,允许连接端按标准协议开关传输压缩:

  • RESTful 接口:客户端在 HTTP 请求头中指定Accept-Encoding告知服务器可接受的压缩类型(如 gzip、deflate);服务器返回结果时在Content-Encoding头中注明所用压缩算法并返回压缩后的数据;
  • WebSocket 接口:遵循 WebSocket 压缩协议标准 RFC7692(permessage-deflate),连接端可按该协议开启/关闭压缩;
  • 数据备份迁移工具 taosX:taosX 与 taosX Agent 之间的通信也可以开启压缩传输,在agent.toml配置文件中设置compression=true即可启用。

压缩全流程

下图展示了 TDengine 引擎在时序数据的整个传输及存储过程中的压缩及解压过程:写入路径上,数据先按列块执行一级重编码,再经二级通用压缩落盘;查询路径则反向解压;客户端与服务端之间的原生协议消息按compressMsgSize策略做消息级压缩。

小结

  • TDengine 的存储压缩是“列式存储 + 差值/类型定制重编码(一级)+ 通用压缩(二级)”的组合:整数与时间戳走差值+zigzag+simple8B,布尔走位打包/RLE,浮点走 delta-delta,字符串走字典/LZ4,二级则可选 LZ4/ZLIB/ZSTD/XZ 及有损 TSZ,各算法在 tcompression.h 与 tcompression.c 中都有清晰的类型化接口;
  • 编译开启BUILD_WITH_ACCEL_COMPRESS后,可通过TAOS_COMPRESS_ACCEL系列环境变量在运行时把硬件加速的 zlib/zstd/lz4 挂入 dispatch table,失败自动回退,并可用compressBench量化加速收益(tcompression_accel.c、compressBench.c);
  • 传输侧用taos.cfgcompressMsgSize控制原生协议消息压缩阈值(默认 -1 关闭),REST/WebSocket 走标准 HTTP 压缩头与 RFC7692,taosX 通过agent.tomlcompression=true开启;
  • 对精度要求宽松的浮点列,可评估 TSZ 有损压缩以获得更高压缩率,同时用无损模式兜底关键列。

按上述源码路径(压缩接口、压缩实现、AVX 解码、TSZ 模块、压缩微基准、配置示例)可继续深入 TDengine 压缩体系的具体实现细节。

【免费下载链接】TDengineHigh-performance, scalable time-series database designed for Industrial IoT (IIoT) scenarios项目地址: https://gitcode.com/GitHub_Trending/tde/TDengine

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询