TDengine3.x 数据文件详解

目录

    • 数据文件整体目录结构
    • [dnode --- 数据节点目录](#dnode — 数据节点目录)
    • [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):

  1. .idx 文件中定位:偏移 = (ver - firstVer) * 16
  2. 读取 SWalIdxEntry(16 字节:ver + offset
  3. .log 文件中跳转到 offset
  4. 读取并校验 cksumHead
  5. 读取 bodyLen 字节(加密时为 ENCRYPTED_LEN(bodyLen)
  6. 校验 cksumBody
.idx 文件二进制格式

纯数组结构,每条记录 16 字节:

复制代码
偏移(相对于 ver)    大小    字段
-------------------  ------  ------
(ver - firstVer) * 16
  +0                 8       ver     版本号
  +8                 8       offset  在 .log 文件中的字节偏移
meta-ver 文件

WAL 元数据文件,JSON 格式,文件名如 meta-ver9meta-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):写入但不周期性 fsync
  • 2 (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.jsonraft_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(最后块号)、fidcidsizeminVermaxVer

源码位置:source/dnode/vnode/src/tsdb/tsdbFS2.c

TSDB 数据文件命名规则

命名格式:v{VNODE_ID}f{FID}ver{CID}.{TYPE}

例如 v2f1736ver3.data

  • v2 --- vnode ID = 2
  • f1736 --- 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 部分:各列的位图 + 偏移数组 + 压缩的值数据

通过 SBrinRecordblockOffset + 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)

通过 SBrinRecordsmaOffset + 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-valuelast-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 = kNoCompression
  • level0_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 读写
相关推荐
csgo打的菜又爱玩2 小时前
5.HeartbeatServices启动解析.md
大数据·flink·源代码管理
老神在在0012 小时前
商城系统(Mall)性能测试实战:从脚本搭建到结果分析
大数据·测试工具·jmeter·压力测试
生万千欢喜心2 小时前
Linux 安装金蝶天燕中间件 AAS-V9.0.zip
java·linux
aq55356003 小时前
三大Linux系统终极对决
linux·运维·服务器
亚马逊云开发者3 小时前
【Bedrock AgentCore】Multi-Agent 架构实战:用 6 个 Agent 打通零售供应链数据→洞察→行动全链路
大数据·架构·零售
renhongxia13 小时前
网络效应与大型语言模型辩论中的协议漂移
大数据·人工智能·机器学习·语言模型·自然语言处理·语音识别·xcode
CeshirenTester3 小时前
计算机专业找工作别再乱投:100家常见目标公司,先按赛道分清楚,然后闭眼冲!
大数据·人工智能
sssjjww3 小时前
服务器不同路径下找conda
linux·运维·服务器
Rubin智造社3 小时前
OpenClaw实操指南20|记忆系统实战:别让你的AI用完就忘,短期+长期记忆配置指南
大数据·人工智能·用户画像·长期记忆·记忆系统·memory.md·openclaw实操