news 2026/9/13 13:40:03

TDengine 数据压缩机制深度解析:从列式存储、一级/二级压缩到硬件加速与传输压缩

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
TDengine 数据压缩机制深度解析:从列式存储、一级/二级压缩到硬件加速与传输压缩

TDengine 数据压缩机制深度解析:从列式存储、一级/二级压缩到硬件加速与传输压缩

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

时序数据天然具有“量大、规律波动、按时间连续采集”的特点,如何在存储与传输两个环节压缩数据,直接决定了时序数据库的磁盘成本与网络带宽占用。本篇基于 TDengine 官方内部机制文档,结合仓库源码(tcompression.c、tcompression_accel.c、compressBench)展开,系统讲解 TDengine 的列式存储与差分编码、按数据类型的“一级压缩”、面向整体冗余消除的“二级压缩”、基于预测模型的 TSZ 有损压缩,以及网络传输压缩的完整配置方式。读完本文,你将能够解释 TDengine 高压缩率从何而来,并掌握compressMsgSizeTAOS_COMPRESS_ACCEL系列环境变量等实战配置。

压缩的总体思路:存储与传输双管齐下

数据压缩是一种在不丢失有效信息的前提下,利用特定算法对数据重新组织与处理的技术,其目标是减少数据占用的存储空间并提升传输效率。TDengine 在存储过程传输过程两个环节都应用了压缩技术:存储压缩降低磁盘占用、加速数据读写;传输压缩降低网络带宽消耗、加快客户端与服务器之间的数据交换。

从整体数据链路看,TDengine 的压缩是分阶段、分层级的:

  • 数据写入时按时间有序落盘前,先完成行转列(Rows to Columns),再对每一列独立压缩(列压缩),最后以压缩块形式写入磁盘;
  • 查询读取时,从磁盘读出压缩块后先解压为列,再在内存中转为行参与过滤与计算;
  • 客户端与服务器之间传输时,可对消息体启用传输压缩,减少带宽占用。

存储压缩:列式存储 + 差分编码的组合拳

列式存储是压缩的基础

TDengine 在存储架构上采用列式存储技术,即数据在存储介质中按列连续存放,这与传统行式存储(按行连续存放)截然不同。列式存储结合时序数据“稳定变化”的特点,天然适合处理此类数据:同一列的相邻值往往非常接近,为差分编码和后续通用压缩提供了极佳的熵环境。

差分编码:不存原始值,只存差值

为进一步提升存储效率,TDengine 使用差分编码技术。该技术不直接存储原始值,而是计算相邻数据点之间的差值并存储差值。由于时序数据相邻采样值通常差异很小,差值本身所需的信息量远小于原始值,因而能显著压缩存储所需空间。差分编码之后,TDengine 还会叠加通用压缩技术做进一步压缩,从而获得更高的压缩率。

对于设备采集的稳定时序数据(如持续监测的工业指标),TDengine 的压缩效果尤为显著:压缩率通常在 10% 以内,部分场景甚至更高。高效的压缩为用户节省了大量存储成本,同时提升了数据存储与访问效率。

注:文档中“压缩率 10% 以内”指压缩后数据量约为原始数据的 10% 以下,属于该机制文档中的说明性结论。

一级压缩(Primary Compression):按数据类型的专用编码

以块为单位、按列独立压缩

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

时序数据的稳定性是其核心特征之一——例如采集的环境温度、水温等,通常只在一定范围内波动。利用这一特性可以对数据进行重新编码,并根据不同数据类型采用不同的编码技术,以获得最高的压缩效率。源码中一级压缩的算法分发表定义于 tcompression.c 的compressL1Dict[]

TCmprL1FnSet compressL1Dict[] = { {"PLAIN", NULL, tsCompressPlain2, tsDecompressPlain2}, {"SIMPLE-8B", NULL, tsCompressINTImp2, tsDecompressINTImp2}, {"DELTAI", NULL, tsCompressTimestampImp2, tsDecompressTimestampImp2}, {"BIT-PACKING", NULL, tsCompressBoolImp2, tsDecompressBoolImp2}, {"DELTAD", NULL, tsCompressDoubleImp2, tsDecompressDoubleImp2}, {"BYTE-STREAM_SPLIT", NULL, tsEncodeDouble, tsDecodeDouble}, };

