# TiKV Hotspot SQL 巡检设计

## 目标

将旧运维层面的 TiKV 热点 SQL 巡检迁移到 `health_check/sql_health_check`，作为正式 SQL Health Check 功能提供。

目标不是构建复杂的归因评分系统，而是基于 TiKV 节点异常窗口，在同一节点、同一时间窗口内查找 TopSQL 中占主要比重的 SQL Digest，并输出足够清楚的统计、证据和链接，方便人工复核。

本功能拆成两个独立巡检项：

- `sql_tikv_high_duration`：从 TiKV Coprocessor gRPC p99 duration 异常出发，定位同窗口内 TopSQL 主要 SQL Digest。
- `sql_tikv_high_cpu`：从 TiKV CPU usage 绝对高或相对离群出发，定位同窗口内 TopSQL 主要 SQL Digest。

两个巡检项都以 SQL 为诊断对象，因此名称必须包含 `sql`。结果直接写入 S3，与其他 SQL Health Check 巡检项保持一致，不引入中间 DB。

## 核心原则

- 先找 TiKV 节点异常，再查该节点该时间窗口内 TopSQL，不从全局 slow log 或全局 TopSQL 反推 TiKV 问题。
- TopSQL 是归因主路径；slow log 只是补充统计和样本，不作为必须命中条件。
- 不再区分 `SQL Likely` / `SQL Possible`，避免给出看似精确但实际不可靠的置信度。
- 不做复杂反证评估，不默认查询 RocksDB、raft、disk 等扩展指标。
- 控制误报优先：TopSQL CPU 为 0、没有进入 TopSQL、或 TopSQL 内部占比太小的 digest 不输出。
- 控制耗时：时间范围、metric step、异常窗口数量、TopSQL TopN、slow log 查询都必须有上限。

## 总体流程

两个巡检项共用同一条归因链路：

1. 查询巡检项对应的 TiKV metrics。
2. 按阈值识别异常点。
3. 按 TiKV instance 合并异常点为异常窗口。
4. 对每个异常窗口扩展 attribution window。
5. 查询该 TiKV instance 在 attribution window 内的 TopSQL TopN 和 TopSQL total CPU。
6. 基于 TopSQL 内部 CPU 占比和主导比例过滤 SQL Digest。
7. 对保留的 SQL Digest 批量查询 slow log 统计和 sample。
8. 输出异常窗口、候选 SQL、指标链接、TopSQL 链接、slow log 链接和严重级别。

## 数据源

### Metrics 与 TopSQL

统一通过 Clinic Data Proxy metrics API 请求 PromQL：

```text
GET/POST /data-proxy/metrics/{clusterId}/api/v1/query
GET/POST /data-proxy/metrics/{clusterId}/api/v1/query_range
```

调用方只传 PromQL，不直接访问 O11Y Prometheus endpoint。后端需要按集群类型区分 metrics scope：

- Dedicated：TiKV metrics、TopSQL、slow log 都使用请求中的 `clusterId`。
- Premium / BYOC / nextgen shared scope：TiKV duration 和 CPU metrics 使用 `physicalClusterId`；TopSQL 和 slow log 仍使用用户集群 `clusterId`。

因此本巡检内部会维护两个 metrics client：

- core metrics client：用于 TiKV duration / CPU 异常窗口检测，cluster id 为 `clusterId` 或 `physicalClusterId`。
- TopSQL metrics client：用于 TopSQL attribution，cluster id 始终为用户集群 `clusterId`。

PromQL 中显式注入 `tidb_cluster_id="$metrics_cluster_id"`，避免 shared-scope metrics endpoint 混入其他集群或查不到 TiKV 指标。

### Slow Log

通过现有 SQL data proxy 查询：

```text
POST /data-proxy/query
```

查询表：

```text
clinic_data_proxy.slow_query_logs
```

slow log 只用于 enrichment，输出：

- slow log count，沿用当前 SQL Health Check 的 `COUNT(*)` 口径。
- max/avg query_time。
- max/avg mem_max。
- max process_keys、total_keys、request_count。
- max cop_proc、cop_wait。
- sample conn_id/time/db/query。
- slow query list/detail 链接。

