参考ClickHouse官方文档
https://clickhouse.com/blog/introduction-to-the-clickhouse-query-cache-and-design
问题:初步检查,目前生产环境的缓存未生效
可能存在的问题1 — 缓存大小超过了Query Cache的上限
默认情况下(当前配置),单条缓存存在两个限制:
-
单条结果字节上限 (
max_entry_size_in_bytes):1MB -
单条结果行数上限 (
max_entry_size_in_rows):30_000_000
我在生产环境grafna中检查:
确实存在超过1MB的查询结果(1.60/1.20 MiB),但是数量较少,占比约1/100
rows最大也就16,可以排除。
因此目前判断:
-
max_entry_size_in_bytes = 1MB可能是部分查询无法进入缓存的原因 -
但从比例看,它不像是“缓存整体未生效”的唯一主因
可能存在的问题2 — 查询走不同副本导致cache miss
目前生产环境确实是多副本
排查思路
生产环境是否命中 query cache,优先从三个位置排查:
-
system.query_log中的ProfileEvents['QueryCacheHits'] -
system.query_cache -
send_logs_level='trace'下的 ClickHouse 服务端日志 -
两次相同 SQL 是否落在同一个副本上(当前的多副本情况)
1. 最小验证
在历史记录中,选一条固定的耗时SQL(当前配置是大于7s)(这里以media SQL为例),连续执行两次,要求:
-
SQL 文本完全一致(复制粘贴同一条SQL)
-
两次执行间隔小于
query_cache_ttl(目前是10s) -
两次都带相同的
SETTINGS
执行后查询(根据SQL情况需要调整%FROM app.media 的参数,后面不再赘述):
SELECT
query_id,
query_duration_ms,
ProfileEvents['QueryCacheHits'] AS cache_hits,
query
FROM system.query_log
WHERE type = 'QueryFinish'
AND query LIKE '%FROM app.media%'
ORDER BY event_time DESC
LIMIT 5;预期:
-
第一次:
cache_hits = 0 -
第二次:
cache_hits > 0
如果第二次仍然是 0,继续往下查。
2. 检查是否成功写入缓存
SELECT *
FROM system.query_cache
ORDER BY expires_at DESC
LIMIT 20;情况 A:system.query_cache 为空
说明问题不在“命中失败”,而在“第一次根本没有写入缓存”。
此时重点排查:
-
查询结果是否超过
1MB -
SQL 是否包含导致缓存被跳过的因素
-
ClickHouse 是否明确记录了跳过写入的原因
可以打开 trace 日志后重跑:
SET send_logs_level = 'trace';重点查看是否出现类似日志:
-
Skipped insert (query result too big) -
No entry found for query ...
官方文档中也是通过 trace 日志定位“结果过大导致未写入缓存”的。
情况 B:system.query_cache 有条目,但第二次仍未命中
说明缓存已经写入,但第二次查询没有复用到。
这时最优先检查:两次 SQL 是否真的完全一致。
3. 检查两次 SQL 文本是否完全一致
query cache 依赖的是查询文本,而不是逻辑等价。
重点检查:
-
projectId段落顺序是否一致 -
(experimentId, key) IN (...)内部 tuple 顺序是否一致 -
SETTINGS是否完全一致 -
第二次执行时是否已经超过
query_cache_ttl
可以直接从 system.query_log 中对比查询文本:
SELECT
event_time,
query_id,
query
FROM system.query_log
WHERE type = 'QueryFinish'
AND query LIKE '%FROM app.media%'
ORDER BY event_time DESC
LIMIT 5;如果两次 query 字符串不同,即使语义相同,也不会命中。
4. 检查是否存在多副本切换
如果生产环境是多副本部署,query cache 很可能是节点本地缓存,不是跨副本共享的。(目前查到的资料显示ClickHouse不支持分布式缓存)
这种情况下:
-
第一次查询落在副本 A
-
第二次相同查询落在副本 B
即使 SQL 文本完全一致,也可能不会命中缓存。
建议从 system.query_log 中查看两次查询的执行节点信息,例如:
-
hostname -
address -
initial_address -
replica
如果能确认两次查询落在不同副本,那么“多副本切换导致 miss”就是非常高优先级的原因。
进一步可以做一个最小验证:
-- 将 your_cluster 替换为生产环境实际的 ClickHouse cluster 名称
-- first_query_id和second_query_id用对应的ID
SELECT
hostName() AS host,
event_time,
query_id,
initial_query_id,
query_duration_ms,
ProfileEvents['QueryCacheHits'] AS cache_hits,
query
FROM clusterAllReplicas('your_cluster', system.query_log)
WHERE type = 'QueryFinish'
AND query_id IN (
'first_query_id',
'second_query_id'
)
ORDER BY event_time;如果两次查询的 host 不同,同时第二次 cache_hits = 0,那么“多副本切换导致缓存未复用”就是非常强的候选原因。
5. 检查 query cache 的前置 setting
需要确认生产上实际执行的 SQL 是否带有完整的 query cache setting:
-
allow_experimental_query_cache = 1 -
use_query_cache = true -
query_cache_ttl = 10 -
query_cache_min_query_duration = 7000 -
query_cache_nondeterministic_function_handling = 'save'
如果某次实际执行的 SQL 缺少这些 setting,或者值不一致,都会影响缓存行为。
当前优先级最高的几个原因
按优先级排序,当前更可能的是:
-
部分查询结果超过
1MB,首次就没有写入缓存 -
多副本导致落在不同副本导致的
cache miss -
第二次查询已经超过
query_cache_ttl -
实际执行 SQL 没带完整 query cache settings