
TDengine 数据缓存机制详解写缓存、读缓存、元数据缓存与 WAL 文件系统缓存【免费下载链接】TDengineHigh-performance, scalable time-series database designed for Industrial IoT (IIoT) scenarios项目地址: https://gitcode.com/GitHub_Trending/tde/TDengine在物联网IoT和工业互联网IIoT应用中数据的高效管理直接决定系统性能与用户体验。TDengine 针对高并发实时读写场景设计了一套完整的缓存体系覆盖写缓存vnode 内存池、读缓存last/last_row LRU 缓存、元数据缓存pages 页池和文件系统缓存WAL fsync四个层面。读完本文你将理解每类缓存的设计动机与底层实现掌握BUFFER、CACHEMODEL、PAGES、WAL_FSYNC_PERIOD等关键数据库参数的取值含义并能在实际部署中按“写入吞吐 / 当前值查询时延 / 持久化可靠性”三个维度对缓存进行调优。缓存体系总览TDengine 的四种缓存机制各司其职又通过 vnode虚拟节点这一存储逻辑单元紧密耦合类型作用关键参数适用场景详见写缓存最新写入优先保存在缓存中达到临界值后最早数据批量落盘BUFFER、VGROUPS近期数据读写、写入吞吐下文“写缓存”小节读缓存缓存子表最近数据加速当前值查询CACHEMODEL、CACHESIZELAST、LAST_ROW下文“读缓存”小节、读缓存元数据缓存缓存 vnode 曾经获取过的元数据PAGES、PAGESIZE元数据相关访问标签过滤、schema 查询下文“元数据缓存”小节文件系统缓存WAL 顺序追加写入时依赖文件系统缓存fsync控制何时强制落盘WAL_LEVEL、WAL_FSYNC_PERIOD写入性能与可靠性权衡下文“文件系统缓存与 WAL”小节各参数的完整取值范围与默认值参见 数据库参数说明实现层面的落盘流程与 last 缓存加载策略参见 整体架构缓存与持久化。写缓存时间驱动的 vnode 内存池TDengine 采用了一种独特的时间驱动缓存管理策略亦称为写驱动的缓存管理机制。与传统读驱动的缓存模式不同其核心思想是将最新写入的数据优先保存在缓存中当缓存容量达到预设临界值时把最早存储的数据批量写入硬盘从而实现缓存与磁盘之间的动态平衡。为什么写缓存天然契合物联网业务在物联网数据应用中用户往往最关注最近产生的数据即设备的当前状态。TDengine 充分利用了这一业务特性最近到达的“当前状态”数据优先驻留在缓存中用户查询设备最新读数时可以快速命中无须穿透到磁盘。从这个角度看合理配置buffer后TDengine 本身就可以承担一部分“数据缓存”的角色用户无须再额外部署 Redis 等外部缓存系统。参数配置VGROUPS 与 BUFFER为了实现分布式存储和高可用TDengine 引入了 vnode虚拟节点概念。每个 vnode 最多可拥有 3 个副本副本之间组成一个 vgroup虚拟节点组。创建数据库时通过两个参数控制写缓存的规模VGROUPS决定数据库的数据由多少个 vgroup 处理BUFFER为每个 vnode 分配多少写入缓存内存池单位 MB默认256最小3最大16384。例如下面的 SQL 创建了一个包含 10 个 vgroup、每个 vnode 占用 256 MB 写缓存的数据库CREATE DATABASE POWER VGROUPS 10 BUFFER 256 CACHEMODEL NONE PAGES 128 PAGESIZE 16;需要强调的是缓存并非越大越好。超过一定阈值后再增加缓存对写入性能提升并无帮助应结合机器总内存与表数量vnode 数量综合规划单个 dnode 的总写缓存开销约等于BUFFER × 该 dnode 上的 vnode 数。源码视角写缓存如何落盘从 整体架构文档 描述的实现机制看每个 vnode 拥有独立的内存空间被划分为多个固定大小的内存块不同 vnode 之间内存完全隔离数据写入采用类似日志的顺序追加方式每个 vnode 还维护 SkipList 结构以便快速检索。当超过三分之一的内存块被写满时系统启动数据落盘流程并将新写入引导至新的内存块——因此 vnode 内存中始终保留着最新的数据块既保证写入不被落盘阻塞又保证近期数据的查询效率。运维层面可以主动触发落盘。在关闭节点之前执行FLUSH DATABASE db_name;可以把内存中的数据落盘避免重启后的预写日志回放加速启动过程参见 数据库运维操作。另外需要注意TDengine 重启后写缓存中的数据会被清除并批量写入硬盘不会像专业的 KV 缓存系统那样自动把缓存数据重新加载回来。读缓存为 LAST / LAST_ROW 加速的 LRU 缓存读缓存机制专为高频实时查询场景设计尤其适用于物联网、工业互联网中“实时掌握设备状态”的业务。用户最关心的是最新数据设备当前读数或状态读缓存正是把这类“最近数据”驻留内存命中后LAST/LAST_ROW无须再从磁盘读取历史数据。LAST 与 LAST_ROW 的语义差异读缓存主要配合两个聚合函数使用二者语义不同配置时需对应选择完整语义见 LAST、LAST_ROW函数语义摘要LAST指定列最后写入的非 NULL 值可按列分别取LAST_ROW表 / 超级表的最后一条记录该行上的列值可为 NULLCACHEMODEL选择缓存内容通过CACHEMODEL参数用户可灵活选择缓存模式CACHEMODEL缓存内容主要加速none不缓存默认—last_row子表最近一行LAST_ROWlast_value子表每列最近的非 NULL 值无WHERE、ORDER BY、GROUP BY、INTERVAL等特殊影响时的LASTboth同时缓存最近行与最近列值LAST_ROW与上述条件下的LASTCACHESIZE 与 CACHESHARDBITSCACHESIZE每个 vnode 用于缓存子表最近数据的内存大小默认1取值范围[1, 65536]单位 MB。容量是否够用的判断方法见 修改 CACHESIZE先通过SELECT * FROM INFORMATION_SCHEMA.INS_DATABASES;查看CACHESIZE再用SHOW db_name.VGROUPS;查看各 vnode 的cacheload当前 last 缓存占用单位字节若cacheload非常接近CACHESIZE说明缓存可能过小可结合系统可用内存将其加倍或提高几倍。CACHESHARDBITSlast 缓存LRU cache的分片位数用于控制缓存内部并发锁粒度。默认-1自动按CACHESIZE推算取值范围[-1, 19]实际分片数为2^CACHESHARDBITS。分片越多并发写缓存时锁竞争越小但过多会增加内存管理开销。修改该值会导致该 vnode 的全部 last 缓存立即失效并重新从磁盘加载需谨慎操作。以上两个参数的细则见 CACHESIZE、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 power\G确认参数生效。实测验证开启读缓存前后的时延对比以下示例摘自 读缓存文档 的实践部分。先用taosBenchmark生成测试数据10000 个子表 × 10000 条 ≈ 1 亿条记录采集间隔 10 秒taosBenchmark -d power -Q --start-timestamp1600000000000 --tables10000 --records10000 --time-step10000 -y未启用读缓存默认CACHEMODEL为none时查询最新电流与时间戳taos SELECT LAST(ts, current) FROM meters; last(ts) | last(current) | 2020-09-15 00:13:10.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-15 00:13:10.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; ... Create Database: CREATE DATABASE power BUFFER 256 CACHESIZE 1 CACHEMODEL both ...再次查询首次查询会填充缓存后续查询时延明显下降taos SELECT LAST(ts, current) FROM meters; Query OK, 1 row(s) in set (0.044021s) taos SELECT LAST_ROW(ts, current) FROM meters; Query OK, 1 row(s) in set (0.046682s)本例中LAST/LAST_ROW时延从约 353 / 344 ms 降至约 44 ms实际效果与数据规模、硬件和并发负载有关。源码视角LRU 延迟加载策略从 整体架构文档 描述的实现机制看TSDB 为每张表的 last 和 last_row 数据提供 LRU 缓存采用延迟加载策略首次查询某张表的 last / last_row 时缓存模块去内存池写缓存和磁盘文件加载数据处理后放入 LRU 缓存再返回给查询模块当有新数据插入或删除时如果缓存中已有该表的数据则做相应更新如果缓存中没有当前被写入表的数据则直接跳过无须额外操作——这解释了为什么读缓存对写入路径的额外开销可控缓存开关变更时如ALTER DATABASE ... CACHEMODEL也会更新缓存数据开启后在首次查询时加载关闭时释放之前的缓存区。查询超级表的 last / last_row 时该超级表对应的所有子表都需要加载到缓存中。源码层面last 缓存的实现位于 tsdbCache.c 与 tsdbCacheRead.cvnode 的 TSDB 层cacheload这一列则由系统表模块注册见 systable.c 中cacheload列的定义BIGINT 类型标记为系统信息列。需要提醒两点频繁切换CACHEMODEL可能导致LAST/LAST_ROW的查询结果短期内不准确请谨慎操作开启读缓存会在写入路径上维护缓存对写入性能有一定影响。高吞吐场景可将both调整为last_row或last_value参见 高吞吐写入。此外LAST查询带过滤、排序、分组或窗口时往往无法充分利用last_value缓存而借助这种无缝集成的读缓存用户可以避免引入 Redis 一类外部缓存系统显著降低架构复杂度和运维成本。元数据缓存vnode 的 pages 页池为了提升查询和写入操作的效率每个 vnode 都配备了元数据缓存用于存储其曾经获取过的元数据表 schema、标签值等。这部分数据支撑着标签过滤、按表名定位数据等操作数据量不大时可以全内存保存千万级规模的标签数据过滤也能快速返回内存不足时同样可以支撑数千万张表的快速查询。元数据缓存的大小由两个参数共同决定PAGES一个 vnode 中元数据存储引擎的缓存页个数默认256最小64PAGESIZE每个缓存页的大小单位 KB默认4取值范围[1, 16384]1 KB 到 16 MB。一个 vnode 元数据存储占用PAGES × PAGESIZE内存默认情况下为 256 × 4 KB 1 MB。例如CREATE DATABASE POWER PAGES 128 PAGESIZE 16;该 SQL 为数据库POWER的每个 vnode 创建 128 个 page、每个 page 16 KB共 2 MB的元数据缓存。参数默认值与取值范围见 PAGES、PAGESIZE。文件系统缓存与 WALfsync 决定数据何时真正落盘TDengine 采用 WALWrite-Ahead Logging技术作为基本的数据可靠性保障手段。其核心原理是在数据实际写入数据存储层之前先把变更记录写入日志文件即便集群崩溃或宕机也能利用日志恢复故障前的状态确保数据无损。文件系统缓存在写入链路中的位置WAL 的写入是顺序追加方式写入过程先进入操作系统的文件系统缓存因此文件系统缓存对写入性能影响显著。为了让数据真正落到磁盘系统需要调用fsync函数把文件系统缓存中的数据强制刷入硬盘。两个数据库参数共同决定 WAL 的保存行为WAL_LEVELWAL 保存级别默认1。1仅将数据写入 WAL不立即执行fsync新写入的数据暂时保存在文件系统缓存中2写入 WAL 的同时执行fsync数据持久性更高但写入性能有所下降。WAL_FSYNC_PERIOD当WAL_LEVEL为2时控制 fsync 的执行频率单位毫秒默认3000取值范围[0, 180000]。设置为0表示每次写入后立即 fsync安全性最高但性能代价最大设置为大于 0 的数值则按周期批量 fsync。CREATE DATABASE POWER WAL_LEVEL 2 WAL_FSYNC_PERIOD 3000;由此可以派生出两种典型权衡策略性能优先WAL_LEVEL 1默认——数据只写入 WAL、暂不 fsync写入路径更短可靠性优先WAL_LEVEL 2——写入 WAL 的同时按WAL_FSYNC_PERIOD控制频率执行 fsync确保数据尽快持久化。参数完整说明见 WAL_LEVEL、WAL_FSYNC_PERIOD。源码视角walFsync 的分支逻辑WAL 库的 fsync 实现印证了上述行为。在 walWrite.c 的walFsync函数中若 WAL 级别为跳过 fsync对应WAL_LEVEL 1函数直接返回不做任何磁盘同步仅在强制 fsyncforceFsync例如落盘、checkpoint 等内部流程触发或“级别为 fsync对应WAL_LEVEL 2且fsyncPeriod 0”时才执行真正的同步流程先写入事务的 pending 标记walTxnPreFsync再对.log文件执行taosFsyncFile最后 fsync.txn文件并清除 pending 标记walTxnPostFsync。而在 walMgmt.c 中WAL 打开时会计算fsyncSeq fsyncPeriod / 1000用于按写入序号周期性触发 fsync——这正是WAL_FSYNC_PERIOD大于 0 时“按周期落盘”的实现基础。也就是说WAL_LEVEL 1下日志依赖操作系统自己的缓存回收策略崩溃可能丢失少量未同步数据WAL_LEVEL 2下则按周期把日志钉到磁盘丢失窗口被压缩到周期长度以内。缓存参数速查与调优要点参数默认值取值范围作用修改方式BUFFER256 MB[3, 16384] MB单个 vnode 写缓存大小创建时 /ALTER DATABASEVGROUPS—正整数初始 vgroup 数量决定数据分片与并发能力仅创建时CACHEMODELnonenone/last_row/last_value/bothlast / last_row 读缓存模式创建时 /ALTER DATABASECACHESIZE1 MB[1, 65536] MB单个 vnode 读缓存大小创建时 /ALTER DATABASECACHESHARDBITS-1自动[-1, 19]last 缓存 LRU 分片位数创建时 /ALTER DATABASE会使缓存失效PAGES256最小 64vnode 元数据缓存页数仅创建时PAGESIZE4 KB[1, 16384] KB元数据缓存页大小仅创建时WAL_LEVEL1{1, 2}是否执行 fsync创建时 /ALTER DATABASEWAL_FSYNC_PERIOD3000 ms[0, 180000] msfsync 周期WAL_LEVEL2时生效创建时 /ALTER DATABASE调优时建议遵循以下思路近期数据查询/写入吞吐为主优先调BUFFER注意总量 BUFFER × vnode 数超过阈值后收益递减高频 LAST / LAST_ROW 查询按查询形态选CACHEMODEL看板类“取最新一行”用last_row即可按列取非空值才需要last_value再用cacheload与CACHESIZE的比值验证容量是否充足表规模巨大、标签过滤频繁适当调大PAGES/PAGESIZE减少元数据从磁盘回读的开销可靠性敏感场景WAL_LEVEL 2并将WAL_FSYNC_PERIOD调小吞吐优先场景保持默认WAL_LEVEL 1。延伸阅读读缓存CACHEMODEL/CACHESIZE的配置步骤与完整效果验证数据库参数VGROUPS、BUFFER、PAGES、PAGESIZE、WAL_LEVEL、WAL_FSYNC_PERIOD等全部参数的取值范围与修改方式整体架构缓存与持久化写驱动缓存的落盘策略、last/last_row LRU 延迟加载、数据文件与.last文件等底层机制高吞吐写入读缓存对写入路径的影响与调优建议。【免费下载链接】TDengineHigh-performance, scalable time-series database designed for Industrial IoT (IIoT) scenarios项目地址: https://gitcode.com/GitHub_Trending/tde/TDengine创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考