slow log 不参与候选 SQL 的必要过滤。也就是说，TopSQL 中明确占主要比重的 SQL 即使没有 slow log，也应该输出。

## 请求参数

通用请求字段沿用现有 SQL Health Check：

- `clusterId`：必填，目标集群 ID。
- `physicalClusterId`：Premium / BYOC / nextgen shared scope 必填，用于查询 TiKV core metrics 和拼接 sharedscope Grafana 链接。
- `reportId`：选填，不传则生成 UUID。
- `deployType`：建议传入，用于区分 Dedicated 与 Premium / BYOC / nextgen shared scope。
- `clinicOrgId`：建议传入，Grafana `var-org` 必须使用 Clinic 内部 org id。
- `timeRange.start`：必填，ISO 8601 时间。
- `timeRange.end`：必填，ISO 8601 时间。
- `params`：选填，巡检项参数，支持 JSON object 或 JSON string。
- `options.dataProxyTimeoutSeconds`：选填，slow log data proxy 查询超时。
- `options.metricsDataProxyTimeoutSeconds`：选填，metrics data proxy 查询超时。
- `clusterMeta`：选填，生成 Clinic 链接所需元信息。

`clusterMeta` 建议字段：

- `provider`
- `region`
- `orgId`
- `orgName`
- `projectId`
- `clusterName`
- `deployType`
- `version`
- `dashboardVersion`

Clinic taskmanager 调用时应传入上述元信息。若调试请求缺失部分字段，后端只做默认值降级，不额外反查外部接口，避免巡检链路引入新的慢依赖。

## 参数设计

参数分为“用户可见参数”和“内部参数”。用户可见参数必须保持少量、可解释；内部参数只用于后端默认值、兼容旧配置或紧急排障，不应在 UI 和报告页默认展示。

### 用户可见参数

通用用户参数：

| 参数 | 默认值 | 说明 |
| --- | --- | --- |
| `window_min_duration_seconds` | `180` | 异常窗口最小持续时间；调低可巡检更短尖刺 |
| `critical_window_duration_seconds` | `600` | 异常持续时间达到该值时定级为 Critical |
| `topsql_cpu_percent_threshold` | `10.0` | digest 在 TopSQL total CPU 中的最低占比，小于该值忽略 |
| `topsql_dominance_ratio_threshold` | `2.0` | 主导 SQL 判断阈值；若当前候选 CPU 是下一名的 N 倍，则忽略下一名及之后候选 |

`sql_tikv_high_duration` 用户参数：

| 参数 | 默认值 | 说明 |
| --- | --- | --- |
| `duration_threshold_ms` | `1000` | TiKV Coprocessor gRPC p99 duration 异常阈值 |
| `critical_duration_threshold_ms` | `4000` | Critical duration 阈值 |

`sql_tikv_high_cpu` 用户参数：

| 参数 | 默认值 | 说明 |
| --- | --- | --- |
| `absolute_cpu_threshold_percent` | `75.0` | 绝对高 CPU 阈值 |
| `relative_cpu_delta_threshold_percent` | `10.0` | 相对集群 P50 的离群阈值 |
| `critical_cpu_threshold_percent` | `85.0` | Critical CPU 阈值 |

### 内部参数

以下参数后端仍支持，但不进入默认 UI，也不进入报告 `params` 展示：

| 参数 | 默认值 | 用途 |
| --- | --- | --- |
| `metric_step_seconds` | `120` | Prometheus range query step，不允许低于 `60` |
| `metric_rate_window` | `"2m"` | PromQL rate window |
| `window_merge_gap_seconds` | `180` | 相邻异常点间隔不超过该值时合并 |
| `window_expand_before_seconds` | `120` | 单点异常窗口向前扩展 |
| `window_expand_after_seconds` | `120` | 单点异常窗口向后扩展 |
| `attribution_window_padding_seconds` | `120` | TopSQL 和 slow log 查询窗口 padding |
| `topsql_top_n` | `5` | 每个异常窗口查询 TopSQL digest 数量 |
| `topsql_top_n_max` | `20` | TopSQL TopN 上限，防止查询和报告过重 |
| `max_anomaly_windows` | `20` | 单次巡检最多归因的异常窗口数 |
| `max_time_range_hours` | `24` | 最大巡检时间范围 |
| `include_links` | `true` | 是否输出 Clinic/Grafana 链接 |
| `include_promql` | `false` | 是否在结果中输出 PromQL |
| `tikv_instance_regex` | Dedicated: `"db-tikv-.*"`；Premium / BYOC / nextgen: `".*-tikv-.*"` | TiKV instance 过滤 |
| `grpc_type` | `"coprocessor"` | Duration 巡检使用的 gRPC message type |
| `exclude_grpc_type_regex` | `"kv_gc"` | Duration 巡检默认排除 kv_gc |
| `slow_log_match_policy` | `"annotate"` | slow log 仅做 enrichment；可设 `none` 跳过 |
| `min_cpu_for_relative_outlier_percent` | `30.0` | 低负载相对离群保护阈值 |

