TDengine Commit 与 Flush 机制 — 从内存到磁盘的数据落盘全流程

分类 :3.存储引擎 | 篇章:05 Commit 与 Flush

适用版本:TDengine v3.x(v3.3.x / v3.4.x) | 最后更新:2026-05-30

Commit(提交/落盘)是 MemTable 数据持久化到 TSDB 文件的过程。这是存储引擎中连接内存层和磁盘层的关键环节------既要保证数据不丢失,又要控制写放大、避免阻塞前台写入。

核心概念速查表

概念 说明
Commit 将 Immutable MemTable 的数据写入 TSDB 文件
Flush Commit 的同义词(用户侧命令为 FLUSH DATABASE)
Swap MemTable Active→Immutable 的切换操作
STT 写入 直接追加到 STT 文件(快速落盘)
合并写入 与已有文件数据合并后写入新文件(有排序开销)
写放大 一份数据被反复重写的次数(合并导致)

详细解析

1. Commit 触发时机

触发条件 说明
Buffer 段写满 当前 Buffer Pool 段使用量达到 BUFFER/3
FLUSH DATABASE 命令 用户手动触发
VNode 关闭 正常关闭前刷盘未持久化数据
定时器 长时间无写入时也会触发(避免数据长期只在内存)

2. Commit 全流程

复制代码
Commit 全流程(8 步):

  ① Swap 切换:
     Active MemTable → Immutable MemTable
     新的空 Buffer 段成为 Active
     (前台写入继续,不阻塞)
       │
       ▼
  ② 遍历 Immutable MemTable 中的所有子表数据:
     按 RB-Tree 顺序(uid 有序)逐表处理
       │
       ▼
  ③ 对每张子表的数据:
     a. 按时间戳确定目标 FileSet(fid)
     b. 将数据按 fid 分组
       │
       ▼
  ④ 对每个目标 FileSet:
     ┌─ STT_TRIGGER > 1:
     │    直接写入 STT 文件(追加写)
     │    - 构建数据块(编码+压缩)
     │    - 追加到 .stt 文件
     │    - 更新 STT 块索引
     │
     └─ STT_TRIGGER = 1:
          与已有 .data 文件合并写入新文件
          - 读取旧文件相关块
          - 合并排序新旧数据
          - 写入新 .head + .data + .sma
          - 删除旧文件
       │
       ▼
  ⑤ 生成 .tomb 文件(如果有 DELETE 操作)
       │
       ▼
  ⑥ 更新 TSDB current 文件(原子更新文件集列表)
       │
       ▼
  ⑦ 截断已提交的 WAL
       │
       ▼
  ⑧ 释放 Immutable MemTable 内存
     Buffer Pool 段标记为 Free

3. STT 写入模式 vs 合并写入模式

复制代码
两种落盘策略对比:

  STT 模式(STT_TRIGGER > 1):
  
    MemTable 数据 → 直接追加到 STT 文件
    
    优点:
    ✓ 落盘速度快(纯追加写,无合并开销)
    ✓ 不读取旧文件(无读放大)
    ✓ 适合高频写入场景
    
    缺点:
    ✗ STT 文件累积 → 查询需要多路合并
    ✗ 需要后台 Compaction 来整理


  合并模式(STT_TRIGGER = 1):
  
    MemTable 数据 + 旧文件数据 → 合并排序 → 新文件
    
    优点:
    ✓ 数据始终有序(查询无需多路合并)
    ✓ 无 STT 堆积
    
    缺点:
    ✗ 落盘需要读旧文件(读放大)
    ✗ 写放大高(旧数据被重写)
    ✗ 落盘延迟高

4. 数据块构建过程

复制代码
从 MemTable 行数据到磁盘数据块的转换:

  MemTable 中的行数据(行式,未压缩)
       │
       ▼
  ① 行转列:将 N 行数据拆分为各列的值数组
     timestamp[] = [t1, t2, t3, ...]
     col1[] = [v1, v2, v3, ...]
     col2[] = [v1, v2, v3, ...]
       │
       ▼
  ② 构建 NULL Bitmap:标记每列的 NULL 位置
       │
       ▼
  ③ 类型特化编码(一阶压缩):
     - TIMESTAMP: Delta-of-Delta
     - INT/BIGINT: ZigZag + Simple8B
     - FLOAT/DOUBLE: XOR 差分
     - BINARY: 字典编码
       │
       ▼
  ④ 通用压缩(二阶压缩,COMP=2 时):
     - LZ4 或 ZSTD 对编码后的字节流压缩
       │
       ▼
  ⑤ 组装数据块:
     Block Header + Column Metadata + Column Data
       │
       ▼
  ⑥ 写入 .data 文件,记录偏移到 .head

5. 多 FileSet 的落盘分发

复制代码
乱序数据导致跨 FileSet 落盘:

  假设 DURATION = 10 天,当前时间在 FileSet 3

  MemTable 中的数据:
    - 99% 数据时间戳在 FileSet 3 范围内(正常)
    - 1% 数据时间戳在 FileSet 2 范围内(乱序/补报)
    
  Commit 时:
    FileSet 3: 接收 99% 数据 → 追加/合并
    FileSet 2: 接收 1% 数据 → 合并到已有文件(或追加 STT)
    
  乱序数据对 Commit 的额外开销:
    - 需要读取旧 FileSet 的索引
    - 可能触发旧 FileSet 的 Compaction