可以看到,不同数据类型在 L1 层对应着不同的专用编码器。各类型的具体压缩方法如下。

时间戳类型:增量(Delta)编码

时间戳列通常记录设备连续采集数据的时间点,且采集频率固定。因此只需要记录相邻时间点之间的差值即可,由于差值通常很小,比直接存储原始时间戳更省空间。源码中对应tsCompressTimestampImp,其算法说明明确指出:

先计算相邻两个整数之差,再用 zig-zag 编码转换为正数,最后以 simple-8B 方式编码存储(对应compressL1Dict[]中的DELTAI条目)。

布尔类型:位打包(Bit-Packing)

布尔值只需 1 个 bit 表示,1 个字节可存放 8 个布尔值,通过紧凑编码即可显著节省空间。源码中布尔压缩提供两种方法:一是用 1 bit 表示真假(压缩率 1/8);二是采用游程编码(RLE),当出现大量连续的 true 或 false 时效果更好。对应实现为tsCompressBoolImpBIT-PACKING条目),源码注释中还保留了 RLE 的扩展思路(tsCompressBoolRLEImp)。

数值类型:zig-zag 编码统一处理

物联网设备产生的数值数据(温度、湿度、气压、车速、油耗等)通常不大且在一定范围内波动。对此类数据,TDengine 统一使用zig-zag 编码:将有符号整数映射为无符号整数,将整数补码的最高位移动到低位,负数除符号位外全部取反,正数保持不变。这样做集中了有效数据位、增加了前导零的数量,从而在后续压缩步骤中获得更好的压缩效果。

源码 tcompression.c 中通过宏实现这一映射:

#define ZIGZAG_ENCODE(T, v) (((u##T)((v) >> (sizeof(T) * 8 - 1))) ^ (((u##T)(v)) << 1)) // zigzag encode #define ZIGZAG_DECODE(T, v) (((v) >> 1) ^ -((T)((v)&1))) // zigzag decode

编码后的值再使用 simple-8B 方法编码(SIMPLE-8B条目,tsCompressINTImp)。需要说明的边界条件(来自源码注释):对于 bigint,只有 59 个 bit 可用,即取值范围限定在-(2^59)(2^59)-1之间。

浮点类型:delta-delta 编码

对于 float 和 double 两种浮点数,采用delta-delta 编码DELTAD条目)。其算法与 Akumuli 相同,前提假设是浮点值变化平缓:对相邻两个值做 XOR,比较前导零与尾随零的数量,若前导零多则记录 XOR 结果的末尾若干字节,否则记录开头若干字节,从而压缩存储。该思路的完整描述记录在 tcompression.c 的文件头注释中。

字符串类型:字典压缩

字符串类型数据使用字典压缩算法:将原始字符串中频繁出现的长字符串替换为短标识符,从而缩短存储信息长度。需要说明的是,从源码角度看字符串在 L2 层实际常走 LZ4 路径(tsCompressStringImp的注释要求输出缓冲区不小于max(input_size, LZ4_compressBound(input_size)) + 1),文档中的“字典压缩”可以理解为对字符串这类高重复度数据的通用压缩思路的概括。

二级压缩(Secondary Compression):消除块间冗余的通用压缩

完成针对特定数据类型的专用压缩(一级压缩)后,TDengine 将数据视为无差别的二进制数据,使用通用压缩技术再进行第二次压缩。与一级压缩聚焦局部数据简化不同,二级压缩的重点是消除数据块之间的信息冗余。这种“局部简化 + 整体去重”的双重压缩机制,共同实现了 TDengine 的超高压缩率。

TDengine 支持多种压缩算法:LZ4、ZLIB、ZSTD、XZ等。用户可根据具体应用场景,在压缩率与写入速度之间灵活权衡,选择最合适的压缩方案。

