目录
-
- 数据文件整体目录结构
- [dnode --- 数据节点目录](#dnode — 数据节点目录)
-
- dnode.json
- dnode.info
- [config --- 配置目录](#config — 配置目录)
- [mnode --- 管理节点目录](#mnode — 管理节点目录)
-
- mnode.json
- [data/sdb.data --- 集群元数据](#data/sdb.data — 集群元数据)
- [sync --- 同步目录](#sync — 同步目录)
- [wal --- 预写日志目录](#wal — 预写日志目录)
-
- [WAL 文件命名规则](#WAL 文件命名规则)
- [.log 文件二进制格式](#.log 文件二进制格式)
- [.idx 文件二进制格式](#.idx 文件二进制格式)
- [meta-ver 文件](#meta-ver 文件)
- [WAL 文件生命周期](#WAL 文件生命周期)
- [vnode --- 虚拟节点目录](#vnode — 虚拟节点目录)
-
- vnodes.json
- vnode.json
-
- [config 对象](#config 对象)
- [state 对象](#state 对象)
- [meta --- 元数据目录](#meta — 元数据目录)
- [sync --- 同步目录](#sync — 同步目录)
- [tsdb --- 时序数据目录](#tsdb — 时序数据目录)
-
- [current.json --- 数据文件集清单](#current.json — 数据文件集清单)
- [TSDB 数据文件命名规则](#TSDB 数据文件命名规则)
- [.head 文件 --- BRIN 索引](#.head 文件 — BRIN 索引)
- [.data 文件 --- 数据块](#.data 文件 — 数据块)
- [.sma 文件 --- 预聚合数据](#.sma 文件 — 预聚合数据)
- [.stt 文件 --- STT 合并文件](#.stt 文件 — STT 合并文件)
- [cache.rdb --- RocksDB 缓存](#cache.rdb — RocksDB 缓存)
- [wal --- vnode 预写日志](#wal — vnode 预写日志)
- 附录:关键常量速查
- 附录:源码文件索引
数据文件整体目录结构
/var/lib/taos/
├── .running # 运行标记文件,taosd 启动时创建,退出时删除
├── .taosudf.sock.0 # UDF 服务 Unix Socket
├── .udf/ # UDF(用户自定义函数)目录
├── dnode/ # 数据节点目录
│ ├── dnode.json # 数据节点配置与集群信息
│ ├── dnode.info # 二进制标记文件(版本信息)
│ └── config/
│ ├── global.json # 全局参数(集群一致)
│ ├── local.json # 本地参数(各节点可不同)
│ └── stype.json # 参数来源标记
├── mnode/ # 管理节点目录
│ ├── mnode.json # 管理节点状态
│ ├── data/
│ │ └── sdb.data # 集群元数据(二进制)
│ ├── sync/
│ │ ├── raft_config.json # Raft 共识配置
│ │ └── raft_store.json # Raft 持久化状态
│ └── wal/ # 管理节点 WAL
│ ├── meta-ver<N> # WAL 元数据(JSON)
│ └── 00000000000000XXXXXX.{idx,log}
├── vnode/ # 虚拟节点目录
│ ├── vnodes.json # 虚拟节点注册表
│ └── vnode<N>/ # 单个虚拟节点
│ ├── vnode.json # 虚拟节点配置与状态
│ ├── meta/
│ │ └── main.tdb # 表元数据(B-tree 存储引擎)
│ ├── sync/
│ │ ├── raft_config.json
│ │ └── raft_store.json
│ ├── tq/
│ │ ├── stream/main.tdb # 流计算元数据
│ │ └── subscribe/main.tdb # 订阅元数据
│ ├── tsdb/
│ │ ├── current.json # 数据文件集清单
│ │ ├── cache.rdb/ # RocksDB 缓存(last/last_row)
│ │ ├── v<N>f<FID>ver<CID>.head # BRIN 索引文件
│ │ ├── v<N>f<FID>ver<CID>.data # 数据块文件
│ │ ├── v<N>f<FID>ver<CID>.sma # 预聚合文件
│ │ ├── v<N>f<FID>ver<CID>.tomb # 墓碑/删除记录
│ │ └── v<N>f<FID>ver<CID>.stt # STT 合并文件
│ └── wal/
│ ├── meta-ver<N>
│ └── 00000000000000XXXXXX.{idx,log}
├── explorer/ # TDengine Explorer 组件
└── taosx/ # taosx 组件
dnode --- 数据节点目录
每个 taosd 进程对应一个 dnode 目录,存储数据节点所有信息。
dnode.json
数据节点配置文件,记录当前节点信息和整个集群的节点信息。
json
{
"dnodeId": 1, // 数据节点 ID
"dnodeVer": "4", // 数据节点版本
"engineVer": "30000000", // 引擎版本号
"clusterId": "1284994906254398242", // 集群唯一 ID
"dropped": 0, // 是否被删除(0=正常, 1=已删除)
"encryptAlgor": 0, // 加密算法(0=不加密)
"encryptScope": 0, // 加密范围
"dnodes": [{ // 集群中所有节点列表
"id": 1,
"fqdn": "c3-65", // 节点 FQDN
"port": 6030, // 节点端口
"isMnode": 1 // 是否同时为管理节点
}]
}
dnode.info
二进制文件,512 字节,前 8 字节包含节点 ID 和版本标记,其余为零填充。
config --- 配置目录
从 3.1 版本开始,大部分参数都可以通过 ALTER 命令动态修改。如果配置文件 taos.cfg 中没有配置 forceReadConfig,则大部分参数从这些文件读取。
global.json
全局参数,整个集群保持一致。
json
{
"file_version": 1, // 文件格式版本
"version": 0, // 配置版本号(每次修改递增)
"configs": {
"timezone": "UTC-8 (UTC, +0800)", // 时区设置
"charset": "UTF-8", // 字符集
"compressor": "ZSTD_COMPRESSOR", // 消息压缩算法(ZSTD/LZ4/NONE)
"locale": "en_US.UTF-8", // 系统区域设置
"syncElectInterval": 4000, // Raft 选举超时间隔(毫秒)
"ttlUnit": 86400, // TTL 时间单位(秒),默认 86400=1天
"enableCoreFile": true, // 是否生成 core dump 文件
"telemetryReporting": false, // 是否开启遥测上报
"monitor": true, // 是否开启监控
"slowLogThreshold": 10, // 慢查询阈值(秒)
"slowLogMaxLen": 4096, // 慢日志最大长度(字节)
// ... 更多参数详见官网
}
}
local.json
本地参数,集群中每个节点均可不同。
json
{
"file_version": 1, // 文件格式版本
"configs": {
"firstEp": "c3-65:6030", // 首选连接端点(FQDN:端口)
"secondEp": "c3-65:6030", // 备用连接端点
"fqdn": "c3-65", // 当前节点 FQDN
"serverPort": 6030, // 服务端口(默认 6030)
"numOfCores": "8.000000", // CPU 核数(自动检测)
"totalMemoryKB": "24521772", // 总内存(KB,自动检测,约 23.4 GB)
"supportVnodes": 21, // 支持的最大 vnode 数
"version": "3.3.6.46", // 软件版本号
"dataDir": [{ // 数据目录列表(支持多磁盘)
"dir": "/var/lib/taos", // 数据目录路径
"level": 0, // 存储层级(0=高性能SSD, 1=SATA, 2=对象存储)
"disk_id": "64768", // 磁盘唯一标识
"primary": 1, // 是否为主数据目录
"disable": 0 // 是否禁用
}],
// ... 更多参数详见官网
}
}
stype.json
记录每个配置参数的来源:1 = 来自配置文件,0 = 自动生成/默认值。
json
{
"file_version": 1,
"config_stypes": {
"firstEp": 1, // 来自配置文件
"serverPort": 0, // 自动生成
"timezone": 1,
"numOfCores": 0,
// ...
}
}
mnode --- 管理节点目录
存储集群元数据信息,包括用户、数据库、超级表、订阅等所有集群级对象。
mnode.json
管理节点状态文件。
json
{
"lastIndex": -1, // 最后应用的日志索引
"deployed": 1 // 是否已部署(1=已部署)
}
完整字段(源码 mmFile.c):
selfIndex:当前节点在副本中的索引deployed:部署状态lastIndex:最后应用的日志索引replicas:副本节点列表(含id,fqdn,port,role)
data/sdb.data --- 集群元数据
二进制文件,存储整个集群的元数据(用户、数据库、超级表、流、订阅等)。
文件头格式
偏移 大小 字段 说明
------ ------ ------------------ ------
0 8 sver 文件版本(=1)
8 8 applyIndex Raft 应用索引
16 8 applyTerm Raft 应用任期
24 8 applyConfig Raft 配置索引
32 8*24 maxId[24] 各表类型最大 ID
224 8*24 tableVer[24] 各表类型版本号
416 ... 预留空间(零填充至 512 字节)
每条记录格式
SSdbRaw 头部(8 字节):
int8_t type - SDB 表类型(cluster/user/db/topic/stable/ctable 等)
int8_t status - 记录状态
int8_t sver - 模式版本
int8_t reserved - 保留
int32_t dataLen - pData 长度
pData[dataLen] - 变长数据(可选 SM4 加密)
int32_t checksum - 校验和(覆盖 SSdbRaw + pData)
源码位置:source/dnode/mnode/sdb/src/sdbFile.c
sync --- 同步目录
多副本同步信息。
raft_config.json
Raft 共识算法配置。
json
{
"RaftCfg": {
"SSyncCfg": {
"replicaNum": 1, // 副本数量
"myIndex": 0, // 当前节点索引
"changeVersion": 1, // 配置变更版本
"nodeInfo": [{ // 副本节点列表
"nodePort": 6030, // 节点端口
"nodeFqdn": "c3-65", // 节点 FQDN
"nodeId": "1", // 节点 ID
"clusterId": "1284994906254398242", // 所属集群 ID
"isReplica": "true" // 是否为正式副本(false=学习者)
}]
},
"isStandBy": 0, // 是否备用模式
"snapshotStrategy": 2, // 快照策略
"batchSize": 1, // 批量大小
"lastConfigIndex": "0", // 最后配置变更索引
"configIndexCount": 1,
"configIndexArr": [{"index": "-1"}]
},
"reason": "new"
}
raft_store.json
Raft 持久化状态。
json
{
"current_term": "1", // 当前任期号
"vote_for_addr": "0", // 投票给的节点地址("0"=未投票)
"vote_for_vgid": 0 // 投票给的虚拟组 ID
}
源码位置:source/libs/sync/src/syncRaftStore.c
wal --- 预写日志目录
WAL 文件命名规则
WAL 文件包括 .log 和 .idx 两种后缀,命名格式为 20 位零填充的版本号:
00000000000000021213.log ← 包含从版本 21213 开始的日志
00000000000000021213.idx ← 对应的索引文件
命名函数 (源码 walInt.h):
c
sprintf(buf, "%s%s%020" PRId64 ".log", wal->path, "/", firstVer);
sprintf(buf, "%s%s%020" PRId64 ".idx", wal->path, "/", firstVer);
数字是该文件中第一条日志的版本号(firstVer)。
.log 文件二进制格式
每条 WAL 记录由 SWalCkHead(校验头)+ body(载荷)组成,多条记录顺序追加。
每条记录布局:
偏移 大小 字段 说明
------ ------ -------------- ------
0 8 magic 魔数 0xFAFBFCFDF4F3F2F1
8 4 cksumHead SWalCont 的 CRC32 校验
12 4 cksumBody body 的 CRC32 校验
16 8 version 日志序列号(单调递增)
24 8 ingestTs 写入时间戳(微秒)
32 4 bodyLen 载荷长度
36 2 msgType 消息类型
38 1 protoVer 协议版本(=0)
39 1 syncMeta.isWeek 同步标记
40 8 syncMeta.seqNum 同步序列号
48 8 syncMeta.term Raft 任期号
56 bodyLen body 载荷数据(支持 SM4 加密)
魔数常量 :WAL_MAGIC = 0xFAFBFCFDF4F3F2F1(源码 wal.h)
读取流程 (源码 walRead.c):
- 在
.idx文件中定位:偏移 =(ver - firstVer) * 16 - 读取
SWalIdxEntry(16 字节:ver+offset) - 在
.log文件中跳转到offset - 读取并校验
cksumHead - 读取
bodyLen字节(加密时为ENCRYPTED_LEN(bodyLen)) - 校验
cksumBody
.idx 文件二进制格式
纯数组结构,每条记录 16 字节:
偏移(相对于 ver) 大小 字段
------------------- ------ ------
(ver - firstVer) * 16
+0 8 ver 版本号
+8 8 offset 在 .log 文件中的字节偏移
meta-ver 文件
WAL 元数据文件,JSON 格式,文件名如 meta-ver9、meta-ver234。同一时刻只存在一个,更新时采用原子写入(写 tmp → 重命名为 meta-ver(N+1) → 删除旧的 meta-ver(N))。
json
{
"meta": {
"firstVer": "21213", // WAL 中最早的版本号
"snapshotVer": "23212", // 快照版本号
"commitVer": "23212", // 已提交版本号
"lastVer": "23212", // 最新版本号
"keepVersion": "-1" // 保留版本(-1=不保留)
},
"files": [
{
"firstVer": "21213", // 该文件第一个版本
"lastVer": "21412", // 该文件最后一个版本
"createTs": "1775800000000", // 创建时间戳
"closeTs": "1775800001000", // 关闭时间戳
"fileSize": "317456" // 文件大小(字节)
},
// ... 每个 .log/.idx 文件对对应一个条目
]
}
WAL 文件生命周期
创建 :walOpen() 时创建 WAL 目录和初始 00000000000000000000.log/.idx。
轮转 :触发条件(源码 walWrite.c):
- 时间驱动 :超过
rollPeriod秒 - 大小驱动 :超过
segSize字节 - 轮转时关闭当前文件,创建以
lastVer + 1命名的新文件
清理 :walEndSnapshot() 根据快照版本和保留策略删除旧文件:
- 计算安全修剪版本:
trimVer = min(snapshotVer - logRetention, ...) - 删除
lastVer <= trimVer的文件对 - 考虑订阅者引用(
refVer)和保留版本(keepVersion)
WAL 级别 (wal.level):
0 (SKIP):不写 WAL,关闭时删除所有文件1 (WRITE):写入但不周期性 fsync2 (FSYNC):写入且后台线程每秒 fsync
源码位置:source/libs/wal/
vnode --- 虚拟节点目录
存储业务数据,每个 vnode 对应一个数据库的分片。
vnodes.json
虚拟节点注册表,位于 <dataDir>/vnode/vnodes.json,是所有 vnode 的索引。
json
{
"vnodes": [
{
"vgId": 2, // 虚拟组 ID(对应目录名 vnode2)
"dropped": 0, // 是否已删除
"vgVersion": 1, // 虚拟组版本
"diskPrimary": 0, // 主磁盘索引
"toVgId": 0 // 迁移目标组 ID(非零时存在)
},
// ...
]
}
源码位置:source/dnode/mgmt/mgmt_vnode/src/vmFile.c
vnode.json
每个 vnode 的配置和状态文件,位于 <dataDir>/vnode<N>/vnode.json。包含两个顶层对象。
config 对象
json
{
"config": {
"vgId": "2", // 虚拟组 ID(全局唯一)
"dbname": "1.test", // 所属数据库名
"dbId": "1286738618890580884", // 数据库唯一 ID
"szPage": "4096", // 缓存页大小(字节)
"szCache": "256", // 缓存页数量
"precision": "0", // 时间精度(0=毫秒, 1=微秒, 2=纳秒)
"daysPerFile": "14400", // 每个数据文件覆盖的时间跨度(分钟),14400=10天
"minRows": "100", // 数据块最少行数(不足时不落盘)
"maxRows": "4096", // 数据块最大行数
"keep0/1/2": "5256000", // 数据保留时间(分钟),5256000=10年;keep0=最热层, keep1=温层, keep2=冷层
"tsdbPageSize": "4096", // TSDB 数据页大小(字节)
"wal.fsyncPeriod": "3000", // WAL fsync 周期(毫秒),仅 wal.level=2 时生效
"wal.retentionPeriod": "3600", // WAL 文件保留时间(秒),快照后清理
"wal.level": "1", // WAL 级别(0=跳过, 1=写入, 2=写入+fsync)
"sstTrigger": "2", // STT 文件触发阈值,达到此数触发合并;1=直接写 .data
"hashBegin": "0", // 哈希范围起始值(表分配到此 vnode 的哈希区间)
"hashEnd": "2147483646", // 哈希范围结束值
"syncCfg.replicaNum": "1", // 副本数量(1=单副本, 3=三副本)
"vndStats.stables": "2", // 超级表数量统计
"vndStats.ctables": "5004", // 子表(child table)数量统计
"vndStats.ntables": "1", // 普通表数量统计
"vndStats.timeseries": "15012" // 时间线总数(所有表的列数之和)
}
}
完整字段还包括:cacheLast, cacheLastSize, isHeap, isWeak, isTsma, isRsma, update, compression, slLevel, retentions[], s3ChunkSize, s3KeepLocal, s3Compact, tsdb.encryptAlgorithm, tdbEncryptAlgorithm, wal.vgId, wal.rollPeriod, wal.retentionSize, wal.segSize, wal.clearFiles, wal.encryptAlgorithm, hashChange, hashMethod, hashPrefix, hashSuffix, syncCfg.myIndex, syncCfg.changeVersion, syncCfg.nodeInfo[], vndStats.ntimeseries。
state 对象
json
{
"state": {
"commit version": "229461", // 已提交版本号
"commit ID": "1086", // 提交 ID(递增计数器)
"commit term": "1" // Raft 任期号
}
}
源码位置:source/dnode/vnode/src/vnd/vnodeCfg.c, vnodeCommit.c
meta --- 元数据目录
main.tdb
TDB(Taos DataBase)是 TDengine 内嵌的页式 B-tree 存储引擎,用于存储 vnode 的表元数据(表名、列定义、schema 版本等)。
核心特性:
- 页大小:512 字节 ~ 16MB,默认 4096 字节
- 页标识:
SPgid = {fileid[24字节], pgno[u32]} - 数据结构:B-tree 索引,由 Pager 管理页缓存
- 每个页包含:页头 → cell 索引区 → 数据区 → 4 字节校验尾
- cell 格式:key长度 + key数据 + value长度 + value数据 + 溢出页链接(大值时)
关联文件:
main.tdb--- 主数据库文件tdb.journal--- 日志文件(崩溃恢复用)
TDB 还用于 tq/stream/main.tdb(流计算元数据)和 tq/subscribe/main.tdb(订阅元数据)。
源码位置:source/libs/tdb/
sync --- 同步目录
与 mnode 的 sync 目录结构相同,包含 raft_config.json 和 raft_store.json。
tsdb --- 时序数据目录
这是 TDengine 的核心数据存储目录,存放所有时间序列数据。
current.json --- 数据文件集清单
TSDB 的权威文件目录,记录所有数据文件的信息。采用 crash-safe 的提交协议,存在三个变体:
current.json--- 已提交的活跃状态current.c.json--- 提交操作的暂存文件(成功后重命名为 current.json)current.m.json--- 合并操作的暂存文件
json
{
"fmtv": 1, // 格式版本(当前为 1)
"fset": [{ // 文件集数组,每个 fid 对应一个时间桶
"fid": 1736, // 文件集 ID(= 时间窗口编号,由 daysPerFile 计算)
"head": { // .head 文件(BRIN 索引)
"fid": 1736,
"cid": 1487, // 提交 ID(每次写入递增)
"size": 879375, // 文件大小(字节),约 859 KB
"minVer": 5005, // 文件中数据的最小版本号
"maxVer": 228881 // 文件中数据的最大版本号
},
"data": { // .data 文件(实际数据块)
"fid": 1736,
"cid": 3, // 提交 ID(head 的 cid 可能不同,因为 head 在每次提交都更新)
"size": 7025980554, // 文件大小(字节),约 6.6 GB
"minVer": 5005,
"maxVer": 228881
},
"sma": { // .sma 文件(预聚合数据)
"fid": 1736,
"cid": 3, // 与 .data 文件同步更新
"size": 23302466, // 文件大小(字节),约 23 MB
"minVer": 5005,
"maxVer": 228881
},
"stt lvl": [{ // STT 合并文件(按层级组织,最多 3 层:0/1/2)
"level": 0, // 层级(0=最新, 1=中间层, 2=最老层)
"files": [
{"cid": 1486, "size": 6513687, "minVer": 228883, "maxVer": 229026}, // cid=提交ID, size=文件大小(字节)
{"cid": 1488, "size": 6489610, "minVer": 229028, "maxVer": 229170}, // minVer~maxVer=版本范围
{"cid": 1489, "size": 6488909, "minVer": 229172, "maxVer": 229316},
{"cid": 1490, "size": 6478330, "minVer": 229318, "maxVer": 229460}
]
}],
"last compact": 0, // 上次压缩时间
"last commit": 1775809376810 // 上次提交时间戳
}]
}
文件条目字段 (源码 tsdbFile2.c):did.level(磁盘层级)、did.id(磁盘 ID)、lcn(最后块号)、fid、cid、size、minVer、maxVer。
源码位置:source/dnode/vnode/src/tsdb/tsdbFS2.c
TSDB 数据文件命名规则
命名格式:v{VNODE_ID}f{FID}ver{CID}.{TYPE}
例如 v2f1736ver3.data:
v2--- vnode ID = 2f1736--- FID(文件集 ID / 时间桶编号),映射到一个时间窗口。fid通过daysPerFile计算:minKey = ticksPerMin * fid * minutes,每个 fid 覆盖固定时长ver3--- CID(提交 ID),每次提交递增.data--- 文件类型
文件类型 (源码 tsdbFile2.h):
| 后缀 | 枚举值 | 用途 |
|---|---|---|
.head |
TSDB_FTYPE_HEAD=0 |
BRIN 块索引(块元数据) |
.data |
TSDB_FTYPE_DATA=1 |
实际时间序列数据块 |
.sma |
TSDB_FTYPE_SMA=2 |
预聚合数据(min/max/sum/null-count) |
.tomb |
TSDB_FTYPE_TOMB=3 |
墓碑/删除记录 |
.stt |
TSDB_FTYPE_STT=5 |
STT 合并文件 |
.head 文件 --- BRIN 索引
存储 BRIN(Block Range Index) ,将表 ID 和版本范围映射到 .data 和 .sma 文件中的数据块。
文件布局:
[文件头: 512 字节零填充]
[BRIN Block 1: 压缩的列式数据]
[BRIN Block 2: ...]
...
[BRIN Block 索引数组: SBrinBlk 结构体数组]
[SHeadFooter: 文件尾部]
SHeadFooter(文件末尾,48 字节):
c
typedef struct {
SFDataPtr brinBlkPtr[1]; // BRIN 块索引数组的位置和大小 {offset, size}
char rsrvd[32]; // 预留字段,对齐到 48 字节
} SHeadFooter; // 总大小 48 字节,位于 .head 文件末尾
SBrinBlk(BRIN 块索引条目):
c
typedef struct {
SFDataPtr dp[1]; // 指向压缩 BRIN 数据的位置 {offset, size}
TABLEID minTbid; // 该块中最小表 ID {suid, uid},用于过滤
TABLEID maxTbid; // 该块中最大表 ID
int64_t minVer; // 块中最小数据版本号
int64_t maxVer; // 块中最大数据版本号
int32_t numRec; // 块中包含的 BRIN 记录数
int32_t size[15]; // 15 个列式压缩数组的大小(字节)
int8_t cmprAlg; // 压缩算法(LZ4/ZSTD 等)
int8_t numOfPKs; // 主键列数量
int8_t rsvd[6]; // 预留对齐
} SBrinBlk;
15 个压缩数组存储(依次):
- 10 个
int64数组:suids, uids, firstKey时间戳, firstKey版本, lastKey时间戳, lastKey版本, minVers, maxVers, blockOffsets, smaOffsets - 5 个
int32数组:blockSizes, blockKeySizes, smaSizes, numRows, counts
SBrinRecord(解码后的单条 BRIN 记录):
c
typedef struct {
int64_t suid; // 超级表 ID(普通表为 0)
int64_t uid; // 表 ID
STsdbRowKey firstKey; // 块中第一行的 key(时间戳+主键)
STsdbRowKey lastKey; // 块中最后一行的 key(时间戳+主键)
int64_t minVer; // 块中最小版本号(用于 MVCC)
int64_t maxVer; // 块中最大版本号
int64_t blockOffset; // 数据块在 .data 文件中的字节偏移
int64_t smaOffset; // SMA 数据在 .sma 文件中的字节偏移
int32_t blockSize; // 数据块总大小(key部分 + value部分)
int32_t blockKeySize; // 数据块 key 部分大小(用于分离 key/value)
int32_t smaSize; // SMA 数据大小(字节)
int32_t numRow; // 块中包含的行数
int32_t count; // 有效数据计数(过滤掉删除行)
} SBrinRecord;
源码位置:source/dnode/vnode/src/tsdb/tsdbUtil2.h, tsdbDataFileRW.c
.data 文件 --- 数据块
存储实际的时间序列数据,按块组织。
文件布局:
[文件头: 512 字节零填充 (TSDB_FHDR_SIZE)]
[数据块 1: SDiskDataHdr + key部分 + 列元数据 + 列数据...]
[数据块 2: ...]
...
SDiskDataHdr(数据块头,每个块开头):
c
struct SDiskDataHdr {
uint32_t delimiter; // 魔数 0xF00AFA0F (TSDB_FILE_DLMT),用于校验块边界
uint32_t fmtVer; // 格式版本(0=旧版, 1=支持主键)
int64_t suid; // 超级表 ID(普通表为 0)
int64_t uid; // 子表/普通表 ID
int32_t szUid; // UID 列压缩后的字节大小
int32_t szVer; // 版本列压缩后的字节大小
int32_t szKey; // 时间戳列压缩后的字节大小
int32_t szBlkCol; // SBlockCol 元数据压缩后的字节大小
int32_t nRow; // 块中包含的行数
uint32_t cmprAlg; // 压缩算法标识(LZ4/ZSTD/XZ 等)
int8_t numOfPKs; // 主键列数量(仅 fmtVer==1 时有效)
SBlockCol primaryBlockCols[TD_MAX_PK_COLS]; // 主键列的 SBlockCol 描述符数组
};
SBlockCol(列描述符):
c
struct SBlockCol {
int16_t cid; // 列 ID(schema 中的列编号)
int8_t type; // 数据类型(TSDB_DATA_TYPE_INT/TIMESTAMP/FLOAT 等)
int8_t cflag; // 列标志(COL_SMA_ON=启用SMA, COL_HAS_NULL 等)
int8_t flag; // 数据标记:HAS_NONE=全空, HAS_NULL=含NULL, HAS_VALUE=有值
int32_t szOrigin; // 原始未压缩大小(变长类型如 BINARY/NCHAR)
int32_t szBitmap; // NULL 位图大小(字节),0 表示无 NULL
int32_t szOffset; // 变长列偏移数组大小(定长列为 0)
int32_t szValue; // 列值数据压缩后的大小(字节)
int32_t offset; // 该列数据在 value 部分的起始偏移(从 key 部分末尾算起)
uint32_t alg; // 列级压缩算法(可与块级算法不同)
};
每个数据块的内部布局:
- key 部分 :
SDiskDataHdr+ 压缩的 UID 列 + 压缩的版本列 + 压缩的时间戳列 + 主键列 - value 部分:各列的位图 + 偏移数组 + 压缩的值数据
通过 SBrinRecord 的 blockOffset + blockKeySize + blockSize 定位和读取。
源码位置:source/dnode/vnode/src/tsdb/tsdbDataFileRW.c
.sma 文件 --- 预聚合数据
存储每个数据块的列级聚合信息(min、max、sum、null 计数),用于加速聚合查询。
文件布局:
[文件头: 512 字节零填充]
[SMA 数据块 1]
[SMA 数据块 2]
...
SColumnDataAgg(每列的聚合信息,28 字节紧凑存储):
c
#pragma pack(push, 1) // 1字节对齐,确保跨平台二进制一致
typedef struct SColumnDataAgg {
int32_t colId; // 列 ID(对应 schema 中的列编号)
int16_t numOfNull; // 该数据块中此列的 NULL 值计数
union {
struct {
int64_t sum; // 求和(整型/浮点型统一用 int64 存储)
int64_t max; // 最大值
int64_t min; // 最小值
};
struct { // decimal128 类型专用布局(128位精度)
uint64_t decimal128Sum[2]; // 128 位求和
uint64_t decimal128Max[2]; // 128 位最大值
uint64_t decimal128Min[2]; // 128 位最小值
uint8_t overflow; // 溢出标记
};
};
} SColumnDataAgg; // 总大小 28 字节(pack=1)
#pragma pack(pop)
序列化格式(varint 编码):colId(varint32) + numOfNull(varint16) + sum(int64) + max(int64) + min(int64)。
通过 SBrinRecord 的 smaOffset + smaSize 定位。
源码位置:include/common/tcommon.h, source/dnode/vnode/src/tsdb/tsdbUtil.c
.stt 文件 --- STT 合并文件
STT(Sorted Time-series Tree)是用于增量写入和后台合并的中间文件格式。当 sstTrigger > 1 时,新写入的数据首先进入 STT 文件,而不是直接写入 .data 文件。
文件布局:
[文件头: 512 字节零填充]
[BlockData #1: 压缩的列数据]
[BlockData #2: ...]
...
[Statistic blocks: 压缩的列统计信息]
[Tomb blocks: 删除记录]
[SttBlk 索引数组: SSttBlk 结构体数组]
[StatisBlk 索引数组]
[TombBlk 索引数组]
[SSttFooter: 文件末尾]
SSttFooter(文件末尾,80 字节):
c
typedef struct {
SFDataPtr sttBlkPtr[1]; // SttBlk 索引数组的位置 {offset, size}
SFDataPtr statisBlkPtr[1]; // StatisBlk(列统计)索引数组的位置
SFDataPtr tombBlkPtr[1]; // TombBlk(删除标记)索引数组的位置
SFDataPtr rsrvd[2]; // 预留字段(对齐到 80 字节)
} SSttFooter; // 总大小 80 字节,位于 .stt 文件末尾
SSttBlk(STT 块索引条目):
c
struct SSttBlk {
int64_t suid; // 超级表 ID(同一 super table 的数据归组)
int64_t minUid; // 块中最小子表 UID(用于表范围过滤)
int64_t maxUid; // 块中最大子表 UID
TSKEY minKey; // 块中最小时间戳(用于时间范围过滤)
TSKEY maxKey; // 块中最大时间戳
int64_t minVer; // 块中最小版本号
int64_t maxVer; // 块中最大版本号
int32_t nRow; // 块中总行数
SBlockInfo bInfo; // 数据块位置信息 {offset, szBlock, szKey}
};
合并机制 (源码 tsdbMerge.c):
- STT 文件按层级组织:Level 0 → Level 1 → Level 2
- 当 Level 0 的 STT 文件数达到
sstTrigger时,触发向上合并 - 超过 Level 2 时,强制合并回
.data文件 sstTrigger == 1时,数据直接写入.data文件,不经过 STT
源码位置:source/dnode/vnode/src/tsdb/tsdbSttFileRW.c, tsdbMerge.c
cache.rdb --- RocksDB 缓存
确认使用 RocksDB 作为底层存储(源码 tsdbCache.c 调用 rocksdb_open())。
用途 :缓存每个 (table, column) 的 last-value 和 last-row-value ,加速 LAST() 和 LAST_ROW() 查询,避免扫描数据文件。
Key 格式(11 字节):
c
typedef struct {
tb_uid_t uid; // 表 UID(8 字节,全局唯一标识)
int16_t cid; // 列 ID(2 字节,schema 中的列编号)
int8_t lflag; // 缓存类型标记:0=LAST_ROW(最后写入行), 1=LAST(最新有效值)
} SLastKey; // 总大小 11 字节(ROCKS_KEY_LEN)
写入策略:
- 使用
rocksdb_writebatch_t批量写入 - RocksDB WAL 已禁用(
disable_WAL = 1) - 批量达到 4096 条时刷盘
目录结构(标准 RocksDB):
cache.rdb/
├── IDENTITY # UUID 标识(如 e195d95b-7dd9-4900-8c2f-79b634ff8e14)
├── CURRENT # 指向当前 MANIFEST 文件
├── MANIFEST-000005 # 二进制 MANIFEST(操作日志)
├── OPTIONS-000007 # RocksDB 配置(INI 格式,可读)
├── LOG # 运行日志
├── 000004.log # RocksDB WAL
└── LOCK # 文件锁
关键配置(从 OPTIONS 文件):
write_buffer_size = 67108864(64 MB)max_bytes_for_level_base = 268435456(256 MB)target_file_size_base = 67108864(64 MB)compression = kNoCompressionlevel0_file_num_compaction_trigger = 4
源码位置:source/dnode/vnode/src/tsdb/tsdbCache.c
wal --- vnode 预写日志
格式与 mnode 的 WAL 完全相同。详细格式见上文 [wal --- 预写日志目录](#wal — 预写日志目录) 章节。
附录:关键常量速查
| 常量 | 值 | 说明 |
|---|---|---|
WAL_MAGIC |
0xFAFBFCFDF4F3F2F1 |
WAL 记录魔数 |
TSDB_FILE_DLMT |
0xF00AFA0F |
TSDB 数据块分隔符 |
TSDB_FHDR_SIZE |
512 |
TSDB 文件头大小(字节) |
WAL_NOSUFFIX_LEN |
20 |
WAL 文件名数字部分长度 |
SWalIdxEntry |
16 字节 | WAL 索引条目大小(ver + offset) |
SWalCkHead |
56 字节(不含 body) | WAL 记录头大小 |
SColumnDataAgg |
28 字节 | SMA 列聚合结构大小 |
SLastKey |
11 字节 | RocksDB 缓存 key 大小 |
ROCKS_BATCH_SIZE |
4096 | RocksDB 批量写入阈值 |
TSDB_MAX_LEVEL |
2 | STT 最大层级数 |
附录:源码文件索引
| 模块 | 关键文件 | 功能 |
|---|---|---|
| WAL | include/libs/wal/wal.h |
公共 API、结构体定义 |
| WAL | source/libs/wal/inc/walInt.h |
内部头文件、索引结构 |
| WAL | source/libs/wal/src/walWrite.c |
写入、轮转、回滚、快照清理 |
| WAL | source/libs/wal/src/walRead.c |
按版本号读取 |
| WAL | source/libs/wal/src/walMeta.c |
meta-ver 文件 I/O |
| WAL | source/libs/wal/src/walMgmt.c |
模块初始化、walOpen/walClose |
| SDB | source/dnode/mnode/sdb/src/sdbFile.c |
sdb.data 读写 |
| SDB | source/dnode/mnode/sdb/inc/sdb.h |
SSdbRaw 结构体 |
| TSDB | source/dnode/vnode/src/tsdb/tsdbFile2.h |
STFile 结构体、文件类型枚举 |
| TSDB | source/dnode/vnode/src/tsdb/tsdbFile2.c |
文件命名、JSON 序列化 |
| TSDB | source/dnode/vnode/src/tsdb/tsdbDataFileRW.c |
.head/.data/.sma 读写 |
| TSDB | source/dnode/vnode/src/tsdb/tsdbSttFileRW.c |
.stt 文件读写 |
| TSDB | source/dnode/vnode/src/tsdb/tsdbFS2.c |
current.json 管理 |
| TSDB | source/dnode/vnode/src/tsdb/tsdbCache.c |
cache.rdb RocksDB 缓存 |
| TSDB | source/dnode/vnode/src/tsdb/tsdbMerge.c |
STT 合并逻辑 |
| TSDB | source/dnode/vnode/src/tsdb/tsdbUtil2.h |
SBrinRecord, SBrinBlk |
| TSDB | source/dnode/vnode/src/inc/tsdb.h |
SDiskDataHdr, SBlockCol |
| TDB | source/libs/tdb/src/inc/tdbInt.h |
TDB 引擎核心结构体 |
| Sync | source/libs/sync/src/syncRaftCfg.c |
raft_config.json 读写 |
| Sync | source/libs/sync/src/syncRaftStore.c |
raft_store.json 读写 |
| Vnode | source/dnode/vnode/src/vnd/vnodeCfg.c |
vnode.json config 编解码 |
| Vnode | source/dnode/vnode/src/vnd/vnodeCommit.c |
vnode.json state 编解码 |
| Vnode | source/dnode/mgmt/mgmt_vnode/src/vmFile.c |
vnodes.json 读写 |
| Mnode | source/dnode/mgmt/mgmt_mnode/src/mmFile.c |
mnode.json 读写 |