mcpField
M
mz-debug-freshness

mz-debug-freshness

github.com/materializeinc其他339 次安装1 天前更新
代码仓库

关于 mz-debug-freshness

mz-debug-freshness 是用于诊断 Materialize 数据新鲜度问题的技能,按延迟、状态和执行计划逐步定位原因。它要求报告指出具体对象、证据以及相关 SQL 所在位置。

主要功能

  1. 01汇总当前延迟与近期峰值
  2. 02沿依赖关系追踪延迟来源
  3. 03检查摄取状态与水合进度
  4. 04分析副本负载及算子成本
  5. 05将执行计划映射回 SQL

适用场景

  1. 01查明导致延迟的源或汇
  2. 02区分持续延迟与已恢复峰值
  3. 03定位副本资源争用或算子倾斜
  4. 04确定需要修改的视图及 SQL

MCP 相关

该技能使用 Materialize developer MCP server 提供的只读工具:query_system_catalog 和 query,分别查询系统目录以及执行 EXPLAIN 和 introspection 查询。

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/materializeinc/agent-skills --skill mz-debug-freshness

安装后在 Claude Code 中输入/mz-debug-freshness确认已加载。

附带文件references/attribution.mdreferences/dataflow-analysis.md

详细介绍

调试新鲜度

找出导致新鲜度问题的那个对象。说明它为什么落后。 报告必须指出问题对象、展示证据,并指出导致昂贵工作的 SQL。

工具

此技能运行在 Materialize 开发者 MCP 服务上,该服务提供两个 只读工具。query_system_catalog 读取 mz_*、pg_catalog 和 information_schema。query 接受一个集群,并运行其他所有内容: EXPLAIN 语句和 mz_introspection 关系。 客户端配置会在工具名称前加上服务名称,例如 mcp__<server>__query_system_catalog。

references/dataflow-analysis.md 中列出了 EXPLAIN 命令、其输出列以及运算符模式。 下面的每个步骤都会指明所需的确切命令。

工作流

digraph freshness {
    "Trigger: something is behind" [shape=ellipse];
    "Gather: ranking, status sweep, recent peaks" [shape=box];
    "What does the evidence show?" [shape=diamond];
    "Report: unhealthy source or sink" [shape=doublecircle];
    "Report: peak already recovered" [shape=doublecircle];
    "Report: nothing is behind" [shape=doublecircle];
    "What type is the object?" [shape=diamond];
    "Does one hop carry most of the lag?" [shape=diamond];
    "Is the dataflow hydrated?" [shape=octagon, style=filled, fillcolor=red];
    "Report: still hydrating, re-run later" [shape=doublecircle];
    "Report: lag enters at the root" [shape=doublecircle];
    "Does one dataflow dominate the replica?" [shape=diamond];
    "Is the load spread across dataflows?" [shape=diamond];
    "Report: replica undersized for its workload" [shape=doublecircle];
    "Report: inconclusive, list ruled out" [shape=doublecircle];
    "Is one worker carrying the operator?" [shape=diamond];
    "Report: culprit operator and SQL" [shape=doublecircle];

    "Trigger: something is behind" -> "Gather: ranking, status sweep, recent peaks";
    "Gather: ranking, status sweep, recent peaks" -> "What does the evidence show?";
    "What does the evidence show?" -> "Report: unhealthy source or sink" [label="status not running, Step 4"];
    "What does the evidence show?" -> "What type is the object?" [label="an object is behind now"];
    "What does the evidence show?" -> "Report: peak already recovered" [label="only a past peak, Step 2"];
    "What does the evidence show?" -> "Report: nothing is behind" [label="all three clean"];
    "What type is the object?" -> "Report: unhealthy source or sink" [label="table or source, Step 4"];
    "What type is the object?" -> "Does one hop carry most of the lag?" [label="view, index, sink, Step 3"];
    "Does one hop carry most of the lag?" -> "Report: lag enters at the root" [label="no"];
    "Does one hop carry most of the lag?" -> "Is the dataflow hydrated?" [label="yes, Step 5"];
    "Is the dataflow hydrated?" -> "Report: still hydrating, re-run later" [label="no"];
    "Is the dataflow hydrated?" -> "Does one dataflow dominate the replica?" [label="yes, Step 6"];
    "Does one dataflow dominate the replica?" -> "Is one worker carrying the operator?" [label="yes, Step 7"];
    "Does one dataflow dominate the replica?" -> "Is the load spread across dataflows?" [label="no"];
    "Is the load spread across dataflows?" -> "Report: replica undersized for its workload" [label="yes"];
    "Is the load spread across dataflows?" -> "Report: inconclusive, list ruled out" [label="no"];
    "Is one worker carrying the operator?" -> "Report: culprit operator and SQL" [label="skew"];
    "Is one worker carrying the operator?" -> "Report: culprit operator and SQL" [label="expensive plan"];
}

