TDengine 数据缓存机制全解析:写缓存、读缓存、元数据缓存与文件系统缓存
【免费下载链接】tdengineTDengine is an open source, high-performance, cloud native time-series database optimized for Internet of Things (IoT), Connected Cars, Industrial IoT and DevOps.项目地址: https://gitcode.com/taosdata/tdengine
TDengine 为 IoT / IIoT 高并发场景设计了一整套分层缓存体系,包括写缓存(Write Cache)、读缓存(Read Cache)、元数据缓存(Metadata Cache)与文件系统缓存(File System Cache)。本文基于 Data Caching 文档展开,结合仓库中的参数定义、架构说明与源码实现(如 tsdbCache.c、tsdbMemTable.c),系统讲解每种缓存的工作原理、关键配置参数、适用场景与调优策略。读完本文,你将能够根据业务特征(读写比例、实时性要求、可靠性要求)通过CREATE DATABASE/ALTER DATABASE精确配置VGROUPS、BUFFER、CACHEMODEL、CACHESIZE、PAGES、PAGESIZE、WAL_LEVEL与WAL_FSYNC_PERIOD,在性能与成本之间找到最佳平衡点。
缓存类型总览
TDengine 的缓存机制并非单一组件,而是由四类缓存协同构成,覆盖从数据写入、实时读取到元数据访问与持久化保障的完整链路:
| 类型 | 作用 | 关键参数 | 典型场景 | 延伸阅读 |
|---|---|---|---|---|
| 写缓存 | 优先把新写入的数据放在内存缓存中,达到阈值后批量落盘最早数据 | BUFFER、VGROUPS | 最近数据读写、写入吞吐 | BUFFER,下文 |
| 读缓存 | 缓存每个子表的最新数据以加速"当前值"查询 | CACHEMODEL、CACHESIZE | LAST、LAST_ROW | Read Cache,下文 |
| 元数据缓存 | 缓存 vnode 之前访问过的元数据 | PAGES、PAGESIZE | 元数据访问 | PAGES,下文 |
| 文件系统缓存 | WAL 顺序追加依赖文件系统缓存,fsync决定何时强制落盘 | WAL_LEVEL、WAL_FSYNC_PERIOD | 写性能与数据可靠性的权衡 | WAL_LEVEL,下文 |
下文分别深入剖析每一类缓存,并在相应小节补充仓库内的参数定义与源码级实现依据。
写缓存(Write Cache)
时间驱动(写驱动)的缓存管理策略
TDengine 采用一种创新的时间驱动缓存管理策略,也称写驱动缓存管理机制。这与传统"读驱动"缓存模型截然不同,其核心思路是:优先把新写入的数据保存在缓存中;当缓存容量达到预设阈值时,系统将最早写入的数据批量落盘,从而实现缓存与磁盘之间的动态平衡。
在 IoT 数据应用中,用户通常最关注最近生成的数据,即设备的"当前状态"。TDengine 充分利用了这一业务特征——把最新到达的当前状态数据优先存放在缓存中,使用户能够快速访问所需信息。从架构文档的表述看,TDengine 直接将新到达的数据存入缓存以快速响应查询与分析需求,从这一角度出发,合理设置数据库参数后,TDengine 本身就可以充当数据缓存层,无需再部署 Redis 等额外缓存系统,从而简化系统架构、降低维护成本。需要特别注意的是:TDengine 重启后缓存数据会被清空,全部批量落盘,与专业 KV 缓存系统重启后自动回填缓存的行为不同。
vnode / vgroup:分布式缓存的基本单元
为实现数据的分布式存储与高可用,TDengine 引入了虚拟节点(vnode)概念。每个 vnode 最多可有 3 个副本,多个副本共同构成一个 vnode 组(vgroup)。创建数据库时,用户需要为每个 vnode 确定写缓存大小,以保证数据分布的合理性与存储的高效性。
从源码结构看,每个 vnode 拥有独立的内存空间,被划分为多个固定大小的内存块,不同 vnode 之间的内存完全隔离(相关实现在 tsdbMemTable.c 与 tsdb.h 中)。写入过程采用类似日志的顺序追加方式,每个 vnode 同时维护自己的 SkipList 结构用于快速检索。当超过 1/3 的内存块被写满时,系统即启动数据落盘(flush)流程,并将新的写操作引导至新的内存块——这样 vnode 中始终保留约 1/3 的内存块给最新数据,既实现了缓存目的,又保证了查询效率。
关键参数:VGROUPS 与 BUFFER
创建数据库时的两个关键参数决定了数据库由多少 vgroup 承载数据、每个 vnode 分配多少写缓存:
VGROUPS:数据库的初始 vgroup 数量(见 VGROUPS)。BUFFER:单个 vnode 写入内存池的大小,单位 MB,默认 256,最小值 3,最大值 16384(见 BUFFER)。该参数正是架构文档中所描述的 vnode 内存空间大小配置入口。
以下 SQL 创建一个包含 10 个 vgroup、每个 vnode 使用 256MB 内存的数据库:
CREATE DATABASE POWER VGROUPS 10 BUFFER 256 CACHEMODEL 'NONE' PAGES 128 PAGESIZE 16;调优要点:缓存并非越大越好。缓存越大虽然写入性能越好,但超过一定阈值后,继续增大缓存对写入性能的提升将不再明显。应根据数据量、机器内存与写入模型综合确定,具体参数含义可参考 VGROUPS 与 BUFFER。
读缓存(Read Cache)
面向"当前值"查询的专用缓存
读缓存机制专为高频率实时查询场景设计,尤其适合需要实时掌握设备状态的 IoT / IIoT 业务——这类场景中用户最关心的往往是最新数据,例如设备的当前读数或状态。读缓存将每个子表的最新数据缓存在内存中,在缓存命中时,LAST/LAST_ROW查询无需再从磁盘读取历史数据,从而显著降低查询响应延迟并缓解存储系统 I/O 压力。
CACHEMODEL:四种缓存模式
通过cachemodel参数,用户可以灵活选择缓存模式。下表完整列出四种模式及其语义:
CACHEMODEL | 缓存内容 | 主要加速对象 |
|---|---|---|
none | 不缓存(默认值) | — |
last_row | 每个子表最近一行数据 | LAST_ROW |
last_value | 每列最近的非 NULL 值 | 不受WHERE、ORDER BY、GROUP BY、INTERVAL等影响的LAST |
both | 同时缓存最近一行与最近各列值 | 上述条件下的LAST_ROW与LAST |
注意:频繁切换
CACHEMODEL可能导致LAST/LAST_ROW结果短暂不准确,请谨慎操作,建议保持开启状态(来源:CACHEMODEL 的注意事项)。此外,带过滤、排序、分组或窗口的LAST查询往往无法充分利用last_value缓存;启用读缓存会在写路径上维护缓存,可能影响写入性能,高吞吐场景可将both调整为last_row或last_value,参见 Ingesting Data Efficiently。
CACHESIZE:缓存容量
CACHESIZE设置每个 vnode 用于缓存子表最新数据的内存大小,默认 1,范围 [1, 65536],单位 MB(见 CACHESIZE)。应按机器内存与表规模合理设置,容量是否足够的判定方法可参考 Modify CACHESIZE。
LRU 与懒加载机制
架构文档(Architecture: last/last_row Cache)给出了读缓存的底层设计:TDengine 为最新行 / 最新非 NULL 值提供LRU 缓存,采用懒加载方式——对某张表的首次查询会从内存池和磁盘读取所需值存入 LRU 缓存并返回查询模块;后续插入或删除会按需更新已有缓存条目;未被缓存的表的写入不会强制将其加载进缓存。同时:
- 变更缓存配置会同步更新缓存数据:启用缓存后首次查询触发加载,禁用则释放已分配的缓存。
- 针对单个子表的查询只加载该子表;针对超级表的查询可能加载其全部子表。
- 可用
SHOW VGROUPS查看每个 vnode 的cacheload列,以字节为单位观察缓存内存占用。
CACHESHARDBITS:并发访问的锁粒度
对于高并发场景,还可通过CACHESHARDBITS控制 last-value LRU 缓存的分片数(内部锁粒度)。默认 -1(自动计算),范围 [-1, 19],实际分片数等于2^CACHESHARDBITS。自动计算规则为:每个分片至少 512KB,理论最大分片数 =CACHESIZE / 512KB,分片位数 =floor(log₂(理论最大分片数)),上限为 6(即最多 64 个分片);当CACHESIZE < 512KB时分片位数为 0(单分片)。示例如下:
| CACHESIZE | 理论最大分片数 | 分片位数 | 实际分片数 |
|---|---|---|---|
| 1 MB | 2 | 1 | 2 |
| 4 MB | 8 | 3 | 8 |
| 32 MB | 64 | 6 | 64 |
| 256 MB | 512 | 6(封顶) | 64 |
分片越多,并发写缓存的锁竞争越小,适合高并发场景,但过多分片也会增加内存管理开销。警告:修改CACHESHARDBITS会立即失效数据库中所有 vnode 的全部 last-value 缓存条目,后续查询需要从磁盘重新加载,可能暂时抬高查询延迟(见 CACHESHARDBITS)。
创建与调整示例
读缓存可通过CREATE DATABASE设置,也可通过ALTER DATABASE动态调整:
-- 建库时开启 CREATE DATABASE power CACHEMODEL 'both' CACHESIZE 16; -- 对已有库开启或调整 ALTER DATABASE power CACHEMODEL 'both'; ALTER DATABASE power CACHESIZE 32;启用后可用SHOW CREATE DATABASE确认参数生效,并用SHOW VGROUPS查看各 vnode 的cacheload(当前 last-cache 使用字节数)。
实战验证:智能电表场景的 LAST / LAST_ROW 加速
以下示例对比启用读缓存前后LAST/LAST_ROW的查询延迟。先用taosBenchmark生成测试数据:
taosBenchmark -d power -Q --start-timestamp=1600000000000 --tables=10000 --records=10000 --time-step=10000 -y该命令创建数据库power与超级表meters,约 1 亿行数据:10000 个子表、每个子表 10000 行、起始时间戳1600000000000(即2020-09-13T20:26:40+08:00)、间隔 10 秒。此时默认CACHEMODEL为none。
未开启读缓存时的查询:
taos> SELECT LAST(ts, current) FROM meters; last(ts) | last(current) | ================================================= 2020-09-13 20:26:40.000 | 1.1294620 | Query OK, 1 row(s) in set (0.353815s) taos> SELECT LAST_ROW(ts, current) FROM meters; last_row(ts) | last_row(current) | ================================================= 2020-09-13 20:26:40.000 | 1.1294620 | Query OK, 1 row(s) in set (0.344070s)开启读缓存并确认生效:
taos> ALTER DATABASE power CACHEMODEL 'both'; Query OK, 0 row(s) affected (0.046092s) taos> SHOW CREATE DATABASE power\G; *************************** 1.row *************************** Database: power Create Database: CREATE DATABASE `power` BUFFER 256 CACHESIZE 1 CACHEMODEL 'both' COMP 2 DURATION 14400m WAL_FSYNC_PERIOD 3000 MAXROWS 4096 MINROWS 100 STT_TRIGGER 2 KEEP 5256000m,5256000m,5256000m PAGES 256 PAGESIZE 4 PRECISION 'ms' REPLICA 1 WAL_LEVEL 1 VGROUPS 10 ... Query OK, 1 row(s) in set (0.000282s)再次查询(首次查询填充缓存,之后的查询通常延迟显著降低):
taos> SELECT LAST(ts, current) FROM meters; last(ts) | last(current) | ================================================= 2020-09-13 20:26:40.000 | 1.1294620 | Query OK, 1 row(s) in set (0.044021s) taos> SELECT LAST_ROW(ts, current) FROM meters; last_row(ts) | last_row(current) | ================================================= 2020-09-13 20:26:40.000 | 1.1294620 | Query OK, 1 row(s) in set (0.046682s)本示例中延迟从约 353 / 344 ms 降至约 44 ms。实际结果取决于数据规模、硬件与并发负载。更多细节见 Read Cache,LAST/LAST_ROW语义见 LAST 与 LAST_ROW。
元数据缓存(Metadata Cache)
为提升查询与写入效率,每个 vnode 都配备了一个缓存机制,用于存放其先前访问过的元数据。元数据缓存的大小由建库时设置的两个参数决定:
PAGES:vnode 元数据存储引擎的缓存页数量,默认 256,最小 64。一个 vnode 的元数据存储占用PAGESIZE * PAGES,默认即 1MB 内存(见 PAGES)。PAGESIZE:vnode 元数据存储引擎的页大小,单位 KB,默认 4 KB,范围 1~16384(即 1KB 到 16MB)(见 PAGESIZE)。
以下 SQL 为数据库power中每个 vnode 创建 128 页、每页 16KB 的元数据缓存:
CREATE DATABASE POWER PAGES 128 PAGESIZE 16;元数据缓存的默认值与取值范围以 PAGES 与 PAGESIZE 为准。
文件系统缓存与 WAL
WAL:数据可靠性的基础保障
TDengine 使用 WAL(Write-Ahead Logging,预写日志)技术作为基础的数据可靠性保障。其核心原理是:在数据真正写入数据存储层之前,先记录到日志文件中。这样,即使集群遭遇崩溃或其他故障,数据安全仍能得到保证——TDengine 利用这些日志文件在故障后恢复失败前的状态。
在 WAL 写入过程中,数据以顺序追加方式写入磁盘文件,因此文件系统缓存在这一过程中扮演关键角色,对写入性能有显著影响。为确保数据真正写入磁盘,系统会调用fsync函数,将数据从文件系统缓存强制写入磁盘。
WAL_LEVEL 与 WAL_FSYNC_PERIOD
数据库参数wal_level与wal_fsync_period共同决定 WAL 保存行为(参数定义见 WAL_LEVEL 与 WAL_FSYNC_PERIOD):
wal_level:控制 WAL 保存级别。级别 1 表示数据仅写入 WAL,但不立即执行fsync;级别 2 表示写入 WAL 的同时执行fsync。默认值为 1。执行fsync能增强数据持久性,但会降低写入性能。wal_fsync_period:当wal_level为 2 时,该参数控制执行fsync的频率。设为 0 表示每次写入后立即执行fsync,可保证数据安全但可能牺牲部分性能;设为大于 0 的值表示fsync周期,默认 3000,范围 [1, 180000],单位毫秒(即最长三分钟)。
CREATE DATABASE POWER WAL_LEVEL 2 WAL_FSYNC_PERIOD 3000;性能优先 vs 可靠性优先
创建数据库时,用户可根据需求选择不同的参数组合,在性能与可靠性之间找到最佳平衡:
- 性能优先:数据写入 WAL,但不立即执行
fsync操作——新写入的数据仅保存在文件系统缓存中,尚未同步到磁盘。该配置可显著提升写入性能,但断电等场景下存在丢失最近数据的风险。 - 可靠性优先:数据写入 WAL 的同时执行
fsync,立即将数据同步到磁盘,确保数据持久化与更高可靠性,代价是写入吞吐下降。
完整参数细节参见 WAL_LEVEL 与 WAL_FSYNC_PERIOD。
缓存与持久化的协同
从架构层面看(Architecture: Cache and Persistence),写缓存与持久化存储构成闭环:当 vnode 中的缓存数据积累到一定量时,为避免阻塞后续写入,TDengine 会启动落盘线程将缓存数据写入持久化存储设备,同时创建新的数据日志文件,并在成功落盘后删除旧日志文件以防止日志无限增长。基于时间序列数据特征,vnode 数据被拆分为多个文件,每个文件按数据库参数duration存储固定天数的数据,从而在查询特定时间段时能快速定位需打开的数据文件。这正是"缓存保证实时性、落盘保证持久性"的整体设计。
总结:如何选择缓存配置
综合四类缓存机制,可按以下思路配置数据库(以官方文档为准则):
- 写入吞吐与最近数据访问:通过
VGROUPS与BUFFER调节写缓存规模;缓存加大到一定程度后收益递减,不必无限增大。 - 当前值查询:按查询形态选择
CACHEMODEL(last_row/last_value/both),并按内存与表规模设置CACHESIZE;高并发写场景用CACHESHARDBITS增加分片降低锁竞争。 - 元数据访问:用
PAGES与PAGESIZE控制 vnode 元数据缓存的内存占用。 - 可靠性 vs 性能:用
WAL_LEVEL与WAL_FSYNC_PERIOD平衡写性能与崩溃恢复的持久性。
上述参数均可在 Databases 文档中查到完整的默认值、取值范围与注意事项;读缓存的配置步骤与验证示例见 Read Cache。
【免费下载链接】tdengineTDengine is an open source, high-performance, cloud native time-series database optimized for Internet of Things (IoT), Connected Cars, Industrial IoT and DevOps.项目地址: https://gitcode.com/taosdata/tdengine
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考