## TopSQL 候选过滤逻辑

TopSQL 查询返回 TopN SQL Digest，并通过 TopSQL total CPU 计算 TopSQL 内部占比。TopSQL 页面通常由 TopN digest 加 Others 组成，本巡检只输出具体 digest，不输出 Others 作为 SQL candidate。

对每个异常窗口：

1. 查询 TopSQL TopN digest CPU，单位统一为 seconds。
2. 查询 TopSQL total CPU，单位统一为 seconds。
3. 对每个 digest 计算：

```text
digest_topsql_cpu_share_percent = digest_cpu_seconds / topsql_total_cpu_seconds * 100
```

4. 丢弃以下 digest：

```text
digest_cpu_seconds <= 0
OR digest_topsql_cpu_share_percent < topsql_cpu_percent_threshold
```

5. 将剩余 digest 按 `digest_cpu_seconds` 从高到低排序。
6. 按主导比例截断候选列表。

主导比例截断规则：

```text
kept = []
for i in sorted_candidates:
    keep current candidate
    if current candidate has no next candidate:
        stop
    if current_cpu >= next_cpu * topsql_dominance_ratio_threshold:
        stop; ignore next and all later candidates
```

例子：

- Top1 60%，Top2 25%，Top3 8%，阈值 2.0：Top1 是 Top2 的 2.4 倍，只保留 Top1。
- Top1 45%，Top2 30%，Top3 12%，阈值 2.0：Top1 未达到 Top2 的 2 倍，保留 Top1、Top2；Top2 是 Top3 的 2.5 倍，忽略 Top3 及之后。
- Top1 35%，Top2 25%，Top3 20%，Top4 5%，阈值 2.0：前三名都没有形成 2 倍主导关系，且 Top4 低于 10% 被过滤，保留 Top1、Top2、Top3。

如果没有 digest 通过过滤，该窗口仍输出 TiKV 异常，但 `decision=not_attributed`。

## High-Duration-TiKV 逻辑

巡检项名称：`sql_tikv_high_duration`

### 1. 查询 TiKV duration

默认 PromQL：

```promql
1000 * histogram_quantile(
  0.99,
  sum(rate(tikv_grpc_msg_duration_seconds_bucket{
    tidb_cluster_id="$metrics_cluster_id",
    component="tikv",
    instance=~"$tikv_instance_regex",
    type="coprocessor",
    type!~"kv_gc"
  }[2m])) by (le, instance)
)
```

说明：

- Dedicated 的 `$metrics_cluster_id` 为 `clusterId`；Premium / BYOC / nextgen shared scope 为 `physicalClusterId`。
- Dedicated 默认 `$tikv_instance_regex` 为 `db-tikv-.*`；Premium / BYOC / nextgen 默认 `.*-tikv-.*`。
- 查询结果按 `instance` 得到时间序列。
- 命中条件默认是 `value > duration_threshold_ms`。

### 2. 合并异常窗口

- 每个异常点先按原始 timestamp 判断是否属于同一异常段。
- 相邻异常点间隔小于等于 `window_merge_gap_seconds` 时合并；合并判断不受 `window_expand_before_seconds` / `window_expand_after_seconds` 影响。
- 合并后的异常段再按参数扩展成展示/归因窗口。
- 合并后实际异常持续时间小于 `window_min_duration_seconds` 的窗口默认丢弃。
- 每个窗口保留 `max_duration_ms`、`avg_duration_ms`、`sample_count`、`start`、`end`、`window_duration_seconds`、`anomaly_duration_seconds`、`instance`。
- 若窗口数超过 `max_anomaly_windows`，按 `max_duration_ms` 和持续时间排序截断，并在 `data_quality.truncated=true` 中说明。