步骤 1:收集证据并选择调查对象

选择调查对象之前,先运行全部三项查询。

  1. 当前延迟 对当前落后的对象进行排名。
  2. 状态扫描 能发现排名无法捕捉的情况:停滞的源会保持当前写入前沿,并报告数秒的延迟,因此在排名中看起来状态正常。此处的任何一行都是一项发现,无论排名结果如何;其错误文本的优先级高于延迟。
  3. 近期峰值 涵盖过去 24 小时。排名只描述当前这一刻,因此一小时前已恢复的峰值不会在排名中留下痕迹。

结合查看这三项结果:

  • 状态扫描中有行,就转到步骤 4,无论其他情况如何。
  • 排名中有一个对象明显突出,它就是调查对象。继续执行下文。
  • 排名没有明显差异,状态扫描也没有异常,但某个对象的近期峰值远高于其平均值,说明事件已恢复。报告这一点,并附上步骤 2 中的形态以及 计算门控 中的任何重启信息。
  • 三项结果都正常,说明没有对象落后。请明确说明这一点。

下游所有查询都以其返回的完整限定名称 database.schema.object 为依据,绝不使用 ID。参考查询都包含一个 materialize.public.my_view 占位符。请替换为你正在调查的名称,否则查询结果为空,对象看起来也会处于空闲状态。参见 名称解析。

type 列决定后续路径:

类型路径
table、source步骤 4。这些对象不会出现在归因追踪中。
materialized-view、index步骤 2。
sink步骤 2,然后查看 sink 状态。无法对 sink 进行运算符分析。

步骤 2:查看延迟历史

单次读数无法区分突发峰值和持续性问题。 mz_wallclock_global_lag_recent_history 会保留过去 24 小时内每分钟一个样本。

查询:references/attribution.md。

延迟不断攀升、停留在平台期,以及出现单次峰值后恢复,是三种不同的问题。前两种值得继续调查。样本数少于 1440,也代表对象的年龄(分钟数);步骤 6 会用到这一信息。

步骤 3:将延迟归因到某个环节

延迟会沿链路传递,mz_internal.mz_materialization_lag 会对其进行归因。

列含义
local_lag此对象在其直接输入之上增加的延迟。
global_lag相对于其根输入的延迟。
slowest_local_input_id此对象最慢的直接输入,沿链路逐环节追踪。
slowest_global_input_id此对象最慢的根输入,可一次跳转到达。

查询:references/attribution.md。

比较各个环节。承载对象大部分 global_lag 的环节造成了延迟,也是步骤 4 及之后要调查的对象,而不是你最初选定的对象。如果没有哪个环节明显突出,说明延迟是从根节点进入的:继续步骤 4,并调查根表或源。

步骤 4:摄取

延迟始于某个表或数据源。

  1. 状态和错误文本:摄取。如果数据源停滞且存在错误,这就是完整的答案。
  2. 快照进度,同一章节。snapshot_committed 为 false 表示数据源仍在执行初始快照,并未落后。
  3. 写入前沿,同一章节,用于查看前沿是否在推进。前沿持续推进并不能排除数据源问题:状态和错误文本的优先级更高。

对于 sink,请查看 sink 状态。

这三项中的任意一项都可能构成完整答案。若是如此,请报告该项并停止排查。

如果数据源的错误文本指出了上游问题,例如不兼容的 schema 变更、发布被删除或凭证过期,那么问题已确诊。修复点位于 Materialize 上游,不在此技能的范围内。

步骤 5:确认水合状态并选择一个副本

执行任何 EXPLAIN ANALYZE 前,先检查两项。

水合状态。正在水合的数据流正在处理快照,因此 EXPLAIN ANALYZE 衡量的是追赶进度,而不是维持进度的能力。如果 mz_internal.mz_hydration_statuses 报告对象尚未水合,请报告这一点,并要求在水合完成后重新运行。

副本。每个副本执行的工作都相同,因此任选一个即可代表。若有两个副本,分析工作量较少且近期没有 offline 记录的那个。

查询:references/attribution.md。

步骤 6:找出消耗副本资源的对象

数据流共享副本的 CPU 和内存,因此,落后的对象可能是被另一个无关对象挤占了资源。

运行 EXPLAIN ANALYZE CLUSTER CPU, MEMORY,然后再运行一次并加上 WITH SKEW。详情:集群级别。

