TDengine 存储引擎:极速、高压缩、零冗余

TDengine 存储引擎设计文档

一、设计理念

TDengine 存储引擎专为时序数据场景设计,核心理念是:充分利用时序数据的时间有序性、连续性和高并发特点,实现极致的写入速度和超高的压缩比

核心设计目标

  • 极速写入:顺序写入优化,支持百万级 TPS
  • 超高压缩:10:1 甚至更高的压缩比
  • 快速查询:无需索引快速定位数据
  • 海量并发:支持亿级表规模

二、存储架构总览

复制代码
┌─────────────────────────────────────────────────┐
│                   vnode 存储单元                 │
├─────────────────────────────────────────────────┤
│  ┌──────────┐  ┌──────────┐  ┌──────────┐       │
│  │   WAL    │  │   META   │  │   TSDB   │       │
│  │  预写日志 │  │  元数据   │  │ 时序数据  │        │
│  └──────────┘  └──────────┘  └──────────┘       │
│       ↓             ↓              ↓            │
│  持久化保证     B+Tree存储    LSM存储引擎           │
└─────────────────────────────────────────────────┘

三、核心技术特性

3.1 一表一采集点模型 - 速度快的根本

设计理念:一个数据采集点(如一个传感器)对应一张表

sql 复制代码
-- 创建超级表(模板)
CREATE STABLE sensors (
    ts TIMESTAMP,           -- 时间戳
    temperature FLOAT,      -- 温度
    humidity INT            -- 湿度
) TAGS (location BINARY(64), device_id INT);

-- 每个传感器是一张子表
CREATE TABLE sensor_001 USING sensors TAGS ('北京机房', 1);
CREATE TABLE sensor_002 USING sensors TAGS ('上海机房', 2);

为什么这么快?

  1. 顺序写入优化

    传统数据库(一个表存所有设备):
    Device_1: [t1,v1] → 随机位置
    Device_2: [t1,v2] → 随机位置
    Device_3: [t1,v3] → 随机位置
    ↓ 磁盘随机写入,速度慢

    TDengine(每个设备一张表):
    Sensor_001表: [t1,v1] → [t2,v2] → [t3,v3] → ...
    ↓ 顺序追加写入,速度快 10x+

  2. 查询性能优化

    -- 查询单个传感器数据,一次连续读取
    SELECT * FROM sensor_001
    WHERE ts >= '2024-01-01' AND ts < '2024-01-02';

    -- 内部执行:直接定位 sensor_001 的数据块,连续读取
    -- 无需扫描其他传感器数据

  3. 标签与数据分离存储

    元数据层(META):
    sensor_001 → tags: {location:'北京机房', device_id:1}

    数据层(TSDB):
    sensor_001 → [时间列][温度列][湿度列]

    优势:
    ✓ 标签不重复存储,节省空间 90%+
    ✓ 查询先过滤标签,再读数据,速度快

3.2 LSM 存储结构 - 写入快,不阻塞

为什么不用 B+Tree?

复制代码
B+Tree 问题:
数据量增长 → 树高度增加 → 查询性能下降
[10GB数据: 3层] → [100GB: 4层] → [1TB: 5层] ✗

LSM 优势:
写入量增长 → 合并优化 → 查询性能稳定
始终保持高性能 ✓

LSM 写入流程(快速且可靠):

复制代码
第1步:写入内存(毫秒级)
  数据 → MemTable (Red-Black Tree + SkipList)
  ↓ 快速索引,立即返回成功

第2步:写入 WAL(保证持久性)
  并行写入 WAL 预写日志
  ↓ 断电可恢复,数据不丢失

第3步:内存满触发落盘(后台进行)
  MemTable → STT 文件(小文件)
  ↓ 不阻塞写入

第4步:后台合并(优化查询)
  多个 STT → 合并成 Data 文件(大文件)
  ↓ 减少文件数量,提升查询速度

双索引设计

c 复制代码
// Red-Black Tree: 索引不同表
RBTree {
    sensor_001 → SkipList_1
    sensor_002 → SkipList_2
    sensor_003 → SkipList_3
    ...
}

// SkipList: 索引单表数据(按时间排序)
SkipList_1 {
    Level 3: [t1] ----------------→ [t100]
    Level 2: [t1] ----→ [t50] ----→ [t100]
    Level 1: [t1] → [t25] → [t50] → [t75] → [t100]
    Level 0: [t1] → [t2] → ... → [t99] → [t100]
}