### 3. 查询 TopSQL 和 slow log

- 在异常窗口基础上增加 `attribution_window_padding_seconds`，得到 attribution window。
- 查询该 TiKV instance 在 attribution window 内的 TopSQL TopN 和 TopSQL total CPU。
- 用统一的 TopSQL 候选过滤逻辑得到 SQL candidates。
- 对 SQL candidates 批量查询 slow log enrichment。

### 4. 分级

- `Critical`：实际异常持续时间 `anomaly_duration_seconds` 大于等于 `critical_window_duration_seconds`，或 `max_duration_ms >= critical_duration_threshold_ms`。
- `Major`：其他命中的异常窗口。

## High-CPU-TiKV 逻辑

巡检项名称：`sql_tikv_high_cpu`

### 1. 查询 TiKV CPU util

默认 PromQL：

```promql
(
  sum(rate(process_cpu_seconds_total{
    tidb_cluster_id="$metrics_cluster_id",
    component="tikv",
    instance=~"$tikv_instance_regex"
  }[2m])) by (instance)
  /
  (sum(tikv_server_cpu_cores_quota{
    tidb_cluster_id="$metrics_cluster_id",
    component=~".*tikv",
    instance=~"$tikv_instance_regex"
  }) by (instance) > 0)
) * 100
```

说明：

- 查询结果是每个 TiKV instance 在每个时间点的 CPU 使用率百分比。
- `$metrics_cluster_id` 和 `$tikv_instance_regex` 的选择规则与 Duration 巡检项一致。

### 2. 计算 P50 和 relative delta

对每个 timestamp：

```text
util_p50(t) = percentile_50([util_i(t)])
relative_delta_i(t) = util_i(t) - util_p50(t)
```

命中条件默认是：

```text
util_i(t) > absolute_cpu_threshold_percent
OR
(
  util_i(t) > min_cpu_for_relative_outlier_percent
  AND relative_delta_i(t) > relative_cpu_delta_threshold_percent
)
```

`min_cpu_for_relative_outlier_percent` 用于避免整体低负载时，因为节点间很小的相对差异触发误报。

### 3. 合并异常窗口

- 对每个 instance 分别合并异常点。
- 相邻异常点间隔小于等于 `window_merge_gap_seconds` 时合并，合并判断基于原始异常点间隔，不基于扩展后的展示窗口。
- 合并后实际异常持续时间小于 `window_min_duration_seconds` 的窗口默认丢弃。
- 每个窗口保留 `max_cpu_percent`、`avg_cpu_percent`、`p50_cluster_cpu_percent`、`max_relative_delta_percent`、`rule_tag`、`start`、`end`、`window_duration_seconds`、`anomaly_duration_seconds`、`instance`。
- 若窗口数超过 `max_anomaly_windows`，按 `max_cpu_percent`、`max_relative_delta_percent` 和持续时间排序截断。

### 4. 查询 TopSQL 和 slow log

- 在异常窗口基础上增加 `attribution_window_padding_seconds`，得到 attribution window。
- 查询该 TiKV instance 在 attribution window 内的 TopSQL TopN 和 TopSQL total CPU。
- 用统一的 TopSQL 候选过滤逻辑得到 SQL candidates。
- 对 SQL candidates 批量查询 slow log enrichment。

### 5. 分级

- `Critical`：实际异常持续时间 `anomaly_duration_seconds` 大于等于 `critical_window_duration_seconds`，或 `max_cpu_percent >= critical_cpu_threshold_percent`。
- `Major`：其他命中的异常窗口。

## 决策结果

每个异常窗口只输出简单决策：

| decision | 含义 |
| --- | --- |
| `sql_candidate` | 该 TiKV 异常窗口内存在至少一个通过 TopSQL 占比和主导比例过滤的 SQL Digest |
| `not_attributed` | TiKV 异常窗口成立，但没有 SQL Digest 通过 TopSQL 过滤 |
| `unknown` | TiKV 异常窗口成立，但 TopSQL 查询失败或关键数据不可用 |

