StarRocks Block Cache Warmup 实战指南:用 CACHE SELECT 主动预热远端数据
【免费下载链接】starrocksThe world's fastest open query engine for sub-second analytics both on and off the data lakehouse. With the flexibility to support nearly any scenario, StarRocks provides best-in-class performance for multi-dimensional analytics, real-time analytics, and ad-hoc queries. A Linux Foundation project.项目地址: https://gitcode.com/GitHub_Trending/st/starrocks
缓存预热(Cache Warmup)是 StarRocks 应对数据湖分析、共享数据集群(shared-data)高要求查询场景的重要能力。本文围绕 StarRocks v3.3 引入的 Block Cache Warmup 特性,系统讲解其原理、CACHE SELECT 语法、四类返回指标、与 SUBMIT TASK 结合的周期调度方案,以及源码级实现细节与使用限制,帮助你在 BI 报表、PoC 性能测试等场景中把远端数据提前"搬到"本地磁盘缓存,显著降低查询延迟。
在数据湖分析和共享数据集群场景中,查询引擎需要从 HDFS 或对象存储等远端存储反复拉取数据,远程 I/O 开销和热点数据重复读取是两大性能瓶颈。Block Cache 把远端文件按块切分后缓存到 BE/CN 节点的本地磁盘,而 Block Cache Warmup 则进一步把"查询时被动填充缓存"升级为"主动预取",让热数据在查询真正到来之前就绪。
从被动填充到主动预热:Block Cache Warmup 的定位
要理解 Block Cache Warmup,先要清楚它与 Block Cache 的关系。Block Cache 是 StarRocks 自 v2.5 引入的磁盘级缓存:当查询首次读取远端数据时,系统将原始文件按固定大小切分成数据块(block),以块为最小缓存单元写入本地磁盘;后续查询命中缓存后直接从本地读取,避免重复访问远端存储。其缓存命中与读取流程可概括为三步:
- 系统按缓存键(cache key)检查本地节点 Block Cache 中是否存在目标块;
- 命中则直接从本地磁盘读取;
- 未命中则从远端存储拉取,并同步写入本地 Block Cache 供后续查询复用。
这里的缓存键由三部分构成:hash(filename) + fileModificationTime + blockId。文件名哈希用于定位文件,修改时间用于感知远端文件是否变化,blockId 则标识文件切分后的第几个块。
从缓存填充方式看,普通 Block Cache 是被动填充——数据在查询过程中被顺带写入缓存;而 Block Cache Warmup 是主动填充——它通过 CACHE SELECT 语句,在查询发生前主动从远端存储抓取目标数据写入缓存。两者互补:被动填充覆盖"已查询过的数据",主动预热覆盖"将要被查询的数据"。
关于 Block Cache 的切分机制、SLRU/LRU 替换策略、Data Cache 的整体架构与 BE 配置项,可参考同一目录下的 Data Cache 文档。
适用场景与前置条件
什么场景值得预热
- 磁盘容量远大于待预热数据量:如果块缓存磁盘容量小于待预热数据量,预热效果会大打折扣。例如需要预热 100 GB 数据但磁盘只有 50 GB,则只能缓存 50 GB,且后写入的 50 GB 会替换先前缓存的 50 GB,最终可能什么都没留下。
- 缓存磁盘上的数据访问相对稳定:如果预热期间出现访问量激增,预热效果同样难以保证。例如磁盘 200 GB、待预热 100 GB,条件一满足;但若预热过程中有 150 GB 新数据写入缓存,或一个异常的大冷查询需要装载 150 GB 数据,都可能触发缓存淘汰(eviction),导致已预热的数据被挤出。
换句话说,预热的前提是"有足够空间装下热数据,且空间不会被意外的数据洪峰冲垮"。
使用前必须确认
- 已启用 Block Cache 特性。Data Cache 自 v3.3.0 起默认开启,由 BE 配置项
datacache_enable(默认true)控制总开关;Block Cache 独立开关为block_cache_enable(默认true)。若你曾手动关闭过,需要先恢复启用。 - 具备目标表的 SELECT 权限。CACHE SELECT 的执行走正常查询链路,权限校验与普通 SELECT 一致。
CACHE SELECT 语法与参数详解
CACHE SELECT 是实现 Block Cache Warmup 的核心语法,完整形式如下:
CACHE SELECT <column_name> [, ...] FROM [<catalog_name>.][<db_name>.]<table_name> [WHERE <boolean_expression>] [PROPERTIES("verbose"="true")]| 参数 | 说明 |
|---|---|
column_name | 要获取的列,可使用*获取外部表的全部列 |
catalog_name | Catalog 名称,默认为 DEFAULT_CATALOG;使用SET CATALOG切换后可不指定 |
db_name | 数据库名称,切换到目标库后可不指定 |
table_name | 要获取数据的表名 |
boolean_expression | WHERE 中的过滤条件,用于细粒度预热 |
PROPERTIES | 目前仅支持verbose属性,用于返回更详细的预热指标 |
CACHE SELECT 是同步过程,一次只能预热一张表。执行成功后返回预热相关指标。
从语法解析层看,这条语句在 StarRocks.g4 中定义:
CACHE SELECT selectItem (',' selectItem)* FROM qualifiedName (WHERE where=expression)? properties?它被解析为 DataCacheSelectStatement,内部实际包装了一个InsertStmt——这也解释了为什么预热能"按正常查询流程"执行:CACHE SELECT 本质上是一条特殊的 INSERT 语句(目标为 BLACKHOLE,见下文"实现原理"一节)。
分析器对参数与属性的校验
DataCacheStmtAnalyzer 在分析阶段会完成以下校验与解析:
- 查询必须为纯表扫描:查询关系必须是
TableRelation,否则报错 "Cache select only support olap table, external table or materialized view."; - 共享无共享(shared-nothing)模式不支持本地 OLAP 表:
CACHE SELECT若针对默认内部 Catalog 的表且集群为 shared-nothing 模式,会报错 "Currently cache select is not supported in local olap table"; verbose属性:通过Boolean.parseBoolean(properties.getOrDefault("verbose", "false"))解析,默认关闭;priority属性:只能取 0 或 1(默认为 0),用于标记缓存数据的优先级;ttl属性:采用 ISO-8601 时长格式(如P1Y、PT0M),表示缓存数据保持有效的时间;当priority > 0时必须指定非零 TTL,否则报错 "TTL must be specified when priority > 0"。
这些属性校验在仓库单元测试 DataCacheStmtAnalyzerTest.java 中有完整覆盖,例如校验 verbose 大小写不敏感、priority=1必须搭配 TTL、TTL 格式非法时报错等场景。
预热实操:四种典型用法
预热外部表全量数据
下面的例子将 Hive Cataloghive_catalog下test_db.lineitem表的所有数据预热到缓存:
mysql> cache select * from hive_catalog.test_db.lineitem; +-----------------+------------------+----------------------+-------------------+ | READ_CACHE_SIZE | WRITE_CACHE_SIZE | AVG_WRITE_CACHE_TIME | TOTAL_CACHE_USAGE | +-----------------+------------------+----------------------+-------------------+ | 48.2MB | 3.7GB | 59ms | 96.83% | +-----------------+------------------+----------------------+-------------------+ 1 row in set (19.56 sec)返回字段含义:
READ_CACHE_SIZE:所有节点从块缓存中读取的数据总大小;WRITE_CACHE_SIZE:所有节点写入块缓存的数据总大小;AVG_WRITE_CACHE_TIME:每个节点写入块缓存的平均耗时;TOTAL_CACHE_USAGE:本次预热完成后整个集群块缓存的磁盘空间使用率,可用于评估块缓存空间是否充足。
指定列 + 过滤条件细粒度预热
通过指定列与谓词可以实现细粒度预热,显著减少预热数据量,降低磁盘 I/O 与 CPU 消耗:
mysql> cache select l_orderkey from hive_catalog.test_db.lineitem where l_shipdate='1994-10-28'; +-----------------+------------------+----------------------+-------------------+ | READ_CACHE_SIZE | WRITE_CACHE_SIZE | AVG_WRITE_CACHE_TIME | TOTAL_CACHE_USAGE | +-----------------+------------------+----------------------+-------------------+ | 957MB | 713.5MB | 3.6ms | 97.33% | +-----------------+------------------+----------------------+-------------------+ 1 row in set (9.07 sec)预热共享数据集群中的云原生表(cloud-native table)同理。下面的示例预热ssb库中lineorder表的lo_orderkey列:
mysql> cache select lo_orderkey from ssb.lineorder; +-----------------+------------------+----------------------+-------------------+ | READ_CACHE_SIZE | WRITE_CACHE_SIZE | AVG_WRITE_CACHE_TIME | TOTAL_CACHE_USAGE | +-----------------+------------------+----------------------+-------------------+ | 118MB | 558.9MB | 200.6ms | 4.66% | +-----------------+------------------+----------------------+-------------------+ 1 row in set (29.88 sec)注意此例中TOTAL_CACHE_USAGE仅为 4.66%,说明缓存空间非常充裕,预热数据可长期驻留,无需担心 SLRU 淘汰。
Verbose 模式:查看每个 BE 的明细指标
默认返回的指标是所有 BE 聚合后的结果。在语句末尾追加PROPERTIES("verbose"="true"),可获取每个 BE 的详细指标:
mysql> cache select * from hive_catalog.test_db.lineitem properties("verbose"="true"); +---------------+-----------------+---------------------+------------------+----------------------+-------------------+ | IP | READ_CACHE_SIZE | AVG_READ_CACHE_TIME | WRITE_CACHE_SIZE | AVG_WRITE_CACHE_TIME | TOTAL_CACHE_USAGE | +---------------+-----------------+---------------------+------------------+----------------------+-------------------+ | 172.26.80.233 | 376MB | 127.8micros | 0B | 0s | 3.85% | | 172.26.80.231 | 272.5MB | 121.8micros | 20.7MB | 146.5micros | 3.91% | | 172.26.80.232 | 355.5MB | 147.7micros | 0B | 0s | 3.91% | +---------------+-----------------+---------------------+------------------+----------------------+-------------------+ 3 rows in set (0.54 sec)Verbose 模式会额外返回一个指标:
AVG_READ_CACHE_TIME:块缓存命中时,每个节点读取数据的平均耗时。
从 DataCacheSelectMetrics.java 的实现可以看到两种输出模式的差异:简单模式将各 BE 的读写字节数取平均、写入耗时按总次数平均后聚合成一行;verbose 模式则按 BE 逐行输出 IP、读写大小、平均读写时间与缓存使用率,其中平均读写时间是readTimeNs / count计算得到的纳秒值再格式化输出。聚合模式下TOTAL_CACHE_USAGE的计算方式是:所有 BE 的(磁盘已用 + 内存已用) / (磁盘配额 + 内存配额)之和的比值。
用 SUBMIT TASK 实现周期调度预热
CACHE SELECT 是一次性的同步操作,配合 SUBMIT TASK 即可实现周期性自动预热。下面的示例每 5 分钟预热一次lineitem表的l_orderkey列:
mysql> submit task always_cache schedule every(interval 5 minute) as cache select l_orderkey from hive_catalog.test_db.lineitem where l_shipdate='1994-10-28'; +--------------+-----------+ | TaskName | Status | +--------------+-----------+ | always_cache | SUBMITTED | +--------------+-----------+ 1 row in set (0.03 sec)查看已创建的任务
任务提交后,可查询default_catalog.information_schema.tasks查看任务定义与调度信息:
mysql> select * from default_catalog.information_schema.tasks; +--------------+---------------------+-----------------------------------------------------+---------------+------------------------------+---------------------------------------------------------------------+---------------------+------------+ | TASK_NAME | CREATE_TIME | SCHEDULE | CATALOG | DATABASE | DEFINITION | EXPIRE_TIME | PROPERTIES | +--------------+---------------------+-----------------------------------------------------+---------------+------------------------------+---------------------------------------------------------------------+---------------------+------------+ | always_cache | 2024-04-11 16:01:00 | PERIODICAL START(2024-04-11T16:01) EVERY(5 MINUTES) | emr_hive_test | zz_tpch_sf1000_hive_orc_zlib | cache select l_orderkey from lineitem where l_shipdate='1994-10-28' | NULL | | +--------------+---------------------+-----------------------------------------------------+---------------+------------------------------+---------------------------------------------------------------------+---------------------+------------+ 1 row in set (0.21 sec)查看任务执行历史
查询default_catalog.information_schema.task_runs可查看每次预热执行的明细,其中EXTRA_MESSAGE字段记录着 CACHE SELECT 的指标:
mysql> select * from default_catalog.information_schema.task_runs; +--------------------------------------+--------------+---------------------+---------------------+---------+---------------+------------------------------+---------------------------------------------------------------------+---------------------+------------+---------------+----------+------------------------------------------------------------------------------------------------------------------------+------------+ | QUERY_ID | TASK_NAME | CREATE_TIME | FINISH_TIME | STATE | CATALOG | DATABASE | DEFINITION | EXPIRE_TIME | ERROR_CODE | ERROR_MESSAGE | PROGRESS | EXTRA_MESSAGE | PROPERTIES | +--------------------------------------+--------------+---------------------+---------------------+---------+---------------+------------------------------+---------------------------------------------------------------------+---------------------+------------+---------------+----------+------------------------------------------------------------------------------------------------------------------------+------------+ | 55b30204-f7da-11ee-b03e-7ea526d0b618 | always_cache | 2024-04-11 16:06:00 | 2024-04-11 16:07:22 | SUCCESS | emr_hive_test | zz_tpch_sf1000_hive_orc_zlib | cache select l_orderkey from lineitem where l_shipdate='1994-10-28' | 2024-04-12 16:06:00 | 0 | NULL | 100% | AlreadyCachedSize: 15.7GB, AvgReadCacheTime: 1ms, WriteCacheSize: 0B, AvgWriteCacheTime: 0s, TotalCacheUsage: 75.94% | | | a2e3dc7e-f7d9-11ee-b03e-7ea526d0b618 | always_cache | 2024-04-11 16:01:00 | 2024-04-11 16:02:39 | SUCCESS | emr_hive_test | zz_tpch_sf1000_hive_orc_zlib | cache select l_orderkey from lineitem where l_shipdate='1994-10-28' | 2024-04-12 16:01:00 | 0 | NULL | 100% | AlreadyCachedSize: 15.7GB, AvgReadCacheTime: 1.2ms, WriteCacheSize: 0B, AvgWriteCacheTime: 0s, TotalCacheUsage: 75.87% | | +--------------------------------------+--------------+---------------------+---------------------+---------+---------------+------------------------------+---------------------------------------------------------------------+---------------------+------------+---------------+----------+------------------------------------------------------------------------------------------------------------------------+------------+ 2 rows in set (0.04 sec)注意上例中第二次执行WriteCacheSize: 0B、AlreadyCachedSize: 15.7GB,说明该表数据此前已全部预热,本次任务仅做了命中验证,未产生新的写入——这正是周期调度预热场景中常见的"缓存已就绪"状态。
删除任务
不再需要周期预热时,使用 DROP TASK 删除:
DROP TASK <task_name>调度任务的实现路径
从源码结构看,周期调度下的 CACHE SELECT 由 DataCacheSelectProcessor 处理:它作为BaseTaskRunProcessor的子类,在任务运行时通过ctx.executeSql(context.getDefinition())重新执行 CACHE SELECT 语句,从子执行器(sub StmtExecutor)的 Coordinator 中取回预热指标,写回任务状态EXTRA_MESSAGE,并调用updateBackendDataCacheMetrics刷新各节点的缓存用量指标。这就是task_runs.EXTRA_MESSAGE中能看到完整预热指标的原因。
典型业务场景
PoC 性能测试:评估 StarRocks 性能时,如果不想让外部存储系统的网络延迟干扰测试结果,可先用 CACHE SELECT 把待测表数据全部装入块缓存,再进行基准查询。这样测得的查询性能即为纯本地缓存命中下的表现。
固定时点的 BI 报表:业务团队每天早晨 8 点查看 BI 报表。为保证查询性能相对稳定,可在每天 7 点调度一个预热任务,将报表涉及的数据提前装好:
mysql> submit task BI schedule START('2024-02-03 07:00:00') EVERY(interval 1 day) AS cache select * from hive_catalog.test_db.lineitem where l_shipdate='1994-10-28'; +--------------+-----------+ | TaskName | Status | +--------------+-----------+ | BI | SUBMITTED | +--------------+-----------+ 1 row in set (0.03 sec)最小化资源消耗的预热:为减少预热对常规查询的影响,可在 SUBMIT TASK 中通过 session 变量控制资源占用,例如指定资源组、调整并行度(DOP)、用 WHERE 缩小预热范围:
mysql> submit task cache_select properties("pipeline_dop"="1", "resource_group"="warmup") schedule EVERY(interval 1 day) AS cache select * from hive_catalog.test_db.lineitem where l_shipdate>='1994-10-28'; +--------------+-----------+ | TaskName | Status | +--------------+-----------+ | cache_select | SUBMITTED | +--------------+-----------+ 1 row in set (0.03 sec)
实现原理:CACHE SELECT 底层是如何工作的
深入 DataCacheSelectExecutor.java 可以看到 CACHE SELECT 的执行骨架:
- CACHE SELECT 语句在 AST 层包装了一个
InsertStmt(目标为 BLACKHOLE),即它本质上是"INSERT INTO BLACKHOLE() SELECT ...",因此走的是完整查询执行链路,预热开销与普通查询相当; - 执行器会按 warehouse 下可用的计算资源(Compute Resource)拆分出多个子执行上下文(sub ConnectContext),逐一创建内部
StmtExecutor执行同一个 INSERT 语句,再聚合各子执行器的指标; - 关键的一点是,buildCacheSelectConnectContext 在克隆会话变量时强制设置了与缓存相关的参数,保证预热必然写入缓存:
setEnableScanDataCache(true)与setEnablePopulateDataCache(true):强制开启扫描缓存与缓存填充;setDataCachePopulateMode(ALWAYS):填充模式固定为"始终填充",确保所有被访问的数据都必须写入缓存;setEnableDataCacheAsyncPopulateMode(false):关闭异步填充,采用同步填充保证一次预热完成全部写入;setEnableDataCacheIOAdaptor(false):关闭 I/O 适配器,避免磁盘高负载时部分请求被路由回远端存储而漏缓存;setDataCacheEvictProbability(100)与可选的priority、ttl:控制缓存写入的淘汰概率、优先级与有效期。
这解释了为什么 CACHE SELECT 与普通查询行为不同:普通 SELECT 会受populate_datacache_mode等会话变量影响决定是否填充缓存,而 CACHE SELECT 无条件强制执行填充。
使用限制与注意事项
- 使用 CACHE SELECT 前必须启用 Block Cache 特性,且对目标表拥有SELECT 权限。
- CACHE SELECT只支持单表预热,不支持 ORDER BY、LIMIT、GROUP BY 等操作符——分析器要求查询关系必须是对单张表的直接扫描(TableRelation)。
- CACHE SELECT 在shared-nothing 与 shared-data 集群中均可使用;但 shared-nothing 模式下仅支持外部表(本地 OLAP 表会被拒绝),共享数据集群中可预热云原生表。
- 可预热的远端文件格式包括TEXT、ORC、Parquet。
- 已预热的数据不保证永久驻留:仍可能依据 Block Cache 的 SLRU 规则被淘汰。
- 数据湖用户可通过
SHOW BACKENDS\G或SHOW COMPUTE NODES\G查看块缓存剩余容量,评估 SLRU 淘汰风险; - 共享数据集群用户可通过集群指标查看块缓存使用情况。
- 数据湖用户可通过
- 当前 CACHE SELECT 的实现基于 INSERT INTO BLACKHOLE(),走正常查询流程,因此性能开销与普通查询相当。官方说明未来版本会优化性能。
后续版本展望
官方文档指出,未来 StarRocks 将引入自适应 Block Cache Warmup(adaptive Block Cache Warmup),结合数据访问模式与缓存命中情况动态调整预热策略,以进一步提升缓存命中率。当前版本下,建议你结合上文的磁盘容量评估、细粒度列/谓词筛选与周期调度手段,把预热成本与收益控制在合理范围内。
【免费下载链接】starrocksThe world's fastest open query engine for sub-second analytics both on and off the data lakehouse. With the flexibility to support nearly any scenario, StarRocks provides best-in-class performance for multi-dimensional analytics, real-time analytics, and ad-hoc queries. A Linux Foundation project.项目地址: https://gitcode.com/GitHub_Trending/st/starrocks
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考