查询时间复杂度:O(log n)

3.3 时间分区 - 无索引快速定位

设计思想:按时间段分文件存储

复制代码
/var/lib/taos/vnode2/tsdb/
├── v2f1845.data    ← 2024-01-01 ~ 2024-01-10
├── v2f1845.head
├── v2f1846.data    ← 2024-01-11 ~ 2024-01-20
├── v2f1846.head
├── v2f1847.data    ← 2024-01-21 ~ 2024-01-30
└── v2f1847.head

时间段长度由 duration 参数控制

查询加速原理

sql 复制代码
-- 查询 2024-01-15 的数据
SELECT * FROM sensors WHERE ts = '2024-01-15';

-- 执行过程:
1. 计算文件组编号:(2024-01-15 - 起始时间) / duration = 1846
2. 直接打开 v2f1846.data 文件
3. 无需扫描其他文件

速度:O(1) 时间复杂度,毫秒级定位!

数据生命周期管理

sql 复制代码
-- 创建数据库,设置保留策略
CREATE DATABASE sensor_db 
    DURATION 10d        -- 每个文件存 10 天数据
    KEEP 365d;          -- 保留 365 天

-- 系统自动操作:
超过 365 天的文件 → 自动删除 → 释放空间

3.4 列式存储 + 多级压缩 - 压缩比高的秘密

列式存储优势
复制代码
行式存储(传统数据库):
[t1,temp1,hum1][t2,temp2,hum2][t3,temp3,hum3]
↓ 每列数据类型不同,压缩效率低

列式存储(TDengine):
时间列: [t1, t2, t3, ..., tn]     ← 时间递增,规律性强
温度列: [25.1, 25.2, 25.1, ...]   ← 数值相近,波动小
湿度列: [60, 61, 60, ...]         ← 数值稳定
↓ 同列数据特征相似,压缩效率高 10x+
一级压缩:针对数据类型优化

整数型压缩

c 复制代码
原始数据: [1000, 1001, 1002, 1003, 1004]

Delta 编码:
  [1000, +1, +1, +1, +1]  // 存储差值
  
Delta-Delta 编码:
  [1000, +1, 0, 0, 0]  // 存储差值的差值
  ↓ 大量 0 值
  
Simple8B 编码:
  将多个小数字打包到一个 64bit 整数
  ↓ 压缩比 10:1+

浮点型压缩

c 复制代码
温度数据: [25.1, 25.2, 25.1, 25.3, 25.2]

Delta-Delta 编码:
  基准值: 25.1
  差值: [0, +0.1, 0, +0.2, +0.1]
  差值的差值: [0, +0.1, -0.1, +0.2, -0.1]
  ↓ 数据变化规律明显,压缩效果好

时间戳压缩

c 复制代码
时间戳: [1704067200, 1704067260, 1704067320, 1704067380]
        (2024-01-01 00:00:00, 每隔60秒)

Delta 编码:
  [1704067200, +60, +60, +60]
  ↓ 等间隔采样,大量重复差值
  
最终存储: [起始时间][间隔60秒][记录数4]
  ↓ 压缩比 16:1 (16字节 → 1字节)

字符串压缩

c 复制代码
设备类型: ["sensor", "sensor", "sensor", "actuator", "sensor"]

字典压缩:
  字典: {0: "sensor", 1: "actuator"}
  编码: [0, 0, 0, 1, 0]
  ↓ 长字符串变成短整数
二级压缩:通用算法进一步压缩
复制代码
一级压缩后的数据
  ↓ 仍有规律可循
  
LZ4 压缩(速度快):
  查找重复模式,用引用替代
  压缩比: 2-3x
  速度: 500 MB/s
  
ZSTD 压缩(压缩比高):
  更智能的匹配算法
  压缩比: 3-5x
  速度: 300 MB/s
  
ZLIB 压缩(平衡):
  压缩比: 2.5-4x
  速度: 400 MB/s
有损压缩(可选)
c 复制代码
// 浮点数精度控制
原始数据: 25.123456789
设置精度: 0.01
存储数据: 25.12

温度传感器精度通常 0.1°C,存储 0.001°C 没有意义
↓ 有损压缩可额外节省 30-50% 空间

