分类 :3.存储引擎 | 篇章:01 存储引擎概览

适用版本:TDengine v3.x(v3.3.x / v3.4.x) | 最后更新:2026-05-24
TDengine 的存储引擎以 VNode 为基本单元,每个 VNode 内部集成了 WAL、MemTable、TSDB、META、Cache 五大子系统,协同完成从写入到持久化到查询的全部数据生命周期管理。本文提供存储层的全景视图。
核心概念速查表
| 概念 | 说明 |
|---|---|
| VNode | 数据存储的基本单元,一个 VGroup 的一个副本 |
| TSDB | 时序数据存储引擎,管理数据文件的写入、读取、合并 |
| META(TDB) | 元数据存储引擎(B+树),管理子表信息和 Tag 索引 |
| WAL | 预写日志,保障写入的持久性和 Raft 复制 |
| MemTable | 内存中的写入缓冲区,数据落盘前的暂存地 |
| Cache | 基于 RocksDB 的 Last 值缓存,加速 LAST()/LAST_ROW() |
| STT | Sorted String Table,排序落盘文件(类似 LSM-Tree 的 L0) |
| File Set | 按时间范围组织的一组数据文件(.head/.data/.sma/.stt/.tomb) |
详细解析
1. VNode 存储架构总览
VNode 内部存储组件:
┌────────────────────────────────────────────────┐
│ VNode │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │
│ │ WAL │ │ MemTable │ │ TSDB │ │
│ │ (预写日志)│ │ (写缓冲) │ │ (时序文件) │ │
│ └────┬─────┘ └────┬─────┘ └──────┬───────┘ │
│ │ │ │ │
│ ┌────┴──────────────┴───────────────┴──────┐ │
│ │ Buffer Pool │ │
│ │ (内存池,3 段轮转) │ │
│ └──────────────────────────────────────────┘ │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │
│ │ META │ │ Cache │ │ TQ │ │
│ │(B+树元数据)│ │(Last缓存)│ │ (订阅队列) │ │
│ └──────────┘ └──────────┘ └──────────────┘ │
└────────────────────────────────────────────────┘
2. 磁盘目录结构
VNode 在磁盘上的目录布局:
/var/lib/taos/vnode/vnode<N>/
│
├── vnode.json ← VNode 配置和状态
├── raft/
│ └── raft_store.json ← Raft 持久化状态(term, vote)
│
├── wal/ ← WAL 目录
│ ├── 00000000.log ← WAL 日志文件
│ ├── 00000000.idx ← WAL 索引文件
│ ├── 00000001.log
│ ├── 00000001.idx
│ └── meta-ver<N> ← WAL 元数据
│
├── meta/ ← META 元数据目录(TDB B+树)
│ ├── main.tdb ← 子表元数据
│ └── main.tdb-journal ← TDB 事务日志
│
├── tsdb/ ← TSDB 时序数据目录
│ ├── current ← 当前文件集元信息
│ ├── v<fid>f<N>.head ← 数据块索引文件
│ ├── v<fid>f<N>.data ← 列数据文件
│ ├── v<fid>f<N>.sma ← 预聚合文件
│ ├── v<fid>f<N>.stt ← STT 文件(排序落盘)
│ └── v<fid>f<N>.tomb ← 删除记录文件
│
├── cache.rdb/ ← RocksDB Last 缓存
│
└── tq/ ← 订阅/流计算状态
3. 写入数据流转
一条数据从写入到落盘的完整路径:
客户端 INSERT
│
▼
① WAL 追加写入(持久化保障)
│
▼
② Raft 复制到 Follower(多副本)
│
▼
③ 写入 MemTable(内存跳表,按 UID+时间有序)
│
├── 同步更新 Last Cache(如果启用 CACHEMODEL)
│
▼
④ MemTable 达到阈值 → 转为 Immutable MemTable
│
▼
⑤ Commit 线程后台刷盘:
│
├── STT_TRIGGER > 1 → 写入 STT 文件(无序容忍)
│ 后续合并到有序文件
│
└── STT_TRIGGER = 1 → 直接与已有文件合并写入
生成新的 .head + .data + .sma
│
▼
⑥ 截断已提交的 WAL(释放磁盘)
4. 读取数据流转
查询数据的读取路径:
SELECT 查询
│
▼
① 确定时间范围 → 定位相关 File Set
│
▼
② 多层数据源合并读取:
│
├── MemTable(最新,内存)
│ └── 跳表遍历,按时间有序输出
│
├── Immutable MemTable(正在刷盘的旧缓冲)
│ └── 同上
│
├── STT 文件(已落盘但未合并)
│ └── 按块索引定位 + 解压读取
│
└── .data 文件(已合并的有序数据)
└── .head 索引定位 → .data 读取 → 解压
│
▼
③ 多路归并排序(Merge Sort by timestamp)
│
▼
④ 应用过滤条件(WHERE)
│
▼
⑤ 返回结果集
5. 各组件职责总结
| 组件 | 职责 | 存储介质 | 关键参数 |
|---|---|---|---|
| WAL | 写前日志,崩溃恢复 | 磁盘(顺序写) | WAL_LEVEL, WAL_FSYNC_PERIOD |
| MemTable | 写入缓冲,有序组织 | 内存 | BUFFER |
| TSDB | 时序数据持久存储 | 磁盘(文件组) | DURATION, COMP, STT_TRIGGER |
| META | 子表/Tag 元数据 | 磁盘(B+树) | PAGES, PAGESIZE |
| Cache | Last 值快速查询 | 磁盘(RocksDB) | CACHEMODEL, CACHESIZE |
| TQ | 数据订阅 offset 管理 | 磁盘 | WAL_RETENTION_* |
6. Buffer Pool 内存管理
VNode 的写入内存通过 Buffer Pool 统一管理,采用三段轮转机制:
Buffer Pool 三段轮转:
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Free │ → │ InUse │ → │ OnCommit │
│ (空闲) │ │ (当前写入)│ │ (正在刷盘)│
└──────────┘ └──────────┘ └──────────┘
↑ │
└────────────────────────────────┘
刷盘完成后回收
段大小 = BUFFER / 3
默认:256MB / 3 ≈ 85MB 每段
当 InUse 段写满 → 转为 OnCommit → 触发刷盘
Free 段成为新的 InUse → 继续接受写入
7. META 元数据引擎
META 子系统负责存储和索引 VNode 内所有子表的元信息,是查询路由和 Tag 过滤的基础:
META 存储的内容:
① 子表注册信息:
- uid: 子表唯一 ID(64 位整数,全局唯一)
- name: 子表名称
- suid: 所属超级表的 uid
- createdTime: 创建时间
- ttl: 子表级过期设置
② Tag 值:
- 每张子表的所有 Tag 列的实际值
- 作为查询中 Tag 过滤条件的数据源
③ Schema 版本:
- 超级表的列 Schema(schemaVersion 递增)
- 超级表的 Tag Schema(tagVersion 递增)
- 用于处理 Schema 变更后新旧数据块的兼容
META 的存储引擎(TDB):
底层使用 B+ 树实现,存储在 meta/main.tdb 文件中
┌─────────────────────────────────────────────────┐
│ B+ Tree (main.tdb) │
│ │
│ Key-Value 存储模式: │
│ │
│ Table 1: name → uid 的映射 │
│ "d1001" → uid=880001 │
│ "d1002" → uid=880002 │
│ │
│ Table 2: uid → 子表元信息 │
│ uid=880001 → {suid, tags[], schema_ver, ...} │
│ │
│ Table 3: Tag 索引 │
│ (suid, tag_col_id, tag_value) → [uid列表] │
│ │
└─────────────────────────────────────────────────┘
Tag 过滤的执行路径:
SELECT * FROM meters WHERE location = 'Beijing'
│
▼
META Tag 索引查找:
Key=(suid_meters, col_location, 'Beijing')
→ 返回 [uid=880001, uid=880005, uid=880023, ...]
│
▼
仅对这些 uid 查询 TSDB 数据
→ 避免全表扫描所有子表
Schema 版本管理:
ALTER TABLE meters ADD COLUMN pressure FLOAT;
→ tagVersion 不变,schemaVersion +1
新写入的数据按新 Schema(含 pressure 列)
旧数据块仍按旧 Schema(无 pressure 列)
查询时:
- 检查数据块的 schemaVersion
- 如果块的版本 < 当前版本 → 缺失列填 NULL
- 如果列已被删除 → 跳过该列
→ 无需重写旧数据,零成本 Schema 变更
| META 参数 | 默认值 | 说明 |
|---|---|---|
| PAGES | 256 | B+ 树的缓存页数 |
| PAGESIZE | 4 KB | 每页大小 |
| META 内存 | PAGES × PAGESIZE ≈ 1MB | 元数据缓存占用 |
8. 文件集(File Set)组织
TSDB 按时间范围将数据组织为多个文件集,每个文件集覆盖 DURATION 指定的时间跨度:
文件集按时间线排列:
时间轴 →
├── FileSet 0: [T0, T0+DURATION)
│ ├── v0f0.head (块索引)
│ ├── v0f0.data (列数据)
│ ├── v0f0.sma (预聚合)
│ └── v0f0.stt (STT)
│
├── FileSet 1: [T0+DURATION, T0+2×DURATION)
│ ├── v1f0.head
│ ├── v1f0.data
│ ├── v1f0.sma
│ └── v1f0.stt
│
└── FileSet N: [T0+N×DURATION, T0+(N+1)×DURATION)
└── ...
fid = (timestamp - 起始时间) / DURATION
查询时:根据 WHERE 时间条件确定需要读取的 fid 范围
跳过不相关的文件集 → 减少 I/O
性能考量
存储引擎的设计权衡
| 设计决策 | 优势 | 代价 |
|---|---|---|
| WAL 顺序写 | 写入延迟低(~0.1ms) | 额外磁盘空间(临时) |
| MemTable 跳表 | 有序插入 + 快速范围查询 | 内存占用 |
| 按时间分文件 | 过期删除=删文件(O(1)) | 文件数随时间增长 |
| STT 延迟合并 | 写入吞吐高(追加写) | 查询需多路合并 |
| 列式存储 | 压缩率高 + 列裁剪 | 点查需拼装行 |
关键性能指标
| 指标 | 典型值(SSD) |
|---|---|
| 单 VNode 写入延迟 | 0.5~2ms(3 副本) |
| WAL 写入带宽 | 200~500 MB/s |
| 压缩后存储效率 | 原始大小的 5%~20% |
| 冷数据查询延迟 | 5~50ms(取决于数据量) |
| Last Cache 查询延迟 | < 0.1ms |
FAQ
Q1: 一个 VNode 占用多少内存?
主要内存 = BUFFER + PAGES×PAGESIZE + CACHESIZE。默认配置下约 260MB。通过 BUFFER × VGROUPS × REPLICA 估算整个数据库的总内存需求。
Q2: 数据是先写 WAL 还是先写 MemTable?
先写 WAL。WAL 保障了即使进程崩溃,已确认的数据也不会丢失。重启时通过 WAL 重放恢复 MemTable。
Q3: 为什么需要 STT 文件?
STT 是写入性能和查询性能的折中。高频写入时直接追加到 STT(快),后台异步合并到有序文件(保证查询性能)。STT_TRIGGER 参数控制这个权衡点。
Q4: VNode 目录可以放在不同磁盘上吗?
可以。通过配置多个 dataDir 并指定不同的 level(0/1/2)实现多级存储:level 0 放 SSD(热数据),level 1/2 放 HDD(冷数据)。
参考
系统构架篇
- 01-《TDengine 整体架构全景》
- 02-《集群拓扑深度解析》
- 03-《MNode 内部机制深度解析》
- 04-《RPC 通信层深度解析》
- 05-《VNode 生命周期》
- 06-《RAFT 共识协议》
- 07-《端到端的消息流》
数据模型
数据模型
关于 TDengine
TDengine 专为物联网IoT平台、工业大数据平台设计。其中,TDengine TSDB 是一款高性能、分布式的时序数据库(Time Series Database),同时它还带有内建的缓存、流式计算、数据订阅等系统功能;TDengine IDMP 是一款AI原生工业数据管理平台,它通过树状层次结构建立数据目录,对数据进行标准化、情景化,并通过 AI 提供实时分析、可视化、事件管理与报警等功能。