二级压缩的分发表与压缩级别

在 tcompression.c 中,L2 层的压缩实现按平台分发表(非 Windows/Darwin 平台):

TCmprL2FnSet compressL2Dict[] = { {"unknown", l2ComressInitImpl_disabled, l2CompressImpl_disabled, l2DecompressImpl_disabled}, {"lz4", l2ComressInitImpl_lz4, l2CompressImpl_lz4, l2DecompressImpl_lz4}, {"zlib", l2ComressInitImpl_zlib, l2CompressImpl_zlib, l2DecompressImpl_zlib}, {"zstd", l2ComressInitImpl_zstd, l2CompressImpl_zstd, l2DecompressImpl_zstd}, {"tsz", l2ComressInitImpl_tsz, l2CompressImpl_tsz, l2DecompressImpl_tsz}, {"xz", l2ComressInitImpl_xz, l2CompressImpl_xz, l2DecompressImpl_xz}, };

其中 XZ 走的是 fast-lzma2(FL2_compress/FL2_decompress)而非主流 xz 库,这一点在“硬件加速”一节会再次提及。每种算法还定义了低/中/高三档压缩级别(compressL2LevelDict):

TCmprLvlSet compressL2LevelDict[] = { {"unknown", .lvl = {1, 2, 3}}, {"lz4", .lvl = {1, 2, 3}}, {"zlib", .lvl = {1, 6, 9}}, {"zstd", .lvl = {1, 11, 22}}, {"tsz", .lvl = {1, 2, 3}}, {"xz", .lvl = {1, 6, 9}}, };

可以看到 zlib 默认三档为 1/6/9,zstd 为 1/11/22,级别越高压缩率越高、耗时越长,这正是文档所述“在压缩率与写入速度之间权衡”的底层实现。

L2 压缩的格式细节

L2 层每个压缩块首字节的低 1 位标记模式(MODE_NOCOMPRESS = 0表示未压缩、MODE_COMPRESS = 1表示已压缩),高 7 位记录算法标识(如ALGO_SZ_LOSSY = 1标记 TSZ 有损压缩)。压缩标志位中,L1/L2 算法与压缩级别被打包为 32 位或 8 位标志(参见 include/util/tcompression.h 中的COMPRESS_L1_TYPE_U32COMPRESS_L2_TYPE_U32等宏)。

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

二级压缩默认链接 TDengine 内置的静态 zlib/zstd/lz4。如果部署环境提供了ABI 兼容的硬件加速替代库(例如 Intel QAT/IAA 加速的 zlib、ISA-L 的 libz、ARM 上 SVE 优化的 zstd),taosd 可以在启动时将这些替代库加载进二级压缩分发表,从而加速压缩——无需修改任何 SQL。若替代库不可用,TDengine 会自动回退到内置静态实现。

该能力仅限 Linux,且只有在编译时显式开启BUILD_WITH_ACCEL_COMPRESS才可用(编译选项定义于 cmake/options.cmake):

# Build 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()读取环境变量后,通过dlopen打开共享库、用dlsym解析符号,再把compressL2Dict[]中对应条目的comprFn/decomprFn替换为加速包装函数(accelCompress_zlibaccelCompress_zstdaccelCompress_lz4等)。注意环境变量为空字符串也视为未设置,因此运维可以通过FOO=的方式在 unit 文件或容器镜像中“关掉”加速。

符号契约:替代库必须导出与上游一致的符号

替代库必须导出与上游相同的公共符号,TDengine 在启动时用dlsym解析:

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

ABI 必须与上游匹配(参数顺序与返回值语义一致)。大多数硬件加速构建本身就是 drop-in 替换,因此无需额外工作。

失败回退:任何一步失败都不中断启动

如果任何步骤失败(环境变量未设置、文件缺失、dlopen失败或符号缺失),taosd 启动都不会中断:该编解码器继续使用内置静态实现,并写入一行形如UTL WARN accel <codec>: ...的日志。从源码看,tryLoadZlibAccel/tryLoadZstdAccel/tryLoadLz4Acceldlopendlsym失败时只是记录uWarn并返回,dispatch 表保持原样。

