Query Cache
StarRocks 提供的 Query Cache 特性,可以帮助您极大地提升聚合查询的性能。开启 Query Cache 后,每次处理聚合查询时,StarRocks 都会将本地聚合的中间结果缓存于内存中。这样,后续收到相同或类似的聚合查询时,StarRocks 就能够直接从 Query Cache 获取匹配的聚合结果,而无需从磁盘读取数据并进行计算,大大节省查询的时间和资源成本,并提升查询的可扩展性。在大量用户同时对复杂的大数据集执行相同或类似查询的高并发场景下,Query Cache 的优势尤为明显。
该特性从 2.5 版本开始支持。
在 2.5 版本,Query Cache 仅支持宽表模型下的单表聚合查询。自 3.0 版本起,除宽表模型下的单表聚合查询外,Query Cache 还支持星型模型下简单多表 JOIN 的聚合查询。
应用场景
Query Cache 可以生效的典型应用场景有如下特点:
- 查询多为宽表模型下的单表聚合查询或星型模型下简单多表 JOIN 的聚合查询。
- 聚合查询以非 GROUP BY 聚合和低基数 GROUP BY 聚合为主。
- 查询的数据以按时间分区追加的形式导入,并且在不同时间分区上的访问表现出冷热性。
目前 Query Cache 支持的查询需要满足下面条件:
-
查询的执行引擎为 Pipeline。
说明
除 Pipeline 以外的其他执行引擎不支持 Query Cache。
-
查询的表为原生 OLAP 表(自 2.5 版本起支持)或存算分离表(自 3.0 版本起支持)。不支持外表上的查询。查询计划中,实际访问的是同步物化视图时,Query Cache 也 可以生效。异步物化视图暂不支持。
-
查询为单表聚合查询或多表 JOIN 的聚合查询。
说明
- Query Cache 支持 Broadcast Join 和 Bucket Shuffle Join。
- Query Cache 支持含 Join 算子的两种树形:先聚合后 Join 和 先 Join 后聚合。在先聚合后 Join 的树形结构中,不支持 Shuffle Join。在先 Join 后聚合的树形结构中,不支持 Hash Join。
-
查询不包含
rand
、random
、uuid
和sleep
等不确定性 (Nondeterminstic) 函数。
Query Cache 支持全部数据分区策略,包括 Unpartitioned、Multi-Column Partitioned 和 Single-Column Partitioned。
产品边界
- Query Cache 依赖于 Pipeline 执行引擎的 Per-Tablet 计算。Per-Tablet 计算是指一个 Pipeline Driver 能够以 Tablet 为单位对整 Tablet 进行处理,而不是每次只处理一个 Tablet 的一部分、或者通过交叉并发的方式同时处理多个 Tablet。如果单个 BE 所访问的 Tablet 的数量大于等于实际调用的 Pipeline Driver 的数量(即,实际并发度)时,则启用 Query Cache。如果单个 BE 所访问的 Tablet 的数量小于 Pipeline Driver 的数量,则每个 Pipeline Driver 只会处理某个 Tablet 的一部分数据,无法形成 Per-Tablet 的计算结果,这种情况下不启用 Query Cache。
- 在 StarRocks 中,一个聚合查询至少包含四个阶段的聚合。在一阶段聚合中,只有当 OlapScanNode 和 AggregateNode 位于同一个 Fragment 时,AggregateNode 产生的 Per-Tablet 计算结果才会缓存。在其他阶段聚合中,AggregateNode 产生的Per-Tablet 计算结果不会缓存。部分 DISTINCT 聚合查询,受会话变量
cbo_cte_reuse
为true
影响,当执行计划中生产数据的 OlapScanNode 和消费数据的一阶段 AggregateNode 位于不同的 Fragment、并且中间通过 ExchangeNode 传输数据时,也不启用 Query Cache。比如如下两个场景里,采用 CTE 优化,不启用 Query Cache:- 查询的输出列包含聚合函数
avg(distinct)
。 - 查询的输出列含多个 DISTINCT 聚合函数。
- 查询的输出列包含聚合函数
- 如果在聚合之前对数据进行了 Shuffle 操作,则 Query Cache 无法加速对该数据的查询。
- 如果表的分组列或去重列是高基数列 (High-Cardinality Column),则对该表执行聚合查询生成的结果会很大。这类查询会在运行时绕过 Query Cache。
- Query Cache 的存储占用 BE 的少量内存,默认缓存大小为 512 MB,因此不宜缓存较大的数据项。此外,在启用 Query Cache 的情况下,如果缓存的命中率低,则会带来性能惩罚。因此,在查询的计算过程中,如果某一个 Tablet 上的计算结果大小超过了
query_cache_entry_max_bytes
或query_cache_entry_max_rows
参数指定的阈值,则该查询接下来的计算不再开启 Query Cache,转而触发使用 Passthrough 机制来执行。
原理介绍
启用 Query Cache 时,BE 会把查询的本地聚合拆分为以下两个阶段:
-
Per-tablet 聚合
BE 逐个处理查询所涉及的每个 Tablet。在处理某一个 Tablet 时,BE 首先会检查 Query Cache 中是否存在该 Tablet 的中间计算结果。如果存在(缓存命中),则 BE 直接取用该结果;如果不存在(缓存未命中),则 BE 从磁盘上读取该 Tablet 的数据并进行本地聚合,然后将聚合得到的中间计算结果填充到 Query Cache,以供后续类似查询使用。
-
Inter-tablet 聚合
BE 收集查询所涉及的所有 Tablet 的中间计算结果,并将这些结果合并成最终结果。
后续发起的类似查询,就可以复用之前缓存的查询结果。比如下图所示的查询,一共涉及三个 Tablet(编号 0 到 2),Query Cache 中缓存了第一个Tablet(即 Tablet 0)的中间结果。此时,BE 可以从 Query Cache 直接获取 Tablet 0 的中间计算结果,而不必访问磁盘上的数据。如果 Query Cache 完全预热,就会包含所有三个 Tablet 的中间计算结果,此时,BE 不需要访问磁盘上的任何数据。
为释放额外占用的内存,Query Cache 采用基于“最近最少使用” (Least Recently Used,简称 LRU) 算法的移出策略对缓存条目进行管理。当 Query Cache 占用的内存超过 query_cache_capacity
参数中设置的缓存大小时,最近最少使用的缓存条目会移出 Query Cache。
说明
未来 Query Cache 还将支持基于 Time to Live (TTL) 的移出策略。
FE 判定各个查询是否需要通过 Query Cache 进行加速,并对查询进行规范化处理,以消除对查询语义没有影响的一些细微的文字细节。
为了防止在某些不适用的场景下由于开启 Query Cache 而导致性能损失,BE 会采用自适应策略在运行时绕过 Query Cache。
开启 Query Cache
本小节介绍用于开启和配置 Query Cache 的参数和会话变量。
FE 会话变量
变量 | 默认值 | 是否支持动态修改 | 说明 |
---|---|---|---|
enable_query_cache | false | 是 | 指定是否开启 Query Cache。取值范围:true 和 false 。true 表示开启,false 表示关闭。开启该功能后,只有当查询满足本文“应用场景”小节所述之条件时,才会启用 Query Cache。 |
query_cache_entry_max_bytes | 4194304 | 是 | 指定触发 Passthrough 模式的阈值。取值范围:0 ~ 9223372036854775807 。当一个 Tablet 上产生的计算结果的字节数或者行数超过 query_cache_entry_max_bytes 或 query_cache_entry_max_rows 指定的阈值时,则查询采用 Passthrough 模式执行。当 query_cache_entry_max_bytes 或 query_cache_entry_max_rows 取值为 0 时, 即便 Tablet 产生结果为空,也采用 Passthrough 模式。 |
query_cache_entry_max_rows | 409600 | 是 | 同上。 |
BE 配置项
您需要在 BE 配置 文件 be.conf 里设置下面参数。更改下面参数的设置以后,需要重启 BE 才能使参数设置生效。
参数 | 必填 | 描述 |
---|---|---|
query_cache_capacity | 否 | 指定 Query Cache 的大小。单位:字节。默认为 512 MB。 每个 BE 都有自己的 Query Cache,并且只填充 (Populate) 和检查 (Probe) 自己的 Query Cache。Query Cache 占用的是所在 BE 的内存。 注意 Query Cache 大小不能不低于 4 MB。如果当前的 BE 内存容量无法满足您期望的 Query Cache 大小,可以增加 BE 的内存容量,然后再设置合理的 Query Cache 大小。 |
确保最大缓存命中率
即使后续查询与先前发起的结果已缓存的查询在语义上并不完全对等,Query Cache 依然能够有效提升缓存命中率,主要包括以下查询场景:
- 语义等价的查询
- 扫描分区重合的查询
- 仅涉及追加写入(无 UPDATE 或 DELETE 操作)数据的查询
语义等价的查询
如果两个查询相似(这两个查询不一定字面上完全一致、但是其执行计划在语义上是相等的),则这两个查询可以复用彼此先前计算的结果。通俗地说,语义等价是指两个查询计算的数据来源相同、计算方式相同、并且具有相似的执行计划。严格地说,判定两个查询是否语义等价的规则如下:
-
两个查询如果包含多次聚合,只要这两个查询中的第一次聚合是语义等价的,则判定为语义等价。例如下面两个查询,Q1 和 Q2。Q1 的第一次聚合和 Q2 的第一次聚合是等价的,因此这两个查询的计算结果可以彼此复用。
-
Q1
SELECT
(
ifnull(sum(murmur_hash3_32(hour)), 0) + ifnull(sum(murmur_hash3_32(k0)), 0) + ifnull(sum(murmur_hash3_32(__c_0)), 0)
) AS fingerprint
FROM
(
SELECT
date_trunc('hour', ts) AS hour,
k0,
sum(v1) AS __c_0
FROM
t0
WHERE
ts BETWEEN '2022-01-03 00:00:00'
AND '2022-01-03 23:59:59'
GROUP BY
date_trunc('hour', ts),
k0
) AS t;
-