不输出 `sql_likely`、`sql_possible`、`non_sql_likely`。这些词会让用户误以为系统做了强因果判断，但实际数据只能证明同节点同窗口 TopSQL 高相关。

## 报告呈现视角

报告需要同时支持两个视角：

- `SQL Digest Summary`：面向用户快速判断“最值得关注的 SQL Digest 是谁，以及它影响了多少 TiKV 节点和异常窗口”。
- `Anomaly Windows`：面向复核，逐窗口展示“哪个 TiKV 节点在什么时间异常、指标打到多少、当时有哪些 TopSQL candidate 和链接”。

主视角仍然以 TiKV 异常窗口为事实基础，因为巡检是从 TiKV metrics 异常出发的。但用户最终排查和治理通常按 SQL Digest 行动，因此报告顶部需要提供按 digest 聚合后的摘要。

前端推荐呈现顺序：

1. Summary cards：总异常窗口数、SQL candidate 窗口数、未归因窗口数、候选 digest 数。
2. Top SQL Digests：按 digest 聚合的摘要表，默认展开在窗口明细之前。
3. Anomaly Windows：逐 TiKV instance + window 的明细表，作为证据链和复核入口。

交互建议：

- 点击 `Top SQL Digests` 中某个 digest，可以过滤或展开下方 `Anomaly Windows`，只看该 digest 关联的异常窗口。
- Digest 聚合表中的代表链接使用影响最大的窗口，即该 digest 在该窗口 TopSQL CPU time 最大，或窗口 severity 最高。
- 窗口明细中的链接仍然保留每个窗口自己的 Grafana、TopSQL、slow log list/detail 链接。

## 输出结构

两个巡检项统一按异常窗口组织结果。

```json
{
  "name": "sql_tikv_high_cpu",
  "description": "Detects prominent TopSQL digests during high or outlier TiKV CPU windows",
  "status": "success",
  "params": {
    "absolute_cpu_threshold_percent": 75,
    "relative_cpu_delta_threshold_percent": 10,
    "critical_cpu_threshold_percent": 85,
    "window_min_duration_seconds": 180,
    "critical_window_duration_seconds": 600,
    "topsql_cpu_percent_threshold": 10,
    "topsql_dominance_ratio_threshold": 2
  },
  "summary": {
    "node_anomaly_windows": 3,
    "sql_candidate_windows": 2,
    "not_attributed_windows": 1,
    "unknown_windows": 0,
    "sql_candidates": 3,
    "distinct_sql_digests": 2,
    "data_quality": {
      "core_metrics_available": true,
      "topsql_available": true,
      "slow_log_available": true,
      "truncated": false,
      "truncated_windows": 0
    }
  },
  "digest_summary": [
    {
      "digest": "abc",
      "affected_windows": 2,
      "affected_instances": 2,
      "instances": ["db-tikv-1", "db-tikv-3"],
      "max_severity": "Critical",
      "total_topsql_cpu_seconds": 380.4,
      "avg_topsql_share_percent": 54.2,
      "max_topsql_share_percent": 59.0,
      "max_duration_ms": null,
      "max_cpu_percent": 91.2,
      "slow_log_count": 18,
      "first_seen": 1772467200,
      "last_seen": 1772468400,
      "representative_window_id": "db-tikv-1_1772467200_1772467800",
      "representative_links": {
        "top_sql": "https://clinic.pingcap.com/...#/topsql?...",
        "slow_query_list": "https://clinic.pingcap.com/...#/slow_query?...",
        "slow_query_detail": "https://clinic.pingcap.com/...#/slow_query/detail?..."
      }
    }
  ],
  "results": [
    {
      "window_id": "db-tikv-1_1772467200_1772467800",
      "decision": "sql_candidate",
      "node_severity": "Critical",
      "node_anomaly": {
        "type": "high_cpu",
        "cluster_id": "123",
        "instance": "db-tikv-1",
        "window_start": 1772467200,
        "window_end": 1772467800,
        "window_duration_seconds": 600,
        "anomaly_duration_seconds": 600,
        "max_cpu_percent": 91.2,
        "avg_cpu_percent": 83.5,
        "p50_cluster_cpu_percent": 52.1,
        "max_relative_delta_percent": 39.1,
        "rule_tag": "abs_high,rel_outlier",
        "links": {
          "metrics": "https://clinic.pingcap.com/grafana/..."
        }
      },
      "topsql_summary": {
        "topsql_total_cpu_seconds": 390.2,
        "topn_topsql_cpu_seconds": 345.3,
        "topn_topsql_share_percent": 88.5,
        "others_topsql_cpu_seconds": 44.9,
        "attribution_window_start": 1772467080,
        "attribution_window_end": 1772467920
      },
      "sql_candidates": [
        {
          "digest": "abc",
          "rank": 1,
          "cpu_time_seconds": 230.1,
          "digest_topsql_cpu_share_percent": 59.0,
          "matched_topsql_cpu_percent_threshold": true,
          "dominance_cutoff": true,
          "is_slow_query": true,
          "slow_log": {
            "count": 12,
            "max_query_time_s": 3.2,
            "avg_query_time_s": 1.4,
            "max_mem_bytes": 1073741824,
            "max_process_keys": 12000000,
            "max_total_keys": 18000000,
            "max_request_count": 120,
            "cop_proc_max_s": 2.4,
            "cop_wait_max_s": 0.3,
            "sample_conn_id": "123456",
            "sample_time": "1772467512.123456",
            "sample_query": "select ..."
          },
          "links": {
            "top_sql": "https://clinic.pingcap.com/...#/topsql?...",
            "slow_query_list": "https://clinic.pingcap.com/...#/slow_query?...",
            "slow_query_detail": "https://clinic.pingcap.com/...#/slow_query/detail?..."
          }
        }
      ],
      "links": {
        "top_sql": "https://clinic.pingcap.com/...#/topsql?..."
      }
    }
  ]
}
```

