root-cause-analysis について
root-cause-analysisは、OpenTelemetryのトレース、ログ、メトリクスを使って本番環境の問題を調べるKopai向けスキルです。TypeScript SDKのクエリでエラーや証拠を関連付け、影響を測定して原因候補を報告します。
主な機能
- 01型付きTypeScript SDKクエリ
- 02トレース、ログ、メトリクスの分析
- 03エラー率とスループットによる順位付け
- 04時系列による影響分析
- 05トレースとログの相関
- 06SQLiteクエリの制約説明
活用シーン
- 01インシデント対応でのサービス順位付け
- 02関連ログをたどったエラー調査
- 03問題の発生時期と影響範囲の測定
- 04レイテンシやメトリクス変化の調査
MCP 対応
Kopai SDKとCLIを使うスキルであり、MCPサーバーやクライアントではありません。READMEにMCP連携の説明はありません。
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ kopai-app/ kopai-mono --skill root-cause-analysisインストール後、Claude Code で/root-cause-analysisを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
tile.jsonreferences/log-filters.mdmetric-filters.mdtrace-filters.mdrules/_sections.mdpattern-distributed.mdpattern-http-errors.mdpattern-log-driven.mdpattern-slow-requests.mdworkflow-check-metrics.mdworkflow-correlate-logs.mdworkflow-find-errors.mdworkflow-get-context.mdworkflow-identify-cause.md詳細
Kopai による根本原因分析
テレメトリ(トレース、ログ、メトリクス)から本番環境の問題をデバッグします。Kopai の分析機能であるエラー率、スループット、レイテンシー、グループ化、時系列のバケット化は、SDK のクエリ APIに含まれており、CLI では利用できません。そのため、調査は必ず短い TypeScript スクリプトから始め、CLI は簡単な参照用の代替手段として使います。
前提条件
- サービスから Kopai に OpenTelemetry データが送信されていること(
otel-instrumentationスキルを参照)。 - プロジェクトに
@kopai/sdkがインストールされていること:npm i @kopai/sdk(クエリ API と.kopaircリーダーが含まれています)。
コードモード(推奨)
接続 — clientFromConfig() は CLI と同じ方法で .kopairc を読み込みます:
import { clientFromConfig } from "@kopai/sdk/node";
// Reads ./.kopairc then ~/.kopairc; defaults to http://localhost:8000.
// Override any field: clientFromConfig({ url, token, configPath, timeout }).
const client = clientFromConfig();
実行 — npx tsx rca.mts を実行します。.mts 拡張子を使ってください。これは常に ES モジュールとして扱われるため、ホストプロジェクトの package.json の "type" に依存せず、トップレベルの await が動作します。(tsx がない場合:node --experimental-strip-types rca.mts。)
kq でクエリを作成し、client.query(q) で実行します。 client.query(q) は、作成したあらゆるクエリを実行するシンプルな方法で、型付けが完全に行われています。行の入力補完が利用でき、タイプミスはコンパイルエラーになります。集計クエリは { data } を返し、生クエリは { data, nextCursor } を返します。結果の行は完全に型付けされているため、any/any[] にキャストせず、data を直接反復処理してください。 集計値は number 型なので(row.error_rate * 100 がそのまま動作し、String(...) やキャストは不要です)、グループ化されたディメンションも利用でき(row["service.name"])、.timeSeries() の行には bucket_start が追加されます:
const { data } = await client.query(q); // typed rows — do NOT write `data as any[]`
for (const row of data) {
console.log(
row["service.name"],
`${(row.error_rate * 100).toFixed(1)}%`,
row.rps
);
}
出力 — 標準出力が結果です。最後に console.log(JSON.stringify(data, null, 2)) を出力し、.build() とクエリの呼び出しを try/catch で囲んでください:
import { kq, KopaiQueryBuildError } from "@kopai/sdk";
try {
const q = kq.traces
.aggregate() /* … */
.build();
const { data } = await client.query(q);
console.log(JSON.stringify(data, null, 2));
} catch (e) {
if (e instanceof KopaiQueryBuildError)
console.error(e.issues); // {path,message}[]
else throw e;
}
バックエンドの注意点と落とし穴(クエリを実行する前にお読みください)
- すべてのクエリに時間範囲が必要です —
.timeRelative("1h")または.timeAbsolute(startISO, endISO)を指定します。ルックバック期間/粒度は^[1-9]\d*[smhdw]$("30s"、"15m"、"2h"、"7d")に一致します。 StatusCodeは正確に"Unset" | "Ok" | "Error"です(タイトルケース)。成功したスパンは通常"Ok"ではなく"Unset"です。この列挙型は型チェックされるため、"ERROR"のような誤った値はコンパイルエラーになり、大文字・小文字の間違いは自動的に検出されます。いずれにしても、値を直接フィルタリングするより、サーバー側でエラー数を数えるerrorRateメジャーを優先してください。ServiceNameではなく、service.name(ドット区切り)を使用してください。ServiceNameはクエリ可能な列ではないため、これでフィルタリングしても何も一致しません(エラーも発生しません)。結果行では、グループ化したキー名で値が返されます。例:row["service.name"]。- 負荷の高いシステムで結果が空の場合、実際にデータがないのではなく、列名または値が間違っている可能性があります。 「エラーなし/データなし」と判断する前に、列名と値の大文字・小文字を再確認してください。空の結果がエラーなく返ることが、誤って「すべて正常」と判断する最大の原因です。
- パーセンタイル(
p50~p999)は ClickHouse 専用です。 SQLite ではクエリ実行時に失敗します(KopaiError: Percentile measures … not yet supported on the sqlite backend)。まずはDurationのavg/maxでレイテンシーを確認し、パーセンタイルは ClickHouse へのアップグレードとしてtry/catchで扱ってください。 Durationフィルターでは、単位s/m/h/d/wの期間文字列を指定できます —f.gt("Duration", "1s")、f.lte("Duration", "2h")(gte/ltも同様)。1 秒未満の単位は使えません。1 秒未満のしきい値には、ナノ秒単位の数値を渡してください。(結果行のDurationは引き続きナノ秒で返されます。Durationのavg/maxも ns 単位で返されます。1ms = 1e6、1s = 1e9。)- メトリクスクエリでは、最初に型を選択します。
kq.metrics("Gauge")…、kq.metrics("Sum")…(型 ∈"Gauge" | "Sum" | "Histogram" | "ExponentialHistogram" | "Summary")。ビルダーの引数によってMetricTypeが自動的に固定されるため、.where(f => f.eq("MetricType", …))を手動で指定する必要はありません。値の列は型ごとに異なります。Gauge/Sum →"Value"、Histogram/ExponentialHistogram →"Count" | "Sum" | "Min" | "Max"、Summary →"Count" | "Sum"。 searchTraces/searchLogs/searchMetricsは非同期イテレーターです(自動ページネーション)。for await (const row of client.searchLogs({ … })) { … }で読み取ってください。配列としてawaitしてはいけません。単一ページを取得するにはsearchTracesPage/searchLogsPage/searchMetricsPageを使います。結果は{ data, nextCursor }です。上限:kqの.limit()は 10000 件、search*のフィルターは 1000 件までです。
RCA ワークフロー
-
失敗している処理を見つける — エラー率とスループットでサービスを順位付けします。
errorRateはサーバー側でStatusCodeを処理するため、値を推測する必要はありません:const q = kq.traces .aggregate() .measure((m) => m.errorRate("error_rate")) .measure((m) => m.throughput("rps")) .measure((m) => m.count("spans")) .dimension("service.name") .timeRelative("1h") .summary() .orderByMeasure("error_rate", "desc") .build(); const { data } = await client.query(q);ログを起点にトリアージする場合は、
SeverityNumber >= 17でエラーレベルのログを取得します(テキストの大文字・小文字に関係なく ERROR/FATAL を検出します):client.query(kq.logs.raw().where(f => f.gte("SeverityNumber", 17)).timeRelative("1h").limit(50).build())。workflow-find-errorsを参照してください。 -
トレースの完全なコンテキストを取得する —
const spans = await client.getTrace(traceId)。Duration(ボトルネック)、ParentSpanId(呼び出しチェーン)、StatusMessage、SpanKindを調べます。workflow-get-contextを参照してください。 -
ログをトレースに関連付ける —
client.query(kq.logs.raw().where(f => f.eq("TraceId", traceId)).timeRelative("1h").build())(またはclient.searchLogs({ traceId })を反復します)。最も早いエラーとその スタックトレースを探します。workflow-correlate-logsを参照してください。 -
影響を定量化し、発生時点を特定する — CLI ではできないアップグレードです。メトリクスを時間でバケット化し、いつ始まったのか、影響範囲がどれほど広いのかを確認します。保持期間の下限に注意してください。最も早いバケットですでに値が上昇している場合、実際の発生時点はその時点またはそれ以前です。下限を「段階的な変化」と呼ばず、その旨を伝えてください:
const q = kq.traces .aggregate() .measure((m) => m.errorRate("error_rate")) .measure((m) => m.avg("Duration", "avg_ns")) .measure((m) => m.max("Duration", "max_ns")) .dimension("service.name") .timeRelative("3h") .timeSeries("5m") .orderByMeasure("error_rate", "desc") .build(); const { data } = await client.query(q);信号がトレースではなくメトリクスの場合は、最初に種類を選択して
MetricTypeを自動設定します。たとえば、Gauge を時間でバケット化します:const q = kq .metrics("Gauge") .aggregate() .measure((m) => m.avg("Value", "avg_value")) .where((f) => f.eq("MetricName", "process.cpu.utilization")) .timeRelative("3h") .timeSeries("5m") .build(); const { data } = await client.query(q);workflow-check-metricsを参照してください。 -
調査結果を提示する — 証拠(具体的な TraceId、ログエントリー、メトリクスの差分)を添えた根本原因、影響範囲、推奨される修正を示します。インシデントダッシュボードの作成を提案してください(
create-dashboardスキルを参照)。workflow-identify-causeを参照してください。
簡単な例
// rca-triage.mts — run: npx tsx rca-triage.mts
import { kq, KopaiQueryBuildError } from "@kopai/sdk";
import { clientFromConfig } from "@kopai/sdk/node";
const client = clientFromConfig();
try {
const q = kq.traces
.aggregate()
.measure((m) => m.errorRate("error_rate"))
.measure((m) => m.throughput("rps"))
.measure((m) => m.avg("Duration", "avg_ns"))
.measure((m) => m.max("Duration", "max_ns"))
.measure((m) => m.count("spans"))
.dimension("service.name")
.timeRelative("1h")
.summary()
.orderByMeasure("error_rate", "desc")
.build();
const { data } = await client.query(q); // typed rows
console.log(JSON.stringify(data, null, 2));
} catch (e) {
if (e instanceof KopaiQueryBuildError) console.error(e.issues);
else throw e;
}
ルール
1. ワークフロー(最重要)
workflow-find-errors- エラートレースとエラーレベルのログを見つけるworkflow-get-context- トレースの完全なコンテキストを取得するworkflow-correlate-logs- ログをトレースに関連付けるworkflow-check-metrics- 集計クエリ/時系列クエリで影響を定量化するworkflow-identify-cause- 根本原因を特定し、調査結果を提示する
2. パターン(高優先度)
pattern-http-errors- HTTP エラーのデバッグpattern-slow-requests- 遅いリクエストの分析pattern-distributed- 分散障害のトレースpattern-log-driven- ログを起点とする調査
詳細については rules/<rule-name>.md をお読みください。
ヒント
- 集計値(サービスごとのエラー率/レイテンシ)から始めて、1 つのトレースを詳しく調べます。
StatusCodeでフィルターするよりも、errorRateメジャーを優先してください(サーバー側でカウントされます)。- 構築したクエリは
client.query(q)で実行します。完全に型付けされており(行の補完が利用でき、タイプミスはコンパイルエラーになります)。 Durationは文字列でフィルターします(f.gt("Duration", "1s"))。結果の行では、引き続きDurationがナノ秒で報告されます。トレースチェーンで最も早いエラーが、通常は根本原因に最も近いものです。.timeSeries(granularity)を使用して、回帰が いつ 始まったかを特定します。保持期間の下限を発生時点と取り違えないでください。- メトリクスでは、最初に種類を選択します —
kq.metrics("Gauge")…(MetricTypeを自動設定し、値の列を型付けします)。 - SQLite では、レイテンシは
Durationのavg/maxです。ClickHouse ではp95/p99に切り替えてください。
CLI による代替手段(単発のクエリ向け)
CLI は単一の検索には適していますが、クエリ/集計コマンドはありません。
StatusCode の値はリテラルの "Error" であることに注意してください。
npx @kopai/cli traces search --status-code Error --limit 20 --json
npx @kopai/cli traces get <traceId> --json
npx @kopai/cli logs search --trace-id <traceId> --severity-min 17 --json
npx @kopai/cli metrics discover --json
参考資料
- トレースフィルター - トレース列、フィルター/メジャーの操作、コンテナー
- ログフィルター - ログ列、重大度モデル、フィルター操作
- メトリクスフィルター - メトリクス列、MetricType の設定、集計