debug-plan-cache-misses
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseDebug Chalk query plan cache misses
调试 Chalk 查询计划缓存未命中
Chalk caches a compiled query plan the first time it plans a query, keyed by a
normalized . A cache miss forces the engine to replan on the
hot path, which is slow. Misses are expected the first time a query shape is
seen; they are a problem when a query that "looks identical" to a previous
one keeps missing — that means some field in the request is varying between
calls.
BatchQueryThis skill: (1) finds misses in the logs, and (2) diffs the planned queries to
identify exactly which field differs and forces the miss.
Chalk 会在首次规划查询时缓存编译后的查询计划,其缓存键是规范化后的
。缓存未命中会迫使 engine 在热路径上重新规划,因此速度很慢。
首次遇到某种查询结构时,未命中属于预期现象;但如果一个与此前“看起来相同”的
查询持续未命中,就构成问题——这意味着请求中的某个字段在两次调用之间
发生了变化。
BatchQuery此 skill 会:(1) 从日志中查找未命中记录;(2) 对比规划后的查询,精确找出
导致未命中的差异字段。
What the cache key actually is
实际的缓存键
The plan cache key wraps the entire normalized . The only
normalization applied is that are resolved to their
effective values — so leaving an option unset and setting it explicitly to its
default produce the same key and will not cause a spurious miss.
BatchQueryplanner_optionsEverything else in the request is part of the key, including:
- — the exact set of input features the caller provides
given_features - target features (/
optional_target_features)required_target_features - ,
tagsrequired_resolver_tags - and staleness/recompute flags
max_staleness_overrides query_name_and_version- and the remaining fields
BatchQuery
So any difference in these forces a new plan. The single most common
real-world cause is a varying set — the caller sometimes
supplies more or fewer input features (e.g. raw inputs, or derived
inputs like ) than a previous call. A query that provides a
different set of inputs genuinely needs a different plan (different resolvers
must run), so it cannot reuse the cached one.
given_features*_originalemail_username计划缓存键封装了整个规范化后的 。唯一的规范化处理,是将
解析为生效值——因此,将某个配置项留空与显式设置为其
默认值会产生相同的键,不会导致虚假的未命中。
BatchQueryplanner_options请求中的其他所有内容都属于缓存键,包括:
- ——调用方提供的输入特征精确集合
given_features - 目标特征(/
optional_target_features)required_target_features - 、
tagsrequired_resolver_tags - 以及 staleness/recompute 标志
max_staleness_overrides query_name_and_version- 中剩余的字段
BatchQuery
因此,上述任何差异都会强制生成新的计划。最常见的现实原因是
集合发生变化——调用方有时会提供更多或更少的输入特征
(例如有时包含原始 输入,有时不包含;或者包含
这样的派生输入)。提供不同输入集合的查询确实需要不同的计划(必须运行不同的
resolver),因此无法复用缓存中的计划。
given_features*_originalemail_usernameThe two log lines that signal a miss
表示未命中的两条日志
The engine emits these on the planning path:
-
Ad-hoc miss warning ():
batch_online_query_service.pyAd-hoc query request with query name/version '(<name>, <version>)' missed the plan cache. The query was: <BatchQuery ...> -
Plan computation — emitted on every miss (ad-hoc or named), and the most useful line for debugging ():
local_plan_factory.pyComputed plan for [<target features>] given [<given features>] with planner options <NonNullPlannerOptions(...)>; query.query_name_and_version=(<name>, <version>)
There are also statsd counters if you want to trend rather than read lines:
vs
.
chalk.engine.planner.python_plan_cache_misschalk.engine.planner.python_plan_cache_hitengine 会在规划路径上发出以下两类日志:
-
Ad-hoc 未命中警告():
batch_online_query_service.pyAd-hoc query request with query name/version '(<name>, <version>)' missed the plan cache. The query was: <BatchQuery ...> -
计划计算——每次未命中时都会发出,无论是 ad-hoc 还是 named;这也是调试时 最有用的日志():
local_plan_factory.pyComputed plan for [<target features>] given [<given features>] with planner options <NonNullPlannerOptions(...)>; query.query_name_and_version=(<name>, <version>)
如果希望观察趋势而不是逐行阅读,还可以使用 statsd counters:
与
。
chalk.engine.planner.python_plan_cache_misschalk.engine.planner.python_plan_cache_hitFinding misses with chalk logs
chalk logs使用 chalk logs
查找未命中
chalk logschalk logschalk config--environmentSearch for the miss warnings over the last day:
sh
chalk logs --query 'message:"missed the plan cache"' \
--start-time "24h ago" --end-time "now"Search for plan computations for one query name (best for diffing):
sh
chalk logs --query 'message:"Computed plan for" message:sv5_idplus_features_parallel' \
--start-time "24h ago" --end-time "now"Trend miss volume over time in 10-minute buckets:
sh
chalk logs --aggregate --window-period 10m \
--query 'message:"missed the plan cache"' --start-time "6h ago"Tail misses live while reproducing:
sh
chalk logs --follow --query 'message:"missed the plan cache"'chalk logschalk config--environment搜索过去一天的未命中警告:
sh
chalk logs --query 'message:"missed the plan cache"' \
--start-time "24h ago" --end-time "now"搜索某个 query name 的计划计算日志(最适合用于 diff):
sh
chalk logs --query 'message:"Computed plan for" message:sv5_idplus_features_parallel' \
--start-time "24h ago" --end-time "now"按 10 分钟桶查看未命中数量的趋势:
sh
chalk logs --aggregate --window-period 10m \
--query 'message:"missed the plan cache"' --start-time "6h ago"复现问题时实时跟踪未命中:
sh
chalk logs --follow --query 'message:"missed the plan cache"'Query syntax
查询语法
field:value.message:"missed the plan cache"query_name:"my.query"- — substring match on the log message (use this for the two lines above)
message - — Chalk component, e.g.
componentcomponent:engine - — filter to a specific named query
query_name - — internal query id;
operation_id— caller-supplied query idcorrelation_id - ,
trace_id,pod_name,resource_group,deploymentapp - — match across multiple fields at once
all_filter
Time flags: / accept , , or
ISO-8601 (). Add for an interactive viewer.
--start-time--end-time'1h ago''now''2024-01-01T00:00:00Z'--tui使用以空格分隔的 对(隐式 AND)。如果值包含空格或 ,请加引号——
例如 、。常用字段:
field:value.message:"missed the plan cache"query_name:"my.query"- ——对日志消息进行子字符串匹配(用于查找上述两种日志)
message - ——Chalk component,例如
componentcomponent:engine - ——筛选特定的 named query
query_name - ——内部 query id;
operation_id——调用方提供的 query idcorrelation_id - 、
trace_id、pod_name、resource_group、deploymentapp - ——一次匹配多个字段
all_filter
时间参数 / 接受 、 或 ISO-8601
()。添加 可打开交互式查看器。
--start-time--end-time'1h ago''now''2024-01-01T00:00:00Z'--tuiDebugging workflow
调试工作流
-
Confirm misses are happening and how often:sh
chalk logs --query 'message:"missed the plan cache"' --start-time "24h ago"If this is empty, plan caching is working — look elsewhere for the latency. -
Pull thelines for the affected query name:
Computed plan forshchalk logs --query 'message:"Computed plan for" message:<query_name>' \ --start-time "24h ago" -
Take the two most recent computations and diff them field by field: compare thelists first (most common culprit), then the target-feature lists, then the
given [...]block. Whatever differs is the cause of the miss. Because planner options are normalized before keying, a difference there is a real difference — not a set-vs-default artifact.planner options -
Attribute the difference to the caller. A varyingset almost always means an upstream service is sending an inconsistent input schema (sometimes including raw/derived inputs, sometimes not). The fix is to make the caller send a stable
givenset on every request so the key is stable and subsequent identical queries hit the cache.given_features
-
确认未命中是否正在发生以及发生频率:sh
chalk logs --query 'message:"missed the plan cache"' --start-time "24h ago"如果结果为空,说明计划缓存工作正常——应转而排查其他延迟原因。 -
拉取受影响 query name 对应的日志:
Computed plan forshchalk logs --query 'message:"Computed plan for" message:<query_name>' \ --start-time "24h ago" -
取出最近两次计算结果,逐字段进行 diff:先比较列表 (最常见的原因),然后比较目标特征列表,最后比较
given [...]块。 存在差异的字段就是导致未命中的原因。由于 planner options 在生成缓存键前 会进行规范化,因此其中的差异是真实差异,而不是“未设置与默认值不同”造成的 假象。planner options -
将差异归因于调用方。不断变化的集合几乎总是意味着上游服务发送了 不一致的 input schema(有时包含 raw/derived inputs,有时不包含)。修复方法是 让调用方在每次请求中发送稳定的
given集合,从而保持缓存键稳定, 让后续相同的查询命中缓存。given_features
Notes
注意事项
- Two byte-identical requests should hit the cache on the second call. If they don't, that's a distinct issue from a field mismatch (e.g. cache not being populated/shared) — call it out separately rather than blaming the request.
- Fixing a miss only helps if all callers converge on one request shape. If
some callers still send a different set, those keep missing.
given - This skill reads logs and reasons about the request; it does not mutate any environment. Any remediation (changing what a caller sends) happens in the caller's code, not here.
- 两个逐字节完全相同的请求,第二次调用应当命中缓存。如果未能命中,则属于另一个 问题(例如缓存未填充或未共享)——应单独指出,而不要归咎于请求字段不一致。
- 只有当所有调用方都统一到一种请求结构时,修复未命中才会有效。如果某些
调用方仍发送不同的 集合,它们仍会持续未命中。
given - 此 skill 只读取日志并分析请求;不会修改任何 environment。任何修复措施 (例如更改调用方发送的内容)都应在调用方代码中完成,而不是在此处完成。