## SQL Digest Summary 聚合规则

`digest_summary` 从所有 `results[].sql_candidates[]` 聚合得到，不额外查询数据。

每个 digest 输出：

- `affected_windows`：该 digest 出现在多少个异常窗口中。
- `affected_instances`：该 digest 关联了多少个不同 TiKV instance。
- `instances`：涉及的 TiKV instance 短名列表。
- `max_severity`：关联窗口中的最高 severity，`Critical` 高于 `Major`。
- `total_topsql_cpu_seconds`：该 digest 在所有关联窗口里的 TopSQL CPU time 总和。
- `avg_topsql_share_percent`：该 digest 在关联窗口中的平均 TopSQL 占比。
- `max_topsql_share_percent`：该 digest 在关联窗口中的最大 TopSQL 占比。
- `max_duration_ms`：仅 `sql_tikv_high_duration` 输出，关联窗口中的最大 duration。
- `max_cpu_percent`：仅 `sql_tikv_high_cpu` 输出，关联窗口中的最大 CPU usage。
- `slow_log_count`：关联窗口中 slow log count 的总和；slow log 查询失败或未命中时为 `0`。
- `first_seen` / `last_seen`：该 digest 关联异常窗口的最早开始和最晚结束时间。
- `representative_window_id`：代表窗口 ID，用于跳转或展开窗口明细。
- `representative_links`：代表窗口中的 TopSQL、slow query list/detail 链接。

排序规则：

- 默认按 `affected_windows desc` 排序。
- 再按 `max_severity` 排序，`Critical` 优先。
- 再按 `total_topsql_cpu_seconds desc` 排序。
- 对 `sql_tikv_high_duration`，再按 `max_duration_ms desc` 辅助排序。
- 对 `sql_tikv_high_cpu`，再按 `max_cpu_percent desc` 辅助排序。

这样可以回答两个用户问题：

- “哪个 SQL Digest 影响面最大？”看 `affected_windows` 和 `affected_instances`。
- “哪个 SQL Digest 当时最重？”看 `total_topsql_cpu_seconds`、`max_topsql_share_percent`、`max_duration_ms` 或 `max_cpu_percent`。

## 链接规则

链接拼接需要与现有 Clinic 页面和 Key Metrics 巡检项保持一致。

