TDengine 读缓存Read Cache实战指南用 CACHEMODEL / CACHESIZE 将 LAST / LAST_ROW 当前值查询提速数倍【免费下载链接】TDengineHigh-performance, scalable time-series database designed for Industrial IoT (IIoT) scenarios项目地址: https://gitcode.com/GitHub_Trending/tde/TDengine读缓存Read Cache是 TDengine 面向当前值查询场景的内置加速机制把每个子表的最新数据常驻 vnode 内存命中时LAST/LAST_ROW无需再扫描磁盘上的历史数据文件。本文以官方文档 Read Cache 为主体结合数据库参数参考与源码实现完整讲解CACHEMODEL/CACHESIZE的语义、配置方法、验证手段、性能对比实验以及底层的规划器优化与 LRU 缓存原理。读完本文你可以独立为任意业务库开启并调优读缓存用一条 SQL 确认命中效果并依据cacheload指标判断容量是否充足。读缓存是什么为最新一条数据而生的内存加速层在工业物联网IIoT与设备监控场景中最频繁的查询往往不是历史聚合而是这台设备现在的最新读数是多少。TDengine 的读缓存把每个子表subtable最近写入的数据存放在 vnode 内存中当查询命中缓存时LAST/LAST_ROW不需要读取磁盘上的历史数据文件从而显著降低响应延迟。典型使用场景包括展示最新设备读数的实时仪表盘Dashboard每张表最新状态的轮询式刷新如告警状态、在线状态高并发下的LAST/LAST_ROW聚合查询。需要区分的是读缓存只是 TDengine 缓存体系中的一环。写缓存BUFFER、元数据缓存PAGES/PAGESIZE、WAL 相关的文件系统缓存WAL_LEVEL/WAL_FSYNC_PERIOD分别解决不同问题完整机制参见 Data Caching 与 Architecture。先分清两个函数LAST 与 LAST_ROW读缓存主要服务于两个聚合函数它们的语义不同因此需要匹配不同的CACHEMODEL取值函数语义概述完整参考LAST某列最后写入的非 NULL 值可以按列分别取值LASTLAST_ROW表 / 超表的最后一行该行各列值可能为 NULLLAST_ROW两者都属于管道聚合函数Pipeline Aggregate Function。LAST返回的是时间戳最大且非 NULL的列值LAST_ROW返回的是整条时间戳最大的记录这一行中的某些列完全可能是 NULL。选择CACHEMODEL时应依据业务真正使用的函数只查整行最新记录用last_row按列取最新非 NULL 值用last_value两者兼有则用both。配置读缓存CACHEMODEL 与 CACHESIZE读缓存由数据库级参数CACHEMODEL和CACHESIZE控制可在CREATE DATABASE时设置也可通过ALTER DATABASE动态调整。完整语法见 Databases其中database_option包含CACHEMODEL {none | last_row | last_value | both}与CACHESIZE value。CACHEMODEL四种缓存模式取值含义主要加速对象none不缓存默认值—last_row缓存每个子表最近的一行数据LAST_ROWlast_value缓存每列最近的非 NULL 值不受WHERE、ORDER BY、GROUP BY、INTERVAL等影响的LASTboth同时缓存最近一行与最近各列值LAST_ROW以及满足上述条件的LAST需要注意的使用要点频繁切换CACHEMODEL可能导致LAST/LAST_ROW结果短暂不准请谨慎操作建议开启后保持稳定。带过滤、排序、分组或时间窗口的LAST查询通常无法完全命中last_value缓存——这一点在规划器层面有严格检查见下文源码分析。开启读缓存后写路径需要同步维护缓存每次写入都要更新该子表的 last_row / last_value可能影响写入性能。对于高吞吐写入场景建议把both降为last_row或last_value详见 Ingesting Data Efficiently 中关于cachemodel影响写路径的说明。CACHESIZE每个 vnode 的缓存内存上限CACHESIZE设置每个 vnode用于缓存子表最新数据的内存大小默认值为1取值范围[1, 65536]单位为 MB。应根据机器内存与表规模综合设置表越多、单条记录越大需要的容量越大。如何判断容量是否足够来自 Modify CACHESIZE 的操作方法查看配置值SELECT * FROM INFORMATION_SCHEMA.INS_DATABASES;可显示每个库配置的CACHESIZEMB。查看实际占用SHOW db_name.VGROUPS;的输出包含cacheload列表示当前 last-cache 的实际使用量字节。判定原则若cacheload非常接近cachesize说明容量可能偏小缓存频繁淘汰、命中率下降若明显小于cachesize则容量充足。调整幅度可结合系统可用内存决定例如翻倍或数倍扩容。进阶参数CACHESHARDBITS缓存分片在数据库参数体系中还有一个与读缓存并发性能强相关的参数CACHESHARDBITS见 Databases它控制 last-value LRU 缓存的分片位数即内部锁粒度。默认值-1按 CACHESIZE 自动计算取值范围[-1, 19]实际分片数 2^CACHESHARDBITS。例如CACHESHARDBITS3表示 8 个分片CACHESHARDBITS6表示 64 个分片。自动计算规则每个分片至少 512 KB理论最大分片数 CACHESIZE / 512KB分片位数 floor(log₂(理论最大分片数))上限为 6最多 64 个分片当CACHESIZE 512 KB时位数为 0单分片。CACHESIZE理论最大分片数分片位数实际分片数1 MB2124 MB83832 MB64664256 MB5126封顶64更多分片可降低并发写缓存时的锁竞争适合高并发场景但分片过多会增加内存管理开销。注意修改CACHESHARDBITS会立即令库内所有 vnode 的 last-value 缓存条目全部失效后续查询需从磁盘重新加载可能短暂抬升查询延迟——这与源码中重建 last cache的逻辑一致见下文。创建与修改示例-- 建库时开启 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 使用量字节。在 command.c 中可以看到SHOW CREATE DATABASE的还原语句会把CACHESIZE %d CACHEMODEL %s CACHESHARDBITS %d一并输出便于迁移时完整复刻配置。实战演练用 taosBenchmark 对比读缓存前后的查询延迟官方文档给出了一套可复现的对比实验使用智能电表数据对比开启读缓存前后LAST/LAST_ROW的延迟。第一步生成测试数据taosBenchmark -d power -Q --start-timestamp1600000000000 --tables10000 --records10000 --time-step10000 -y该命令创建数据库power和超表meters共约 1 亿行数据10,000 个子表、每表 10,000 行起始时间戳1600000000000即2020-09-13T20:26:4008:00时间间隔 10 秒。此时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)未开启缓存时两条查询分别耗时约 353 ms 与 344 ms——每次都要扫描全表定位最新的行。第三步开启读缓存并确认生效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 SINGLE_STABLE 0 TABLE_PREFIX 0 TABLE_SUFFIX 0 TSDB_PAGESIZE 4 WAL_RETENTION_PERIOD 3600 WAL_RETENTION_SIZE 0 KEEP_TIME_OFFSET 0 Query OK, 1 row(s) in set (0.000282s)SHOW CREATE DATABASE输出中已出现CACHESIZE 1 CACHEMODEL both说明配置生效。第四步再次查询对比第一次查询会填充缓存后续查询通常能观察到明显更低的延迟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.044021s) 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.046682s)本实验中延迟从约 353 / 344 ms 降至约 44 ms提速约 8 倍。实际效果取决于数据规模、硬件配置与并发负载但方向是一致的查询越频繁、数据越热读缓存收益越明显。源码视角读缓存何时真正生效读缓存并非无条件加速所有LAST/LAST_ROW查询规划器与存储层都有一整套判定与落盘逻辑。规划器只有在合适的查询形态下才走缓存扫描在 planOptimizer.c 中hasSuitableCache根据CACHEMODEL与查询中是否出现LAST_ROW/LAST决定能否应用lastRowScan优化TSDB_CACHE_MODEL_NONE一律不优化TSDB_CACHE_MODEL_LAST_ROW仅当查询含LAST_ROW时优化TSDB_CACHE_MODEL_LAST_VALUE仅当查询含LAST时优化TSDB_CACHE_MODEL_BOTH两者皆可。同时lastRowScanOptCheckFuncListplanOptimizer.c还会检查函数列表中是否混入了其他函数、WHERE/ORDER BY/GROUP BY/INTERVAL等条件一旦出现这类特殊效果查询就不会被优化为缓存扫描而是回退到常规扫描路径——这正是文档中带过滤、排序、分组或窗口的LAST查询往往无法完全使用last_value缓存的源码依据。存储层LRU 缓存 惰性加载 写路径维护在存储引擎 tsdbCache.c 中last-cache 的载体是每个 vnode 的 LRU 缓存pTsdb-lruCache其行为与 Architecture: last/last_row Cache 的描述完全对应惰性加载lazy loading对某张表的首次查询从内存池与磁盘读出所需值写入 LRU 缓存后返回后续插入或删除只更新已存在的缓存条目未缓存的表写入不会强制进缓存。对子表查询只加载该子表对超表查询可能加载其全部子表。状态标记缓存条目带有ELastCacheStatustsdb.h区分缓存有效TSDB_LAST_CACHE_VALID与无缓存但 tsdb 可能有数据TSDB_LAST_CACHE_NO_CACHE。容量控制tsdbCacheSetCapacitytsdbCache.c把CACHESIZEMB换算成字节设置 LRU 容量tsdbCacheGetUsage返回当前使用量正是SHOW VGROUPS中cacheload指标的数据来源。配置变更联动在 vnodeSvr.c 中可以看到CACHESIZE变化会立即调用tsdbCacheSetCapacity调整 LRU 容量CACHESHARDBITS变化则加锁调用tsdbRebuildLastCache重建整个 last cache并打印 rebuilding last cache 日志——印证了文档中修改分片位会立即失效所有缓存条目的警告。写路径开销的量化指标读缓存开启后每次写入都需要更新对应子表的 last_row / last_value。vnode 的写入指标中维护了last_cache_commit_time与last_cache_commit_count见 vnodeInt.h在 vnodeCommit.c 中通过METRICS_TIMING_BLOCK统计每次缓存提交的耗时。这些指标可以直接印证读缓存由写路径维护、影响写入性能的结论——这也是官方建议高吞吐场景将both降级为last_row或last_value的原因。运维建议与最佳实践按查询形态选模式纯最新状态仪表盘用last_row按列取最新非 NULL 值如LAST(current)用last_value两者都高频出现再用both避免无谓的写路径开销。开启后保持稳定避免频繁在none/last_row/last_value/both之间切换防止LAST/LAST_ROW结果短暂不准。定期观察 cacheload用SHOW VGROUPS对比cacheload与CACHESIZE接近即扩容扩容可结合CACHESHARDBITS的自动分片规则一起评估。与写缓存等机制配合读缓存只解决最新值查询高频写入吞吐主要受BUFFER、VGROUPS、WAL_LEVEL/WAL_FSYNC_PERIOD影响需要整体规划参见 Data Caching。验证生效SHOW CREATE DATABASE确认参数、SHOW VGROUPS观察cacheload增长、以及查询延迟对比三步即可完成上线验证。相关文档LAST / LAST_ROW函数语义与使用约束CACHEMODEL / CACHESIZE / CACHESHARDBITS完整参数参考与容量判定方法Data Caching写缓存、元数据缓存、WAL 等其他缓存类型Architecture: last/last_row CacheLRU、惰性加载与配置联动机制Ingesting Data Efficiently读缓存对写路径的影响与高吞吐调优【免费下载链接】TDengineHigh-performance, scalable time-series database designed for Industrial IoT (IIoT) scenarios项目地址: https://gitcode.com/GitHub_Trending/tde/TDengine创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考