mz-debug-freshness について
mz-debug-freshness は Materialize の鮮度問題を調査するスキルです。依存関係と実行計画をたどって遅延の原因を特定し、対象オブジェクト、根拠、確認すべき SQL を報告します。
主な機能
- 01現在の遅延と過去のピークを集約
- 02オブジェクト間の依存をたどって遅延を追跡
- 03取り込み状態とハイドレーションを確認
- 04レプリカ負荷とオペレーターコストを分析
- 05実行計画を SQL に対応付け
活用シーン
- 01遅延の原因となるソースやシンクの特定
- 02継続中の遅延と回復済みピークの区別
- 03レプリカ競合やオペレーターの偏りの発見
- 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
詳細
Freshness のデバッグ
Freshness の問題を引き起こしているオブジェクトを 1 つ見つけてください。遅延している理由を説明します。 レポートには、原因となっているオブジェクトを挙げ、証拠を示し、高コストな処理を引き起こしている SQL を指摘してください。
ツール
このスキルは、読み取り専用ツールを 2 つ公開する Materialize 開発者 MCP サーバー上で実行されます。query_system_catalog は mz_*、pg_catalog、information_schema を読み取ります。query はクラスターを受け取り、それ以外のすべてを実行します。
EXPLAIN 文と mz_introspection リレーションです。
クライアント設定では、サーバー名をプレフィックスとして付けます。たとえば、mcp__<server>__query_system_catalog のようになります。
EXPLAIN コマンド、その出力列、オペレーターのパターンについては、
references/dataflow-analysis.md
を参照してください。以下の各ステップには、必要なコマンドが明記されています。
ワークフロー
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:証拠を集め、対象を選ぶ
対象を選ぶ前に、3 つすべての検索を実行してください。
- 現在の遅延は、現時点で遅延しているものを順位付けします。
- ステータスの一括確認は、順位付けでは見つからないものを検出します。停止したソースは現在の書き込みフロンティアを維持し、遅延を秒単位で報告するため、順位付けでは正常に見えます。ここにある行は、順位付けの結果にかかわらず問題を示しており、エラーテキストは遅延より優先されます。
- 最近のピークは、過去 24 時間を対象とします。順位付けが示すのは現在の状態のみなので、1 時間前に回復したピークは順位付けには残りません。
3 つの結果を合わせて確認してください。
- ステータスの一括確認に行がある場合は、ほかの状況にかかわらずステップ 4 に進みます。
- 順位付けで明らかに突出しているオブジェクトがあれば、それを対象とします。以下に進んでください。
- 順位付けに偏りがなく、ステータスの一括確認にも問題がない一方で、最近のピークが平均を大きく上回るオブジェクトがある場合は、そのイベントはすでに回復しています。ステップ 2 で確認する形状と、 compute gates で確認する再起動があれば、それを添えて報告してください。
- 3 つすべてに問題がなければ、遅延しているものはありません。そのように伝えてください。
以降のすべてのクエリでは、ID ではなく、クエリが返す完全修飾名 database.schema.object を使います。参照クエリにはすべて materialize.public.my_view というプレースホルダーが含まれています。調査対象の名前に置き換えてください。そうしないと、クエリ結果が空になり、オブジェクトがアイドル状態に見えてしまいます。名前解決を参照してください。
type 列によって進む経路が決まります。
| Type | Path |
|---|---|
table、source | ステップ 4。これらにはアトリビューションの追跡結果がありません。 |
materialized-view、index | ステップ 2。 |
sink | ステップ 2、その後 sink status。シンクではオペレーター分析を利用できません。 |
ステップ 2:遅延の履歴を確認する
1 回の計測では、一時的なスパイクと持続的な問題を区別できません。
mz_wallclock_global_lag_recent_history には、過去 24 時間の 1 分ごとのサンプルが格納されています。
クエリ:references/attribution.md。
遅延の増加、一定水準での停滞、回復した単発のスパイクは、それぞれ異なる問題です。最初の 2 つについては、調査を続ける価値があります。サンプル数が 1440 未満の場合、その数はオブジェクトの経過時間を分単位で表しており、ステップ 6 で必要になります。
ステップ 3:遅延の原因をホップごとに特定する
遅延は引き継がれます。mz_internal.mz_materialization_lag を使うと、その原因を特定できます。
| 列 | 意味 |
|---|---|
local_lag | このオブジェクトが直接の入力に加える遅延。 |
global_lag | ルート入力に対する遅延。 |
slowest_local_input_id | 最も遅い直接入力。ホップごとにたどります。 |
slowest_global_input_id | 最も遅いルート入力。1 回の参照で特定します。 |
クエリ:references/attribution.md。
各ホップを相互に比較してください。オブジェクトの global_lag の大部分を占めるホップが遅延の原因です。ステップ 4 以降では、そのホップを対象とし、最初に調査を始めたオブジェクトは対象にしません。突出したホップがなければ、遅延はルートで発生しています。ルートテーブルまたはソースを対象に、ステップ 4 に進んでください。
Step 4: 取り込み
遅延が発生しているのは、テーブルまたはソースです。
- ステータスとエラーテキスト:取り込み。エラーのあるソースが停止していれば、それだけで回答になります。
- 同じセクションでスナップショットの進行状況を確認します。
snapshot_committedが false の場合、ソースは初回スナップショットの取得中であり、遅延はまったく発生していません。 - 同じセクションで書き込みフロンティアを確認し、進行しているかどうかを確認します。フロンティアが進行していても、ソースの問題が解消したとは限りません。ステータスとエラーテキストのほうが優先されます。
シンクの場合は、シンクのステータスを使用します。
この3つのうち、どれか1つだけで回答できる場合があります。その場合は、その内容を報告して終了してください。
エラーテキストに上流の問題が記載されているソースは、原因を特定できています。スキーマ変更との互換性がない、パブリケーションが削除された、認証情報の有効期限が切れた、などです。修正すべきなのは Materialize より上流であり、このスキルの対象外です。
Step 5: ハイドレーションを確認してレプリカを選択する
EXPLAIN ANALYZE を実行する前に、2つの点を確認します。
ハイドレーション。データフローがハイドレーション中の場合、スナップショットの処理中です。そのため、EXPLAIN ANALYZE で測定されるのは処理への追いつきであり、処理に追いついた状態の維持ではありません。mz_internal.mz_hydration_statuses でオブジェクトがハイドレーションされていないと報告された場合は、その旨を報告し、完了後に再実行するよう依頼してください。
レプリカ。すべてのレプリカは同じ処理を行うため、どれか1つを代表として選べます。2つから選ぶ場合は、処理量が少なく、最近の offline エントリがないほうを分析してください。
クエリ:references/attribution.md。
Step 6: レプリカを消費しているものを特定する
データフローはレプリカの CPU とメモリを共有するため、遅延しているオブジェクトが、無関係な別のオブジェクトによってリソース不足になっている場合があります。
EXPLAIN ANALYZE CLUSTER CPU, MEMORY を実行し、続けて WITH SKEW を付けてもう一度実行します。詳細:クラスターレベル。
total_elapsed はデータフローの開始以降の累積値です。そのため、結論を出す前に、候補の値をオブジェクトの経過時間と照らし合わせてください。object 列の値が、以降の調査で引き継ぐ名前です。global_id は一時的なもので、参照先はありません。
ランキングは、候補同士で比較してください。レプリカが費やした時間の大部分を占めるデータフローがあれば、それが Step 7 の調査対象です。最初に調べていたオブジェクトと同じかどうかは問いません。そのデータフローと次点のデータフローとの開きが、際立っていることの根拠になります。
際立ったものがなく、レプリカに余力も残っていない場合、ワークロードはレプリカの処理能力を超えています。多くのデータフローが同じレプリカを共有しており、個別に問題のあるものはありません。これが診断結果であり、使用率の数値がその根拠です。際立ったものがなく、レプリカに余力がある場合は、結論を出せません。ランキングの最上位を原因と決めつけず、除外できた可能性を報告してください。
ステップ 7:コストの高いオペレーターを特定する
そのデータフローのオペレーターをランク付けします: オペレーターのコスト。完全修飾名を指定して実行すると、データフロー自身のクラスターで処理されます。
percent_of_dataflow の大部分を占めるオペレーターから調べます。リストの値が徐々に下がっている場合、コストは分散しており、単独で答えとなるオペレーターはありません。最大のオペレーターを挙げるのではなく、その旨を伝えてください。
結果が薄い場合、2 通りの解釈ができます。オペレーター名がその違いを示します。実際の経過時間があるオペレーターが 1 つか 2 つの場合、そのインデックスのデータフローは別のオブジェクトの出力を整えるだけです。ステップ 8 でインデックス対象を特定し、そちらを分析してください。コストが null のオペレーターが 1 つだけの場合、そのパススルーはオペレーターをまったく持ちません。処理は Read または Arranged の行に記載されたオブジェクト内にあり、そのオブジェクトは別のクラスター上にある可能性もあります。その名前をステップ 6 に戻してください。
上位のオペレーターだけについて、分布を確認します: オペレーターの偏り。
max_worker_ratio がワーカー数に近づくほど高くなる場合、1 つのワーカーがそのオペレーターを処理しており、データフローはそのワーカーの速度で実行されています。そのオペレーターを挙げたうえで、それが空のキーを持つ Arrange かどうかを確認してください。キーが空だと、すべてのレコードが 1 つのワーカーに送られます。これはクロス結合、または述語がすべて不等式である結合で発生します。処理を分散するには、結合に少なくとも 1 つの等価条件が必要です。求められた場合は、ホットキーの特徴を説明してください。
コストの高いオペレーターの比率が 1 に近い場合、偏りではなく、プラン自体のコストが高いことを意味します。結合順序と、さらに下流へ押し下げられるフィルターや射影を確認してください。物理プランの Source セクションには、project、filter、pushdown が表示されます。
オペレーターの意味:オペレーターの読み方。
ステップ 8:オペレーターを SQL に対応付ける
- コストの高いオペレーターとその
lir_idの値は、ステップ 7 ですでに取得しています。 - SQL を所有するオブジェクトを特定します。マテリアライズドビューの場合は、そのオブジェクト自体です。インデックスの場合は、
mz_indexes.on_idを使ってインデックス対象を特定します。マテリアライズドビューに対するインデックスは、そのビューの遅延を引き継ぐため、代わりにマテリアライズドビューを分析します。一方、通常のビューに対するインデックスは、そのビューの本体全体を自身のデータフローに含みます。 - データフローにインライン化されているビューを特定します。通常のビューには独自のデータフローがないため、その本体はそれを使用するオブジェクトの内部に構築され、コストの高いオペレーターがそこから生じている可能性があります。
mz_object_dependenciesを再帰的にたどり、型がviewのすべての依存関係を集めます。マテリアライズドビュー、テーブル、ソースは独自のデータフローを持つ境界です。クエリはリファレンスにあります。 EXPLAIN PHYSICAL PLAN WITH (node identifiers) AS TEXT FOR ...を実行します。各ノードには// { node_id: LirId(N) }があり、ステップ 1 で取得した ID と一致します。また、列名、結合述語、グループキーも確認できます。- ステップ 2 と 3 で取得した定義を
SHOW CREATE MATERIALIZED VIEWまたはSHOW CREATE VIEWで読み、プランノードの元になった句を見つけます。
クエリと実例: references/dataflow-analysis.md。
ステップ 9:報告する
すべての項目を埋めてください。「None」も有効な値であり、省略するより有用です。
**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>
句、それを含むビュー、データフローをそれぞれ別に記載してください。異なる場合は、修正すべきデータフローと編集すべきファイルの両方が読者に伝わるようにします。
報告書が成果物です。改善策は別途依頼してください。