### TiKV Duration Metrics Grafana link

`sql_tikv_high_duration` 的 TiKV Instance 链接跳转到 TiKV duration metrics 面板。

参考 Key Metrics 巡检项中的 `P99TiKVLatency_coprocessor` 链接拼接规则，面板语义相同，但在本巡检中使用巡检整体时间范围和 `var-instance=All`，不指定单个 TiKV instance。

### TiKV CPU Usage Grafana link

`sql_tikv_high_cpu` 的 TiKV Instance 链接跳转到 TiKV CPU Usage 面板。

参考 Key Metrics 巡检项中的 `TiKV_CPU` 链接拼接规则，面板语义相同，但在本巡检中使用巡检整体时间范围和 `var-instance=All`，不指定单个 TiKV instance。

Grafana 链接注意事项：

- `from` / `to` 使用本次巡检整体时间范围，而不是单个异常窗口范围。
- `var-instance=All`，不指定具体 TiKV instance。
- `var-tidb_version` 使用 dashboard 匹配版本，例如 `v8.5.x`，不要使用精确版本 `v8.5.4`。
- `var-org` 使用 Clinic 内部 org id，即 taskmanager 请求中的 `clinicOrgId`，不要使用 TiDB Cloud tenant id。
- Dedicated 使用 cluster-level dashboard，`var-tidb_cluster={clusterId}`。
- Premium / BYOC / nextgen shared scope 使用 sharedscope dashboard，`var-tidb_cluster={physicalClusterId}`。

Premium / BYOC / nextgen shared scope 的 TiKV 链接规则：

- Duration：`/grafana/d/e53523ea9ee7fcf4nextgenv{version}/sharedscope-tikv-details-cluster-clinic?...&viewPanel=55`
- CPU：`/grafana/d/e53523ea9ee7fcf4nextgenv{version}/sharedscope-tikv-details-cluster-clinic?...&viewPanel=8`

### TopSQL link

```text
{clinic_base_url}?provider={provider}&region={region}&orgName={orgName}&orgId={orgId}&projectId={projectId}&clusterId={clusterId}&clusterName={clusterName}&deployType={deployType}#/topsql?from={from}&to={to}&instance={instance}
```

`projectId` 必须是 TiDB Cloud project id 的数字字符串。若调用方传入非数字占位值，后端不应输出该参数，避免 NGM 页面请求 `/topsql/instances` 时 `strconv.ParseUint` 失败。

`instance` 需要使用 TopSQL 页面接受的完整 TiKV 地址格式。若 Prometheus 返回短名，例如 `db-tikv-1`，可拼成：

```text
db-tikv-1.db-tikv-peer.tidb{clusterId}.svc:20160
```

URL 中需要 encode 为：

```text
db-tikv-1.db-tikv-peer.tidb{clusterId}.svc%3A20160
```

Premium / BYOC / nextgen shared scope 的 TopSQL instance 使用 metrics 返回的原始短名，例如 `db-62fb4e73-tikv-2fb9os`，不要拼接 Dedicated 的 `db-tikv-peer.tidb{clusterId}.svc:20160`。

### Slow query list link

```text
{clinic_base_url}?provider={provider}&region={region}&orgName={orgName}&orgId={orgId}&projectId={projectId}&clusterId={clusterId}&clusterName={clusterName}&deployType={deployType}#/slow_query?show_internal=true&digest={digest}&from={from}&row=0&to={to}
```

只有 slow log enrichment 查询成功时输出。

### Slow query detail link

```text
{clinic_base_url}?provider={provider}&region={region}&orgName={orgName}&orgId={orgId}&projectId={projectId}&clusterId={clusterId}&clusterName={clusterName}&deployType={deployType}#/slow_query/detail?digest={digest}&connection_id={connId}&timestamp={timestamp}
```

只有 slow log sample 存在 `conn_id` 和 `time` 时输出。

## 性能边界

默认查询量按异常窗口数线性增长，必须控制窗口数量。

- High-CPU detection：1 次 range query。
- High-Duration detection：1 次 range query。
- 每个异常窗口：1 次 TopSQL topN query。
- 每个异常窗口：1 次 TopSQL total CPU query。
- 每个异常窗口：1 次 slow log enrichment SQL，批量查询该窗口所有保留 digest。
- cluster metadata：最多 1 次 HTTP 查询，本次巡检内缓存。

