压缩效率是衡量时序数据库存储能力的关键指标。关系型数据库存储1TB的工业时序数据,时序数据库在同等数据量下通常只需100-150GB。10倍压缩比不是营销数字,而是由时序数据的内在特征和专用压缩算法共同决定的工程结果。理解这10倍压缩比从何而来,需要从数据特征、压缩算法、存储格式三个层面逐层拆解。
时序数据的三个压缩前提
压缩算法的效率取决于数据的冗余度。时序数据在三个维度上天然具有高冗余性,这是专用压缩算法发挥作用的前提。
时间戳单调递增。时序数据按时间顺序写入,每条数据的时间戳严格递增(或递减)。相邻数据的时间戳差异通常在一个固定范围内——如果采样周期是1秒,相邻时间戳差值为1000毫秒。这种规律性使得时间戳可以被极高比例压缩。
相邻值变化小。工业物理量具有连续性——温度不会在1秒内从25°C跳到200°C再回到25°C。相邻采样点的数值差异通常远小于数值本身。一个反应釜温度从75.3°C变化到75.4°C,绝对值差异0.1,相对变化仅0.13%。这种小变化特性使得差值编码极为高效。
状态量重复多。工业设备的状态参数(开关状态、运行/停机、故障标志)在长时间区间内保持不变。一个断路器一天分合2次,但每秒采样一次状态值,86400条数据中只有2条真正变化,其余86398条完全重复。Run-Length Encoding(RLE)对这种数据可实现1000:1以上的压缩。
通用压缩算法的局限
通用压缩算法(LZ4、Snappy、Zstd、Gzip)设计目标是处理任意类型数据,对时序数据的特定冗余模式不够敏感。
LZ4和Snappy追求速度而非压缩比。它们的算法基础是LZ77滑动窗口——查找重复字节序列并替换为指针。对时序数据中的浮点数序列,字节层面的重复模式不明显(75.3和75.4在IEEE 754浮点表示中字节差异可能很大),LZ77难以捕获。实际测试中,LZ4对时序数据的压缩比通常在1.5-3倍。
Zstd在通用压缩中表现较好,压缩比可达3-5倍。但Zstd的核心算法仍然是LZ77+Huffman/ANS编码,对时序数据的时间戳规律性和浮点数趋势性没有专门优化。更关键的是,通用压缩以数据块为单位处理,无法利用列式存储中同列数据的连续性。
通用压缩算法在时序场景的根本局限在于:它们不理解数据的语义。时间戳列、浮点数列、整数状态列有完全不同的冗余模式,通用算法一视同仁地处理,无法发挥列级压缩的优势。
时序专用压缩算法
时序数据存储系统采用了多种面向特定数据类型的专用压缩算法,这些算法针对时序数据的冗余特征量身设计。
Delta-of-Delta编码 用于时间戳列。算法步骤:第一步,计算相邻时间戳的差值(Delta)——如果采样周期1秒,差值为1000ms。第二步,计算差值的变化量(Delta-of-Delta)——如果采样周期稳定,变化量为0。编码规则:Delta-of-Delta为0时用1个bit编码;在[-63,63]范围内用2个bit+7个bit编码;在[-255,255]范围内用3个bit+9个bit编码;以此类推。稳定采样的时间戳序列,绝大多数Delta-of-Delta为0,编码后每条数据的时间戳仅占1个bit,压缩效率极高。
Run-Length Encoding(RLE) 用于状态量列。连续相同的值编码为(值,重复次数)。断路器状态序列[CLOSED, CLOSED, …, CLOSED(86398次), OPEN, OPEN, OPEN, CLOSED, …, CLOSED]编码为[(CLOSED, 86398), (OPEN, 3), (CLOSED, …)]。完全重复的序列压缩比可达数千倍。
XOR编码 用于浮点数列。相邻浮点数的IEEE 754表示中,高位字节通常相同(符号位、指数位、尾数高位),只有低位字节变化。XOR编码计算相邻值的异或,然后对异或结果的前导零和尾随零进行紧凑编码。75.3和75.4的IEEE 754双精度浮点异或结果可能只有低8位非零,编码后从64bit压缩到约10-12bit。
Swinging Door算法 用于浮点数趋势压缩。该算法由 OSIsoft PI System提出,核心思想是:在一个数据窗口内,如果第一个点和最后一个点确定的线性趋势能够代表窗口内所有点(偏差在容差范围内),则只保留首尾两点,丢弃中间所有点。对于缓慢变化的温度数据(如环境温度从22.0°C用1小时升到24.0°C,每秒采样一次共3600个点),Swinging Door可能只保留20-30个趋势代表点,压缩比超过100:1。
压缩算法 | 适用数据类型 | 压缩比范围 | CPU开销 | 算法复杂度 |
Delta-of-Delta | 时间戳列 | 10-50倍 | 极低 | O(n) |
RLE | 整数状态列 | 50-1000倍 | 极低 | O(n) |
XOR | 浮点数列 | 3-8倍 | 低 | O(n) |
Swinging Door | 浮点趋势列 | 10-100倍 | 中 | O(n) |
LZ4 | 通用数据块 | 1.5-3倍 | 极低 | O(n) |
Zstd | 通用数据块 | 3-5倍 | 中 | O(n) |
列式存储的压缩乘数效应
专用压缩算法在列式存储格式下产生乘数效应。行式存储中,一条记录的所有字段连续排列——时间戳、温度值、压力值、状态值交错存储。LZ77类算法在交错数据中难以发现同类型数据的重复模式。列式存储将同列数据连续排列——所有时间戳在一起、所有温度值在一起、所有压力值在一起。每列数据类型一致,冗余模式一致,专用压缩算法的效率最大化。
列式存储还带来另一个压缩优势:同列数据的取值范围通常远小于全表所有字段的取值范围。温度列取值范围-50到200,用8位整数或16位浮点即可表示,不需要64位双精度浮点的完整范围。列级类型推断可以进一步压缩存储——如果一列的所有值都是整数,即使定义为浮点类型,存储时也可以用整数格式编码。
多级压缩是时序数据库的常见策略。第一级,列级专用压缩:时间戳用Delta-of-Delta,状态量用RLE,浮点数用XOR。第二级,块级通用压缩:对列级压缩后的数据块用Zstd或LZ4再做一次通用压缩,消除残余冗余。第三级,文件级压缩:对存储文件整体做压缩。TDengine默认采用列级压缩+块级压缩的两级策略,综合压缩比7-10倍。
工业场景的压缩效果差异
不同工业数据类型的压缩效果差异显著,取决于数据的物理特征。
温度数据是压缩效果最好的类型。反应釜温度采样周期1秒,数值从75.0°C缓慢变化到76.0°C用10分钟,每秒0.0017°C变化。Delta编码后差值序列中大量重复的小值,XOR编码进一步压缩,Swinging Door在趋势层面再砍一个数量级。综合压缩比可达15-20倍。
振动数据压缩效果最差。振动信号(加速度、速度)频率高、幅值变化大,相邻采样点可能从+5g跳到-5g。XOR编码后异或结果非零位多,压缩效率低。振动数据的压缩主要依赖列式存储的同类型连续性,压缩比通常3-5倍。这也是振动数据存储成本高的技术原因——压缩不掉就得硬存。
状态量压缩比最高。开关状态、运行/停机标志、故障代码等离散量,RLE编码后压缩比可达50-100倍。一个10000条全为"CLOSED"的状态序列,RLE编码后仅占几十字节。
电流/电压等电气量居中。正常运行时三相电流幅值稳定,变化小,XOR编码压缩比8-12倍。故障期间电流剧烈波动,压缩比降到2-3倍。整体压缩比6-8倍。
压缩对系统性能的双向影响
压缩不是免费的。压缩比越高,CPU开销越大,查询时需要解压的代价也越大。
写入路径上,压缩CPU开销增加写入延迟。Delta-of-Delta和RLE算法本身简单(位运算为主),CPU开销可忽略。XOR编码稍重但仍是O(n)复杂度。Swinging Door算法需要维护趋势窗口状态,CPU开销中等。整体而言,写入路径的压缩CPU开销在可接受范围内,不会成为写入瓶颈。
查询路径上,解压开销可能成为瓶颈。范围查询需要读取并解压大量数据块。如果查询涉及大时间范围的全量数据,解压CPU时间和I/O时间的比值需要平衡。TDengine的处理策略是:标签列和索引列不压缩或轻度压缩(保证过滤速度),数据列重度压缩(节省存储)。查询时先通过标签过滤和时间分区裁剪减少需要解压的数据量,再对剩余数据块解压。
写入吞吐↑、存储成本↓、CPU开销↑、查询延迟可能↑——压缩在这四个维度上的权衡是存储引擎工程调优的核心决策点。大多数工业场景中,存储成本和写入吞吐是主要矛盾,重度压缩的收益大于CPU开销的代价。但实时性要求极高的场景(如电力保护动作分析,需要毫秒级查询响应),可能需要降低压缩级别或对热数据不压缩。
结语
时序数据库的高压缩比不是单一算法的功劳,而是数据特征、专用算法、列式存储三者协同的结果。时间戳的规律性使Delta-of-Delta成为可能,状态量的重复性使RLE大放异彩,浮点数的小变化特性使XOR编码有效,列式存储将这些算法的效率叠加放大。理解压缩机制的价值不仅在于解释存储成本,更在于指导工程决策——数据类型决定压缩策略,压缩策略影响写入和查询性能调优方向。未来方向在于自适应压缩:根据数据特征动态选择压缩算法,对缓慢变化数据用Swinging Door,对剧烈变化数据用XOR,对完全重复数据用RLE,实现每列数据的最优压缩。

