6. 原子性保证

复制代码
Commit 的原子性:

  TSDB 使用 "write-new-then-switch" 策略:
  
  ① 写入新文件(新的 .head/.data/.sma)
  ② 所有文件写完后 → fsync 确保持久化
  ③ 原子更新 current 文件(切换文件集指针)
  ④ 删除旧文件(如果是合并写入)
  
  崩溃恢复:
  - 如果在 ③ 之前崩溃 → 新文件被忽略(垃圾回收)
  - 如果在 ③ 之后崩溃 → 新文件生效
  - 不会出现半写入状态

7. Commit 期间的读写并发

复制代码
Commit 与读写的并发关系:

  写入操作:
    → 写入新的 Active MemTable(与 Commit 不冲突)
    
  查询操作:
    → 读 Active MemTable ✓
    → 读 Immutable MemTable ✓(只读,安全并发)
    → 读磁盘文件 ✓(Commit 写新文件,不修改旧文件)
    
  Commit 操作:
    → 读 Immutable MemTable(遍历数据)
    → 写新磁盘文件
    
  结论:前台读写与后台 Commit 完全并发,互不阻塞

8. FLUSH DATABASE 命令

sql 复制代码
-- 手动触发所有 VNode 的 Commit
FLUSH DATABASE power;

-- 使用场景:
-- 1. 维护前确保所有数据已落盘
-- 2. 测试/验证数据持久化
-- 3. 调试:确认数据已写入文件

代码示例

监控 Commit 状态

bash 复制代码
# 观察 taosd 日志中的 Commit 相关信息
grep -i "commit" /var/log/taos/taosdlog.0

# 典型日志:
# "vgId:2 start to commit, nRows:100000"
# "vgId:2 commit finished, elapsed:500ms"

调优参数

sql 复制代码
-- 减少 Commit 频率(增大 Buffer)
ALTER DATABASE power BUFFER 512;

-- 加快落盘速度(减少合并开销)
ALTER DATABASE power STT_TRIGGER 4;

-- 查看当前配置
SELECT * FROM information_schema.ins_databases WHERE name='power';

性能考量

Commit 耗时分析

阶段 典型耗时 瓶颈
Swap MemTable < 1ms CPU(指针交换)
遍历 MemTable 10~100ms CPU + 内存带宽
编码压缩 50~500ms CPU
磁盘写入 100~2000ms 磁盘 IOPS/带宽
fsync 0.1~10ms 磁盘
WAL 截断 < 10ms 磁盘

STT_TRIGGER 对性能的综合影响

STT_TRIGGER 写入延迟 查询延迟 写放大 磁盘 I/O
1 高(合并写) 最低
2(默认)
4
8~16 最低 高(多路合并) 最低 最低

FAQ

Q1: Commit 期间写入被阻塞了怎么办?

说明所有 Buffer Pool 段都被占满(一个在 Commit,另一个已满无法轮转)。解决:

  1. 增大 BUFFER 参数(增加缓冲空间)
  2. 检查磁盘 I/O 是否饱和(Commit 写盘慢)
  3. 增加 VGROUPS 分散单 VNode 的写入压力

Q2: FLUSH DATABASE 是同步还是异步?

异步。命令发出后立即返回,实际 Commit 在后台线程执行。如果需要确认 Flush 完成,可以通过日志观察或等待一段时间后检查文件。

Q3: 乱序数据多时 Commit 会变慢吗?

会。乱序数据需要写入旧的 FileSet,可能需要:

  • 读取旧文件的块索引
  • 与旧数据合并排序
  • 重写旧文件

如果乱序比例高,建议增大 STT_TRIGGER 让数据先追加到 STT,后台 Compaction 异步整理。

Q4: Commit 失败会丢数据吗?

不会。Immutable MemTable 的数据在 Commit 完成前不会释放,且 WAL 中仍有完整日志。如果 Commit 失败(如磁盘满),系统会重试。WAL 是最终的数据安全保障。

参考

系统构架篇

数据模型

存储引擎

关于 TDengine

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

相关推荐
GISer_Jing1 小时前
Claude Code多Agent架构深度剖析
前端·人工智能·架构·自动化
芝麻开门GEO1 小时前
2026年Q2济南企业如何选择可靠的GEO服务商
大数据·人工智能·python
KaMeidebaby2 小时前
卡梅德生物技术快报|Pull Down 实验在 lncRNA - 蛋白互作机制研究中的应用实例解析
大数据·前端·架构·spark·新浪微博
硅谷秋水2 小时前
世界动作模型:具身智能的下一前沿
大数据·人工智能·深度学习·计算机视觉·语言模型·机器人
ID_180079054732 小时前
(淘宝 / 京东)商品评论 API 接口:技术实战案例与架构分析
服务器·数据库·架构
爱莉希雅&&&2 小时前
Zabbix监控初步搭建
linux·运维·数据库·mysql·zabbix
狼与自由2 小时前
mysql到clickhouse
数据库·mysql·clickhouse
六月雨滴2 小时前
Oracle 数据库之归档日志
数据库·oracle·dba
土狗TuGou2 小时前
SQL内功笔记 · 第6篇:窗口函数的使用ROW_NUMBER等
java·数据库·后端·sql·mysql