压缩效果对比

复制代码
原始数据: 1000 MB

一级压缩后: 100 MB  (压缩比 10:1)
  └─ Delta-Delta + Simple8B

二级压缩后: 30 MB   (最终压缩比 33:1)
  └─ ZSTD 压缩

实际案例:
- 稳定工况数据:压缩比可达 50:1
- 一般时序数据:压缩比 10:1 ~ 20:1

3.5 BRIN 索引 - 快速过滤数据块

设计思想:存储数据块的统计信息,而非每条记录的索引

复制代码
传统索引(B+Tree):
├─ 记录1 → 位置1
├─ 记录2 → 位置2
├─ ...
└─ 记录100万 → 位置100万
↓ 索引巨大,占用大量空间

BRIN 索引(块范围索引):
├─ 数据块1: [时间范围: t1~t1000, 最大值, 最小值, 总和]
├─ 数据块2: [时间范围: t1001~t2000, 最大值, 最小值, 总和]
└─ 数据块N: [时间范围: ..., 最大值, 最小值, 总和]
↓ 索引小,查询快

head 文件结构

c 复制代码
// v2f1845.head 文件内容
struct BRINRecord {
    uint64_t tableId;        // 表ID
    int64_t  minTimestamp;   // 最小时间戳
    int64_t  maxTimestamp;   // 最大时间戳
    uint64_t offset;         // 数据块在 .data 文件中的偏移
    uint32_t length;         // 数据块长度
    
    // 预计算统计信息(SMA)
    double   minValue;       // 最小值
    double   maxValue;       // 最大值
    double   sum;            // 总和
    int32_t  count;          // 记录数
};

查询加速示例

sql 复制代码
-- 查询最大温度
SELECT MAX(temperature) FROM sensor_001 
WHERE ts >= '2024-01-15' AND ts < '2024-01-16';

-- 执行过程:
1. 打开 v2f1845.head 文件
2. 扫描 BRIN 记录,找到时间范围匹配的数据块
3. 直接读取 BRIN 记录中的 maxValue
4. 无需读取 .data 文件中的原始数据

结果:查询速度提升 100x+

BRIN 索引本身也压缩

复制代码
BRIN 记录采用列式存储 + 压缩:

tableId列:   [1001, 1001, 1001, ...]  ← 大量重复
minTime列:   [t1, t1000, t2000, ...]  ← 递增序列
maxTime列:   [t999, t1999, t2999, ...] ← 递增序列
offset列:    [0, 4096, 8192, ...]     ← 等差序列

经过压缩后,索引文件非常小

3.6 预计算(SMA)- 秒级聚合查询

设计思想:在数据写入时计算统计信息,查询时直接使用

sql 复制代码
-- 这类查询无需读取原始数据
SELECT MAX(temperature), MIN(temperature), AVG(humidity)
FROM sensors
WHERE ts >= '2024-01-01' AND ts < '2024-02-01';

-- 执行过程:
1. 扫描 head 文件中的 BRIN 记录
2. 读取每个数据块的统计信息:
   - maxValue (最大值)
   - minValue (最小值)  
   - sum (总和)
   - count (记录数)
3. 聚合计算:
   - MAX = max(所有块的maxValue)
   - MIN = min(所有块的minValue)
   - AVG = sum(所有块的sum) / sum(所有块的count)
4. 返回结果

I/O 减少:99%+(只读索引,不读数据)
查询速度:毫秒级(原本需要几秒甚至几十秒)

3.7 写驱动缓存 - 最新数据优先

设计理念:物联网场景最关心最新数据

复制代码
传统缓存(读驱动):
查询什么 → 缓存什么
↓ 冷数据占用缓存空间

TDengine 缓存(写驱动):
写入什么 → 缓存什么
↓ 最新数据始终在缓存

缓存结构

复制代码
vnode 内存分配:
┌────────────────────────────┐
│  内存块1 (正在写入)          │ ← 最新数据
├────────────────────────────┤
│  内存块2 (准备落盘)          │
├────────────────────────────┤
│  内存块3 (后台落盘中)         │
└────────────────────────────┘

当内存块1写满 1/3 时:
  → 触发内存块2落盘
  → 写入切换到新的内存块1
  → 始终保持 1/3 内存缓存最新数据