硬限制：

- `max_anomaly_windows` 默认 `20`。
- `topsql_top_n` 默认 `5`，最大 `20`。
- `max_time_range_hours` 默认 `24`。
- `metric_step_seconds` 默认 `120`，不允许低于 `60`。
- slow log enrichment 必须批量查询，禁止按 digest 单独查询。
- 如果超过窗口上限，按异常强度截断，并在结果中显式标记 `data_quality.truncated=true` 和 `data_quality.truncated_windows`。

## 已验证事实

以集群 `10989049060142230334`、窗口 `2026-04-27T02:55:00Z` 到 `2026-04-27T03:25:00Z` 验证过核心链路：

- TiKV duration metrics 可通过 data proxy metrics API 查询，异常 TiKV 节点能识别。
- TiKV CPU util metrics 可通过 data proxy metrics API 查询。
- TopSQL 可按 TiKV instance 和窗口查询，并能返回 `sql_digest` 与 CPU time。
- TopSQL total CPU 可查询，可用于计算 digest 在 TopSQL 内部的占比。
- slow log 字段可通过 data proxy SQL 查询，能批量聚合 count、query_time、mem、keys、request、cop 字段和 sample。
- metrics 短 TiKV instance 与 TopSQL/slow log 完整 TiKV 地址可以通过 `db-tikv-X.db-tikv-peer.tidb{clusterId}.svc:20160` 规则对齐。
- TopSQL link、slow query list link、slow query detail link 的 Clinic URL 拼接可行。
- slow log count 沿用 `COUNT(*)` 语义，不在本巡检项内做 `COUNT(DISTINCT id)` 或自定义去重。

之前验证发现的 Delta Lake active file 读取问题属于 data service/O11Y 数据读取 bug，不纳入本巡检设计，也不在巡检项中做数据修正。

## 实施计划

1. 更新本文档，删除旧的 TiKV process CPU 解释率、Likely/Possible 和 counter-evidence 设计，固化简化后的 TopSQL 归因规则。
2. 后端参数收敛：保留实际生效参数，新增 `topsql_dominance_ratio_threshold`，移除旧设计中不再生效的解释率、置信度和反证参数。
3. 后端候选 SQL 过滤：按 `topsql_cpu_percent_threshold` 过滤，再按 `topsql_dominance_ratio_threshold` 做主导截断。
4. 后端结果结构：统一输出 `sql_candidate`、`not_attributed`、`unknown`；不输出 Likely/Possible。
5. 后端链接：补齐 TiKV Duration Metrics / TiKV CPU Usage Grafana link、TopSQL link、slow log list/detail link。
6. 后端测试：覆盖异常窗口合并、TopSQL 占比过滤、主导比例截断、slow log enrichment 非强制、digest summary 聚合、严重级别、链接生成。
7. 前端策略配置：只展示用户需要理解和调整的核心参数，高级参数默认收起，隐藏内部参数。
8. 前端报告：突出 TiKV 异常窗口、TopSQL 候选、TopSQL 占比、slow log 补充样本和链接；去掉 Evidence Modal 与 Likely/Possible 语言。
9. 端到端验证：使用已知 high-duration 样本和至少一个 high-cpu 样本验证输出是否减少误报、digest summary 是否能反映影响面、窗口明细是否便于复核。

## 非目标

- 不迁移旧运维逻辑中的本地 S3 下载。
- 不迁移 SQLite/MySQL 中间存储。
- 不迁移 Feishu callback。
- 不实现跨 org 批处理；SQL Health Check 入口按单个 cluster/report 执行。
- 不直接调用 O11Y Prometheus endpoint；所有 metrics/TopSQL 请求走 Clinic Data Proxy。
- 不在默认实现中查询 RocksDB、raftstore、disk、thread CPU 等扩展反证指标。
- 不在巡检项内修正 slow log 底层重复数据问题。
- 不宣称默认巡检能证明 SQL 是唯一根因；报告只说明该 TiKV 异常窗口内哪些 SQL Digest 在 TopSQL 中占主要比重。
