TDengine 存储引擎概览 — TSDB 分层存储架构与数据流转全景 分类3.存储引擎 |篇章01 存储引擎概览适用版本TDengine v3.xv3.3.x / v3.4.x | 最后更新2026-05-24TDengine 的存储引擎以 VNode 为基本单元每个 VNode 内部集成了 WAL、MemTable、TSDB、META、Cache 五大子系统协同完成从写入到持久化到查询的全部数据生命周期管理。本文提供存储层的全景视图。核心概念速查表概念说明VNode数据存储的基本单元一个 VGroup 的一个副本TSDB时序数据存储引擎管理数据文件的写入、读取、合并METATDB元数据存储引擎B树管理子表信息和 Tag 索引WAL预写日志保障写入的持久性和 Raft 复制MemTable内存中的写入缓冲区数据落盘前的暂存地Cache基于 RocksDB 的 Last 值缓存加速 LAST()/LAST_ROW()STTSorted String Table排序落盘文件类似 LSM-Tree 的 L0File 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/vnodeN/ │ ├── vnode.json ← VNode 配置和状态 ├── raft/ │ └── raft_store.json ← Raft 持久化状态term, vote │ ├── wal/ ← WAL 目录 │ ├── 00000000.log ← WAL 日志文件 │ ├── 00000000.idx ← WAL 索引文件 │ ├── 00000001.log │ ├── 00000001.idx │ └── meta-verN ← WAL 元数据 │ ├── meta/ ← META 元数据目录TDB B树 │ ├── main.tdb ← 子表元数据 │ └── main.tdb-journal ← TDB 事务日志 │ ├── tsdb/ ← TSDB 时序数据目录 │ ├── current ← 当前文件集元信息 │ ├── vfidfN.head ← 数据块索引文件 │ ├── vfidfN.data ← 列数据文件 │ ├── vfidfN.sma ← 预聚合文件 │ ├── vfidfN.stt ← STT 文件排序落盘 │ └── vfidfN.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_PERIODMemTable写入缓冲有序组织内存BUFFERTSDB时序数据持久存储磁盘文件组DURATION, COMP, STT_TRIGGERMETA子表/Tag 元数据磁盘B树PAGES, PAGESIZECacheLast 值快速查询磁盘RocksDBCACHEMODEL, CACHESIZETQ数据订阅 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: 子表唯一 ID64 位整数全局唯一 - name: 子表名称 - suid: 所属超级表的 uid - createdTime: 创建时间 - ttl: 子表级过期设置 ② Tag 值 - 每张子表的所有 Tag 列的实际值 - 作为查询中 Tag 过滤条件的数据源 ③ Schema 版本 - 超级表的列 SchemaschemaVersion 递增 - 超级表的 Tag SchematagVersion 递增 - 用于处理 Schema 变更后新旧数据块的兼容 META 的存储引擎TDB 底层使用 B 树实现存储在 meta/main.tdb 文件中 ┌─────────────────────────────────────────────────┐ │ B Tree (main.tdb) │ │ │ │ Key-Value 存储模式 │ │ │ │ Table 1: name → uid 的映射 │ │ d1001 → uid880001 │ │ d1002 → uid880002 │ │ │ │ Table 2: uid → 子表元信息 │ │ uid880001 → {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) → 返回 [uid880001, uid880005, uid880023, ...] │ ▼ 仅对这些 uid 查询 TSDB 数据 → 避免全表扫描所有子表 Schema 版本管理 ALTER TABLE meters ADD COLUMN pressure FLOAT; → tagVersion 不变schemaVersion 1 新写入的数据按新 Schema含 pressure 列 旧数据块仍按旧 Schema无 pressure 列 查询时 - 检查数据块的 schemaVersion - 如果块的版本 当前版本 → 缺失列填 NULL - 如果列已被删除 → 跳过该列 → 无需重写旧数据零成本 Schema 变更META 参数默认值说明PAGES256B 树的缓存页数PAGESIZE4 KB每页大小META 内存PAGES × PAGESIZE ≈ 1MB元数据缓存占用8. 文件集File Set组织TSDB 按时间范围将数据组织为多个文件集每个文件集覆盖 DURATION 指定的时间跨度文件集按时间线排列 时间轴 → ├── FileSet 0: [T0, T0DURATION) │ ├── v0f0.head (块索引) │ ├── v0f0.data (列数据) │ ├── v0f0.sma (预聚合) │ └── v0f0.stt (STT) │ ├── FileSet 1: [T0DURATION, T02×DURATION) │ ├── v1f0.head │ ├── v1f0.data │ ├── v1f0.sma │ └── v1f0.stt │ └── FileSet N: [T0N×DURATION, T0(N1)×DURATION) └── ... fid (timestamp - 起始时间) / DURATION 查询时根据 WHERE 时间条件确定需要读取的 fid 范围 跳过不相关的文件集 → 减少 I/O性能考量存储引擎的设计权衡设计决策优势代价WAL 顺序写写入延迟低~0.1ms额外磁盘空间临时MemTable 跳表有序插入 快速范围查询内存占用按时间分文件过期删除删文件O(1)文件数随时间增长STT 延迟合并写入吞吐高追加写查询需多路合并列式存储压缩率高 列裁剪点查需拼装行关键性能指标指标典型值SSD单 VNode 写入延迟0.5~2ms3 副本WAL 写入带宽200~500 MB/s压缩后存储效率原始大小的 5%~20%冷数据查询延迟5~50ms取决于数据量Last Cache 查询延迟 0.1msFAQQ1: 一个 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并指定不同的level0/1/2实现多级存储level 0 放 SSD热数据level 1/2 放 HDD冷数据。参考系统构架篇01-《TDengine 整体架构全景》02-《集群拓扑深度解析》03-《MNode 内部机制深度解析》04-《RPC 通信层深度解析》05-《VNode 生命周期》06-《RAFT 共识协议》07-《端到端的消息流》数据模型01-《数据库创建与参数详解》02-《超级表/子表/普通表》03-《支持数据类型深度解析》04-《TDengine Tag 设计哲学与 Schema 变更机制》05-《TDengine 虚拟表实现原理》关于 TDengineTDengine 专为物联网IoT平台、工业大数据平台设计。其中TDengine TSDB 是一款高性能、分布式的时序数据库Time Series Database同时它还带有内建的缓存、流式计算、数据订阅等系统功能TDengine IDMP 是一款AI原生工业数据管理平台它通过树状层次结构建立数据目录对数据进行标准化、情景化并通过 AI 提供实时分析、可视化、事件管理与报警等功能。