查询加速

sql 复制代码
-- 查询最新记录(last_row)
SELECT last_row(*) FROM sensor_001;

-- 执行:直接从内存缓存返回,微秒级响应

-- LRU 缓存配置
CREATE DATABASE sensor_db 
    CACHEMODEL 'both';  -- 开启 last 和 last_row 缓存

-- 查询最新温度
SELECT last(temperature) FROM sensor_001;
↓ 命中缓存,无需访问磁盘

3.8 多表低频优化 - 碎片合并

场景:成千上万张表,每张表写入频率低

复制代码
问题:
sensor_001 写入 10 条 → 落盘生成小文件
sensor_002 写入 8 条  → 落盘生成小文件
sensor_003 写入 12 条 → 落盘生成小文件
...
↓ 产生大量小文件(碎片化)

优化方案(多 STT 文件):
1. 配置多个 STT 文件(如 6 个)
2. 首次落盘 → stt1 文件
3. 再次落盘 → stt2 文件
4. ...
5. 后台合并:stt1 + stt2 + ... → data 文件

结果:
✓ 减少文件数量 90%+
✓ 提升查询性能 10x+

四、文件组织结构

完整的数据文件组

复制代码
文件组 v2f1845/ (存储 2024-01-01 ~ 2024-01-10 数据)
├── v2f1845.head       ← BRIN 索引文件
├── v2f1845.data       ← 时序数据文件(列式存储+压缩)
├── v2f1845.sma        ← 预计算文件(统计信息)
├── v2f1845.tomb       ← 删除记录文件
├── v2f1845.stt1       ← 碎片数据文件1
├── v2f1845.stt2       ← 碎片数据文件2
└── ...

文件交互流程

复制代码
写入流程:
数据 → MemTable → STT文件 → (后台合并) → Data文件

查询流程:
查询条件 → Head文件(BRIN索引) → 定位数据块 → Data文件

聚合查询:
查询条件 → Head文件(读取SMA) → 直接返回(无需读Data)

五、性能优势总结

5.1 写入性能

优化技术 性能提升 原理
顺序写入 10x+ 单表数据连续存储,磁盘顺序I/O
内存缓存 100x+ 数据先写内存,批量落盘
WAL 日志 2x+ 异步刷盘,不阻塞写入
LSM 结构 稳定 写入不受数据量影响

实测数据

  • 单机写入:100万 数据点/秒
  • 集群写入:300万+ 数据点/秒

5.2 压缩比

数据类型 压缩比 技术
稳定工况数据 50:1 Delta + ZSTD
一般时序数据 10:1 ~ 20:1 列存 + 多级压缩
字符串数据 5:1 ~ 10:1 字典压缩

对比

  • 通用数据库:无压缩或压缩比 2:1
  • TDengine:10:1 起步,最高 50:1

5.3 查询性能

查询类型 性能提升 原理
单表时间范围查询 10x+ 时间分区,快速定位
聚合查询(MAX/MIN/AVG) 100x+ BRIN 索引 + SMA 预计算
最新数据查询 1000x+ 写驱动缓存,内存直接返回
多表标签过滤 10x+ 标签分离存储,元数据索引

5.4 空间占用

复制代码
1亿条记录(10个字段,每条100字节)存储空间对比:

传统数据库:
  原始数据: 10GB
  索引:    5GB
  总计:    15GB

TDengine:
  原始数据: 10GB
  压缩后:  0.5 - 1GB  (压缩比 10:1 ~ 20:1)
  BRIN索引: 10MB     (极小)
  总计:    0.51 - 1.01GB

空间节省:93% - 96%

六、适用场景

最佳适用场景

  1. 物联网设备监控

    • 海量传感器数据采集
    • 设备状态实时查询
    • 历史数据趋势分析
  2. 工业互联网

    • 生产线设备监控
    • 能耗分析
    • 预测性维护
  3. 车联网

    • 车辆轨迹数据
    • 车况实时监控
    • 驾驶行为分析
  4. IT 运维监控

    • 服务器性能监控
    • 应用日志分析
    • 网络流量监控

数据特征要求

  • ✅ 时间序列数据
  • ✅ 写多读少
  • ✅ 数据连续采集
  • ✅ 需要长期存储
  • ✅ 查询以时间范围为主