确认加载成功

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
度量加速效果:使用 compressBench

编译时开启BUILD_TOOLS=ON还会产出compressBench(源码见 tools/compressBench/compressBench.c),它直接对二级压缩分发表做微基准测试——绕过 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 的吞吐 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

输出对每个 codec 报告 mean / p50 / p95 / stdev、MB/s 吞吐与压缩比,并给每行打上backend=stock|accel标签便于对比。--shape提供四种数据模式——random / repeating / sequential / mixed,分别覆盖高熵、低熵、单调时间戳与混合负载;--size可取值如0.004(4 KiB)、0.0625(64 KiB)、1(1 MiB),覆盖典型列块大小。建议至少运行 30 次测量迭代外加 5 次 warmup 迭代,并在不同空闲时段重复两遍,以排除瞬时噪声(compute_stats在源码中通过排序取分位数并计算标准差,逻辑与上述建议一一对应)。

使用注意事项
  • 替代库通过dlopen只加载一次并保持到 taosd 整个生命周期,因此taosd 运行期间不要替换或删除磁盘上的该文件
  • 如果替代库本身依赖其他共享库(例如 QAT 用户态驱动),这些库也必须能被dlopen发现——标准机制(/etc/ld.so.conf.d/LD_LIBRARY_PATH)均适用;
  • TSZ(有损浮点压缩)与 XZ 不可切换:前者是 TDengine 内部实现,后者使用 fast-lzma2 而非主流 xz,因此没有通用的 drop-in 加速替代品。

有损压缩:基于预测模型的 TSZ 算法

TDengine 引擎为浮点类型数据提供两种模式:无损压缩有损压缩。浮点数的精度通常由小数点后的位数决定。某些场景下,设备采集的浮点数精度较高,但实际应用关心的精度较低——此时使用有损压缩可有效节省存储空间。

TDengine 的有损压缩算法基于预测模型,核心思想是:利用前面数据点的趋势预测后续数据点的趋势。该算法能显著提升压缩率,压缩效果远超无损压缩,其算法名为TSZ

从源码看,TSZ 的实现与配置入口为tsCompressInit(tcompression.c),它接收lossyColumns(开启有损压缩的列)、fPrecision/dPrecision(float/double 精度)等参数并调用tdszInit初始化;lossyColumns中是否包含"float"/"double"决定了lossyFloat/lossyDouble全局开关。对应地,tglobal.c 注册了fPrecision(范围0.0 ~ 100000.0)与dPrecision(范围0.0 ~ 1000000.0)两个服务端动态配置项。TSZ 相关实现位于 contrib/TSZ 目录。

传输压缩:降低网络带宽消耗

TDengine 在数据传输过程中也提供压缩功能,以减少网络带宽消耗。传输压缩主要覆盖三类连接场景。

原生连接:compressMsgSize 配置项

使用原生连接(native connection)从客户端(如 taosc)向服务器传输数据时,可通过配置文件taos.cfg中的compressMsgSize选项开启压缩传输。可配置值如下:

  • 0:关闭压缩传输;
  • 1:开启压缩传输,但仅对大于 1KB 的数据包生效;
  • 2:对所有数据包开启压缩传输。

配置项定义与语义可在 source/common/src/tglobal.c 中确认:compressMsgSize以 Int32 注册(合法范围-1 ~ 100000000CFG_SCOPE_BOTH表示客户端与服务端均可配置),默认值tsCompressMsgSize = -1,源码注释明确说明“当消息负载大小大于tsCompressMsgSize时,消息将被压缩”。默认配置文件模板 packaging/cfg/taos.cfg 中给出了该选项的占位示例(compressMsgSize -1,即默认不启用传输压缩)。

RESTful 与 WebSocket 连接(taosAdapter)