total_elapsed 是自数据流启动以来的累计值,因此,在得出结论前,请将候选对象与其在对象运行时长中显示的年龄进行比较。object 列是后续要追踪的名称;global_id 是临时值,无法用于查找。

将排名与排名本身进行比较。无论它是不是你最初关注的对象,占用副本大部分累计资源的数据流都是步骤 7 的排查对象。它与排名紧随其后的数据流之间的差距,证明了它为何突出。

如果没有对象特别突出,且副本已无剩余余量,则说明工作负载超出副本的承载能力:许多数据流共享该副本,但没有任何一个单独出错。这就是诊断结果,而利用率数据就是证据。如果没有对象特别突出,且副本仍有余量,则结果尚无定论。请报告已排除的可能性,不要将排名第一的对象直接认定为罪魁祸首。

步骤 7:找出开销高的算子

对该数据流的算子进行排序: 算子开销。它接收全限定名称,并在数据流自身所在的集群上运行。

如果某个算子占据了大部分 percent_of_dataflow,就从它入手。列表中的数值逐渐下降,说明开销分散,没有哪个单一算子是答案。应如实说明这一点,而不是只指出开销最大的算子。

精简的结果有两种解读方式,算子名称可以帮助区分。如果一两个算子有实际运行时间,说明这是一个索引,其数据流只负责整理另一个对象的输出:在步骤 8 中找出它索引的对象,并改为分析该对象。如果只有一个算子且开销为 null,说明这是一个不拥有任何算子的直通算子:实际工作位于其 Read 或 Arranged 行所指名的对象中,该对象可能位于另一个集群,因此将该名称带回步骤 6。

只检查开销最高的算子的分布情况: 算子倾斜。

max_worker_ratio 趋近于工作器数量,意味着有一个工作器在承担该算子的工作,数据流的运行速度受该工作器限制。指出该算子,然后检查它是否是一个键为空的 Arrange。空键会将每条记录都路由到同一个工作器;交叉联接或谓词全是不等式的联接都会出现这种情况。联接至少需要一个等式才能分散工作。若有人询问热点键,请说明其特征。

开销高的算子若比率接近 1,说明计划本身开销高,而不是存在倾斜。检查联接顺序,以及可以进一步下推的筛选条件和投影。物理计划的 Source 部分列出了 project、filter 和 pushdown。

算子含义:解读算子。

步骤 8:将算子映射回 SQL

  1. 在步骤 7 中,你已获得开销高的算子及其 lir_id 值。
  2. 找出哪个对象拥有该 SQL。对于物化视图,对象本身就是 SQL 所属对象。对于索引,通过 mz_indexes.on_id 找出它所索引的对象:物化视图上的索引继承该视图的延迟,因此应改为分析物化视图;而普通视图上的索引会在自身数据流中包含整个视图主体。
  3. 找出内联到该数据流中的视图。普通视图没有自己的数据流,因此其主体会在使用它的对象中构建,开销高的算子可能就来自那里。递归遍历 mz_object_dependencies,收集类型为 view 的所有依赖项;物化视图、表或源则是拥有自身数据流的边界。查询方式见参考资料。
  4. 运行 EXPLAIN PHYSICAL PLAN WITH (node identifiers) AS TEXT FOR ...。每个节点都有 // { node_id: LirId(N) },与步骤 1 中的 ID 相匹配,还会列出列名、联接谓词和分组键。
  5. 使用 SHOW CREATE MATERIALIZED VIEW 或 SHOW CREATE VIEW,查看步骤 2 和 3 中的定义,并找出计划节点对应的子句。

查询和完整示例: references/dataflow-analysis.md。

步骤 9:报告

填写每个字段。“无”是有效值,而且比省略更有用。

**Culprit:** <fully qualified name> on cluster <cluster>, replica <replica>
**Classification:** unhealthy source or sink | recovered peak, nothing behind now
| still hydrating | starved by another dataflow | worker skew | expensive plan |
undersized replica | lag enters at the root | nothing behind | inconclusive
**Evidence:**
- Lag: <object> at <lag>, history <shape> over <samples> minutes
- Attribution: <hop chain, with the local_lag of the hop that owns it>
- Replica: <cpu_percent>, <heap_percent>, top dataflow <percent of total>
**Expensive operator:** <operator> at <percent_of_dataflow>, worker ratio <ratio>
**SQL:** <clause>, in <fully qualified view name>, inlined into <dataflow name>
**Ruled out:** <what was checked and found healthy>
**Unexplained:** <what the evidence does not account for>

如果子句、包含该子句的视图和数据流各不相同,请分别指出,让读者既能看到需要修复的数据流,也能看到需要编辑的文件。

报告是最终交付物。补救措施属于单独的请求。

评论

支持 Markdown