七、配置建议

少表高频场景

sql 复制代码
CREATE DATABASE iot_db
    BUFFER 256         -- 大内存缓存,提升写入性能
    PAGES 64           -- 元数据缓存页数
    PAGESIZE 16        -- 每页大小(KB)
    DURATION 10d       -- 文件组时间跨度
    KEEP 365d          -- 数据保留时长
    MINROWS 100        -- 每块最少行数
    MAXROWS 4096       -- 每块最多行数
    COMP 2             -- 二级压缩
    PRECISION 'ms'     -- 时间精度:毫秒
    SINGLE_STABLE 0;   -- 多个超级表

多表低频场景

sql 复制代码
CREATE DATABASE sensor_db
    BUFFER 64          -- 中等内存缓存
    PAGES 128          -- 更多元数据缓存(支持更多表)
    PAGESIZE 8         
    DURATION 1d        -- 更短的文件组时间跨度
    KEEP 3650d         -- 长期保留
    MINROWS 10         -- 更小的块大小(适应低频写入)
    MAXROWS 1000       
    COMP 2             
    STT_TRIGGER 6      -- 开启多 STT 文件优化
    PRECISION 'ms';

八、总结

TDengine 存储引擎通过一系列创新设计,实现了时序数据库的极致性能:

速度快的核心原因

  1. 顺序写入:一表一采集点模型,充分利用磁盘顺序I/O
  2. LSM 结构:写入内存即返回,后台异步落盘
  3. 时间分区:无需索引快速定位数据文件
  4. BRIN 索引:块级索引,减少数据扫描
  5. 预计算:SMA 统计信息,聚合查询秒级返回
  6. 写驱动缓存:最新数据优先,查询命中率高

压缩比高的核心原因

  1. 列式存储:同列数据特征相似,压缩效率高
  2. 一级压缩:针对数据类型优化(Delta-Delta, Simple8B等)
  3. 二级压缩:通用算法进一步压缩(LZ4, ZSTD等)
  4. 标签分离:避免标签数据重复存储
  5. 有损压缩:可选的精度控制,额外节省空间

关键数据指标

  • 📊 写入性能:100万+ 数据点/秒(单机)
  • 📊 压缩比:10:1 ~ 50:1
  • 📊 查询性能:10x ~ 1000x 提升
  • 📊 空间节省:93% ~ 96%
  • 📊 支持规模:亿级表数量

TDengine 存储引擎是专为时序数据场景深度优化的高性能存储方案,在保证数据可靠性的前提下,实现了写入速度、压缩比、查询性能的全面领先。

相关文章

  1. TDengine 存储引擎设计

关于 TDengine

TDengine 专为物联网IoT平台、工业大数据平台设计。其中,TDengine TSDB 是一款高性能、分布式的时序数据库(Time Series Database),同时它还带有内建的缓存、流式计算、数据订阅等系统功能;TDengine IDMP 是一款AI原生工业数据管理平台,它通过树状层次结构建立数据目录,对数据进行标准化、情景化,并通过 AI 提供实时分析、可视化、事件管理与报警等功能。

相关推荐
玩具猴_wjh2 小时前
MongoDB
数据库·mongodb
梨落秋霜2 小时前
Python入门篇【if判断语句】
android·java·python
美狐美颜SDK开放平台2 小时前
跨平台直播美颜SDK开发:iOS/Android/WebGL实现要点
android·人工智能·ios·美颜sdk·第三方美颜sdk·视频美颜sdk·美狐美颜sdk
九章-2 小时前
平滑替换:大唐经研院经济评价系统成功完成国产化迁移
数据库·安全·政务
w_zero_one2 小时前
ArkTS鸿蒙--关系型数据库的级联
数据库·harmonyos
武子康2 小时前
大数据-180 Elasticsearch 近实时搜索:Segment、Refresh、Flush、Translog 全流程解析
大数据·后端·elasticsearch
2501_915921432 小时前
重新理解 iOS 的 Bundle Id 从创建、管理到协作的工程策略
android·ios·小程序·https·uni-app·iphone·webview
阿桂天山2 小时前
阿桂的数据资产灵动实战 (一) 开发框架
大数据·python·软件工程
2301_800256112 小时前
【第九章知识点总结3】9.4 Physical model 9.5 pgRouting
数据库