使用 RESTful 与 WebSocket 连接与taosAdapter通信时,taosAdapter 支持行业标准压缩协议,连接端可按行业标准协议在传输过程中开启或关闭压缩:

  • RESTful 接口使用压缩:客户端在 HTTP 请求头中通过Accept-Encoding告知服务器可接受的压缩类型(如gzipdeflate等);返回结果时,服务器在Content-Encoding头中指明实际使用的压缩算法并返回压缩数据;
  • WebSocket 接口使用压缩:参照 WebSocket 协议标准文档RFC7692实现 WebSocket 连接中的压缩。

数据迁移工具 taosX 的压缩

数据备份迁移工具taosX 与 taosX Agent之间的通信同样可以开启压缩传输:在agent.toml配置文件中设置compression=true即可启用压缩。

压缩与解压的完整流程

下图展示了 TDengine 引擎在时序数据整个传输与存储过程中的压缩与解压处理流程(与本文开头配图一致),有助于从全局理解整个处理过程:

从图中可以看到两条主链路:

  • 写入链路(Server 端):按时间顺序写入 → 准备刷盘 →行转列(Rows to Columns)压缩列(Compress Columns)→ 存储到磁盘。列式存储是压缩的前提,一级(类型专用)与二级(通用)压缩在这一环节叠加完成;
  • 查询链路(Client 端):发起查询请求 → 传输到服务器 →解压为列(Decompress to Columns)→ 内存中转为行 → 过滤 → 返回结果;
  • 写入传输(Client 端):写入请求 → 按行组织 →压缩并传输(Compress and transmit),对应原生连接的compressMsgSize传输压缩。

该图在仓库中位于 docs/en/assets/compress-01.png(中文版见 docs/zh/assets/compress-01.png)。

总结:如何为你的场景选择合适的压缩策略

  • 存储压缩由引擎自动完成,无需人工干预:一级压缩按类型编码(增量/zig-zag/simple-8B/位打包/delta-delta),二级压缩在 LZ4/ZLIB/ZSTD/XZ 之间选择,侧重压缩率与写入速度的平衡;
  • 浮点精度冗余较大时,可考虑开启 TSZ 有损压缩,配合fPrecision/dPrecision控制精度损失边界,换取远超无损压缩的压缩率;
  • 传输带宽紧张时,原生连接设置compressMsgSize为 1 或 2;RESTful/WebSocket 按标准协议协商压缩;taosX 迁移在agent.toml中开启compression=true
  • 压缩成为性能瓶颈且部署在 Linux 上时,可用BUILD_WITH_ACCEL_COMPRESS=ON编译并借助TAOS_COMPRESS_ACCEL系列环境变量挂载 QAT/IAA/ISA-L 等硬件加速库,用compressBench做 stock 与 accel 的量化对比,再决定是否启用。

通过本文介绍的机制与配置,你可以依据实际负载特征(数据熵、块大小、压缩/写入速度优先级)为 TDengine 部署选择最合适的压缩方案,在存储成本与写入吞吐之间取得最优平衡。

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

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

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

FPFH点云特征描述子原理与Matlab实现实战

简介&#xff1a;基于Matlab实现的快速点特征直方图&#xff08;FPFH&#xff09;算法&#xff0c;支持2014/2019a环境运行&#xff0c;是一份面向本科、硕士阶段点云处理与三维视觉方向的教学研习资源。FPFH作为点云局部特征描述的经典方法&#xff0c;广泛用于配准、识别与分…

作者头像 李华
网站建设 2026/9/13 13:33:44

DecoHack #056: Newsletter 的产品化重构与可装配知识单元实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/13 13:32:59

工控入门四阶段实战指南:从断电接线到稳定运行

1. 工控入门到底学什么&#xff1f;——这不是选课清单&#xff0c;而是现场工程师的生存地图“工控入门到底学什么&#xff1f;”——这句话我每年在车间、调试现场、客户机房里至少被问37次。不是学生问&#xff0c;是刚转行的电气工程师、被临时拉去盯PLC项目的机械设计师、…

作者头像 李华
网站建设 2026/9/13 13:32:28

Windows上部署GitLab Runner:PowerShell与config.toml生产级实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华