ClickHouse performance.ci API 参考:基于 REST 接口与 Dashboard 的 PR 性能回归分析指南
【免费下载链接】ClickHouseClickHouse® is a real-time analytics database management system项目地址: https://gitcode.com/GitHub_Trending/cli/ClickHouse
在 ClickHouse 开源仓库的日常开发中,评估一个 PR 是否引入性能回归,需要回答一个核心问题:这个性能结果是真实的、还是噪声/偶发、抑或是 master 历史上已知的波动?ci-api.md 正是为回答该问题而编写的performance.ciAPI 与 Dashboard 操作参考——它系统性地覆盖了 Run 发现、Run 概览、置信度、趋势/历史、覆盖率/PR 交集、火焰图、master 状态统计以及 CI 产物兜底等全部数据通道。阅读完本文,你将掌握以performance.ci.clickhouse.com/api/v1为基址的完整 HTTP 调用模式,并能结合仓库内的辅助脚本 perf_api.py 与判定规则 verdict-rules.md,对任意 PR 的性能结果给出有据可查的结论。
数据源优先级:先看 Dashboard/API,再看历史与产物
该参考文档是 perf-comparison 技能(SKILL.md)的组成部分。技能层面对数据源规定了严格的优先级,理解这一点才能正确使用本文的各个 API:
- performance.ci API/Dashboard 优先:用于当前 PR 的 Run 列表、变更行清单(changed-row inventory)、置信度、查询详情、趋势/历史图、覆盖率交集与 flamegraph-diff。
- ClickHouse Play(
default.checks表):当需要对每一行变更统计"过去 30 天 master 上同样测试/查询出现slower/faster/unstable的次数"时使用。 - CI 产物/日志:仅在 Dashboard/API 无法提供根因数据时兜底,例如 server 日志、raw trace、raw ProfileEvents TSV、精确二进制版本/build ID。
- 本地
perf.py:仅在显式提供 old/new 二进制并做本地验证时使用,且必须声明"不等价于 CI"。
这种"证据阶梯"(evidence ladder)的设计保证了每个结论都建立在最可靠的数据源上,raw 产物永远只是 artifact fallback evidence,不能反过来覆盖 Dashboard 的分类结果。
API 基础:基址与 Run 发现
所有接口共用一个基址:
BASE="https://performance.ci.clickhouse.com/api/v1"查找某个 PR 的全部性能 Run
PR=104350 curl -fsS "$BASE/runs?q=$PR" | jq .返回的items[]中每个元素代表一次完整的性能对比 Run,文档要求重点关注以下身份与摘要字段:
| 字段 | 含义 |
|---|---|
identity.runId | Run 的唯一标识,后续所有/runs/$RUN_ID/...接口的参数 |
identity.prNumber | 关联的 PR 编号 |
identity.oldSha | 基线(master/参考)二进制 commit SHA |
identity.newSha | 候选(被测试 PR)二进制 commit SHA |
identity.runTime | Run 的执行时间 |
arches | 本次 Run 覆盖的架构(如amd、arm) |
changedQueries | 判定为"已变更"的查询数 |
slowdownQueries | 变慢查询数 |
speedupQueries | 加速(改善)查询数 |
unstableQueries | 高噪声/不稳定查询数 |
重要约定:搜索可能返回超出预期的结果,必须始终用identity.prNumber == PR二次过滤。辅助脚本中对应的过滤逻辑可参考 perf_api.py 的fetch_pr_runs()。
实测中观察到的 API 陷阱
参考文档专门记录了 live testing 中发现的四条 caveat,直接决定数据如何解读:
unstableQueries[]可能包含高噪声/高阈值行,即使汇总卡片显示Unstable queries: 0。它们应被当作 flakiness/噪声上下文,除非同时超过阈值,否则不应视为 changed rows。- 当
fileSet=intersecting无交集文件时,totals.intersectingFiles可能被省略,此时回退用len(files)作为零交集计数。 - 趋势响应中的
selectedRunPoint.value可能是标记用的0,该 PR 的真实测量值应以 query detail 的 old/new 值为准。 - Flamegraph 端点即使在查询确实变更时也可能返回 404——缺少 flamegraph 数据不代表任何方向,既不能证明有变化也不能证明是噪声。
Run 概览:一次 Run 的完整对比清单
拿到runId后,首先获取该 Run 的概览。通过metrics参数可以控制返回哪些指标维度,惯例为client_time,real_time,cpu_time,memory:
curl -fsS "$BASE/runs/$RUN_ID?metrics=client_time,real_time,cpu_time,memory" | jq .值得使用的顶层结构:
slowdowns[]—— 判定变慢的对比行speedups[]—— 判定改善的对比行unstableQueries[]—— 高噪声/高阈值行testSummaries[]—— 各测试摘要assets[]、reports[]—— 关联的产物与报告
ComparisonRow 字段
每一条变更对比行的核心字段如下:
| 字段 | 含义 |
|---|---|
test | 性能测试名(对应tests/performance/*.xml的测试文件基名) |
queryIndex | 测试内的查询序号 |
queryDisplayName | 查询展示名 |
metric | 指标名(client_time/real_time/cpu_time/memory等) |
arch | 架构(amd/arm) |
oldValue | 基线测量值 |
newValue | 候选测量值 |
diffPercent | 变化百分比 |
statThreshold | 统计判定阈值 |
direction | 方向(slowdown/speedup) |
severity | 严重度 |
confidence | 置信度信息 |
数值单位陷阱:在已观察到的示例中,API 的diffPercent与statThreshold以小数/分数形式返回——例如0.232表示约+23.2%。不确定展示格式时,应按分数处理并在展示时转换为百分比。这一点在 perf_api.py 的change_pct()中同样被注释为"Observed API values are fractional"。
判断一行是否超过阈值,参考脚本采用abs(diffPercent) + 1e-6 >= abs(statThreshold)(容忍二进制/十进制舍入误差),见 perf_api.py。
Confidence:置信度如何把 raw change 转化为可对外表述的证据
Confidence 模块回答"这一行变更到底可信多少"。它可以按 Run 或按单个 Test 拉取:
# Run 级置信度 curl -fsS "$BASE/runs/$RUN_ID/confidence?metrics=client_time,real_time,cpu_time,memory" | jq . # Test 级置信度(更常用) curl -fsS "$BASE/runs/$RUN_ID/tests/$TEST/confidence?metrics=client_time,real_time,cpu_time,memory" | jq .使用规范(写入报告时必须遵守):
- 始终汇报
confidence.tier与confidence.reason(当存在时)。 - 模块细节使用
modules[].title、modules[].status、modules[].interpretation,以及有用的modules[].rows[]事实。 - 严禁把
M1:downgrade, M2:neutral这类 Dashboard 内部简写直接写进报告——那只是内部代号,不是面向评审的结论。辅助脚本在 perf_api.py 中会对 speedup 行做措辞校正(不打印 slowdown/regression 语义)。 - 对 speedup 行要格外小心:当前 tier 名称/原因可能以 slowdown 为导向,不要在加速行上打印
confirmed_regression;只有当周边证据支持时才称之为 stable speedup/change。
推荐的对外表述示例:
tier=noise; reason=change is smaller than recent-history adaptive threshold; History Adaptive Threshold downgraded because observed 47.2% < adaptive 94.8%.
Test 与 Query 详情:定位到具体查询与火焰图资产
从 Run 概览定位到可疑的(test, queryIndex, metric, arch)组合后,下钻到最细粒度:
# 单个 Test 详情 curl -fsS "$BASE/runs/$RUN_ID/tests/$TEST?metrics=client_time,real_time,cpu_time,memory" | jq . # 单个 Query 详情(最常用) curl -fsS "$BASE/runs/$RUN_ID/tests/$TEST/queries/$QUERY_INDEX?metrics=client_time,real_time,cpu_time,memory" | jq .Query detail 是证据收集的核心入口,用于采集:
queryText—— 实际执行的 SQL 文本;- 该查询的全部 metric rows(跨 arch/方向);
flamegraphAssets—— 火焰图资产引用;- report links。
结合 perf_api.py 的cmd_query()实现可以看到,脚本正是通过该端点过滤 metric 与 arch 后输出"匹配的 metric 行"表格,并据此进一步拼接 confidence/trend/history/coverage/flamegraph-diff 参数。
Trend 与 History:用历史分布判断"越界"还是"噪声"
单次 PR Run 的数值没有意义,必须放进历史分布中解读。文档提供两个互补接口:
# 选定 Run 附近的趋势 curl -fsS "$BASE/runs/$RUN_ID/tests/$TEST/trend?metric=$METRIC&queryIndex=$QUERY_INDEX&arch=$ARCH" | jq . # 更长历史 curl -fsS "$BASE/history/$TEST/$QUERY_INDEX?metric=$METRIC&arch=$ARCH" | jq .可用结构
points[]/centerTrend[]:正常散布与选定 Run 的上下文;changePoints[]:已知的历史跳变点;periodComparisons[]:较大的历史区间变化。
报告必须满足的摘要形状
趋势/历史部分仅给 min/max 是不合格的。规范的摘要必须包含:
- 说明 points 覆盖的时间段;
- 给出分位数而不仅是 min/max:
p05/p25/p50/p75/p95; - 展示近期窗口的散布,例如最近 30 个点及其时间跨度;
- 把选定 Run 的 old/new 值分别与
p50、p95对比; - 对 change points 去重,列出最近相关条目的日期/方向/SHA。
例如 verdict-rules.md 中给出的合格措辞模板:
History center trend: 976 points over 2026-04-24 → 2026-06-15; all p05/p50/p95 = ...; recent 30 points over 22.7h p05/p50/p95 = ...; candidate is 3.06x p95.
对应的辅助实现见 perf_api.py:percentile()计算线性插值分位数,summarize_points()自动生成"全量分布 + 最近 30 点分布",selected_value_context()计算选定行相对 p50/p95 的倍数;change points 的去重与排序在change_points_summary()中完成。
解读规则
- 选定 Run 远超出近期 p95 且多次 PR Run 重复出现 → 证据更强;
- 选定 Run 落在近期正常散布内 → 很可能噪声/不稳定;
- master 近期存在已知 change point → 该 PR 可能只是在继承 master 自身的波动,而非它引起的变化。
Coverage / PR 交集:判断"测试是否执行了 PR 修改的代码"
Coverage 接口回答的问题是这个 perf test 有没有执行到 PR 触碰的代码,它不能证明因果关系,只能评估 PR/测试关系的 plausible(合理性):
curl -fsS "$BASE/runs/$RUN_ID/tests/$TEST/coverage?fileSet=intersecting" | jq .fileSet支持的其他取值:
fileSet=pr # PR 触碰的文件 fileSet=covered # 测试覆盖的文件 fileSet=all # 全部关键字段:
coverageAvailable、messagetotals.touchedFiles(PR 触碰文件数)、totals.coveredFiles(测试覆盖文件数)、totals.intersectingFiles(交集数)files[].path、files[].status、files[].patch、files[].coverageRanges[]
辅助脚本 perf_api.py 在totals.intersectingFiles缺失时自动用len(files)兜底,与文档 caveat 一致。当 coverage 不可用时,可以退回到gh pr diff结合查询文本人工判断相关子系统。
Flamegraphs:从采样栈增量定位热点
文档强调:面向评审者的链接应首先指向 UI 的 query 页面(该页面内含 Flamegraphs 卡片):
https://performance.ci.clickhouse.com/runs/$RUN_ID/tests/$TEST/queries/$QUERY_INDEX只有需要机器可读的栈/增量时才使用 raw API。
单侧 collapsed stacks
curl -fsS "$BASE/runs/$RUN_ID/tests/$TEST/queries/$QUERY_INDEX/flamegraph?metric=$METRIC&arch=$ARCH&side=candidate&traceType=CPU" | jq -r .collapsedside可取candidate(被测试 PR 侧)或基线侧;配合arch指定架构。
差分火焰图数据
curl -fsS "$BASE/runs/$RUN_ID/tests/$TEST/queries/$QUERY_INDEX/flamegraph-diff?metric=$METRIC&arch=$ARCH&traceType=CPU" | jq .Flamegraph diff 的字段为stack、baselineSamples、candidateSamples。
traceType 语义
| traceType | 含义 |
|---|---|
CPU | 计算时间 |
REAL_TIME | 墙钟时间:适合观察等待、锁、I/O、调度器噪声 |
MEMORY | 内存相关的采样(如可用) |
建议的摘要表格
| Leaf/subsystem | Baseline samples | Candidate samples | Delta | Interpretation |
|---|
根因声明约束:不能仅因为某个 frame 有 samples 就声称根因,它必须与 metric/query 匹配,且与 PR 修改的 plausibility 吻合。评审可用的火焰图证据必须包含:① 精确的 UI query-page 链接(优先);② 仅在有价值时附 raw API 链接;③ 顶部采样增量表格;④ 与查询关联的简短解读;若无数据则明确写no frames returned/not available。
30 天 Master 状态计数:通过 ClickHouse Play 的default.checks
Dashboard/API 偏重图表与趋势;当需要"同一测试/查询在 master 上 30 天内出现过多少次slower/faster/unstable"这类直接计数时,走 ClickHouse Play 的default.checks表。该表记录了每次 master 性能检查的状态。
推荐直接使用封装好的 helper:
python3 scripts/perf_api.py master-checks --pr "$PR" --limit 50等价的 HTTP + SQL 查询模式:
curl -fsS 'https://play.clickhouse.com/?user=explorer' --data-binary @- <<'SQL' SELECT replaceRegexpOne(test_name, '::(new|old)$', '') AS test, countIf(test_status = 'slower') AS slower_count, countIf(test_status = 'faster') AS faster_count, countIf(test_status = 'unstable') AS unstable_count, count() AS total_runs FROM default.checks WHERE pull_request_number = 0 AND check_name LIKE '%Performance%arm%' AND check_start_time >= now() - INTERVAL 30 DAY AND test_name IN ('fixed_hash_table_parallel_merge #1::new') GROUP BY test FORMAT JSONEachRow SQL注意关键过滤条件:
pull_request_number = 0:只统计 master 自身运行;check_name LIKE '%Performance%arm%':与待分析行保持同架构;test_name需写成'<test> #<queryIndex>::new'形式;- 时间窗
INTERVAL 30 DAY由--days参数控制(默认 30)。
计数结果用于分类:new in PR、rarely on master、flaky/slower on master、unstable on master、new improvement、fixes known master regression。具体分类规则可在 perf_api.py 的classify_with_master_counts()中看到:例如unstable >= 5或比例 >1% 判为 "unstable on master";slowdown 行在 master 上 slow 比例 >1% 或次数 ≥3 判为 "flaky/slower on master",1~2 次判为 "rarely slower",master 总样本 ≥50 且从未出现则判为 "new in PR — investigate"。
TSV/raw 产物清单:Dashboard 缺失时的次选证据
当 Dashboard/API 无法提供所需 artifact 级数据(例如某分片、某 run 的原始逐次运行值)时,使用 raw TSV。可用数据源包括:
all-query-metrics.tsv(raw,按当前 CI 列序、无表头);fetch_perf_report.py --tsv输出(带命名字段)。
两者按 per-shard 行提供:old/new(raw 文件中叫left/right)、diff、times_change、stat_threshold、test、query_index、查询展示文本;fetch_perf_report.py --tsv额外携带arch、shard、is_changed、is_unstable、direction。
使用 helper 解析:
python3 scripts/perf_api.py tsv-inventory --tsv pr_${PR}_amd.tsv pr_${PR}_arm.tsv --limit 20解析器 perf_api.py 同时支持命名 TSV 与无表头的 rawall-query-metrics.tsv(当前 CI 列序定义在RAW_ALL_QUERY_METRICS_COLUMNS),并且能透明解压 zstd/gzip 压缩的文本产物(与 CI 对超过阈值文本产物做 zstd 压缩的策略一致,见read_text_maybe_compressed())。判定规则复刻自 compare.sh 中的:
changed_fail:abs(diff) > changed_threshold && abs(diff) >= stat_threshold,默认changed_threshold = 0.15;unstable_fail:非 changed 且stat_threshold > unstable_threshold(默认0.25)。
边界:TSV 行只是 artifact fallback evidence,不得用来推翻 Dashboard 的分类结论。
Artifact/日志兜底:需要根因时的最终落点
当 Dashboard/API 无法解释"为什么"时,下载 CI 产物。文档给出清晰的产物地图:
| 产物 | 用途 |
|---|---|
logs.tar.zst/job.log.zst | Job 上下文、server 配置、警告/错误、机器争用 |
left/server.log、right/server.log | 精确的 old/new 二进制 revision、build ID、启动设置、查询执行计时、后台活动 |
report/stacks.left.tsv、report/stacks.right.tsv | 每个查询的 symbolized collapsed stacks(CPU/Real/Memory),优先于*-trace-log.tsv |
analyze/tmp/{test}_{queryN}.tsv | 每次运行的 raw profile events,Dashboard 只显示汇总指标时使用 |
预构建的.svgflamegraphs | Dashboard flamegraph-diff 缺失或不完整时使用 |
注意:*-trace-log.tsv并不随logs.tar.zst一起发布,且只包含query_id, trace, trace_type, size(原始地址、无符号)。这些 trace/addresses 类文件的产生逻辑可以对照 compare.sh 中的get_profiles()——它只 dumpsystem.trace_log的四列并把符号解析任务交给独立的*-addresses.tsv。使用顺序必须是:优先 Dashboard flamegraph/query API,仅在需要 raw logs、raw traces、精确 build 元数据或 Dashboard 未 profile 的查询/run 时才回退到产物。
实战集成:把 API 组合成一份可评审的性能结论
单独的接口只是数据,真正的价值在于如何组合成结论。建议按以下工作流使用本文接口:
- Run 发现与身份核对:
GET /runs?q=$PR,过滤prNumber,记录oldSha/newSha/arch/runTime; - 变更行全量盘点:
GET /runs/$RUN_ID分离slowdowns/speedups/unstableQueries三张表,且改善行必须与回归行一起列出(pr-inventory命令会自动输出Top likely signal/Top likely noise/Top improvements摘要层); - 重复性检查:同一
(test, queryIndex, metric, arch)跨多个 PR Run 是否反复出现同向变化,方向是否翻转; - 置信度取证:按 test 拉
confidence,翻译 tier/reason 为自然语言证据; - 历史/趋势定位:用 trend/history 分位数判断是否越界;
- master 计数分类:
master-checks给出new in PR/flaky/unstable/fixes known regression等标签; - 覆盖率与火焰图:
coverage?fileSet=intersecting判断相关性,UI query 页的 Flamegraphs 卡片定位热点; - 产物兜底:必要时解包
logs.tar.zst与server.log核对构建与计时细节。
所有步骤都有现成命令封装在 perf_api.py 中:runs、pr-inventory、run/changes、query、pr-query-history、master-checks、tsv-inventory。各判定标签的取舍标准(real regression、likely noise、needs rerun、unstable test、real improvement、local-only evidence、not enough evidence)以及禁止/推荐的报告措辞,均在 verdict-rules.md 中有完整定义。
报告措辞红线
- 不得用"dashboard 表说 slower 所以是 regression"这类一句式结论;
- 不得输出
M1:downgrade, M2:neutral这类未解释的内部代号; - 历史部分不得只写
min/max/median/last,必须给出分位数与时间窗; - 新 Run 数据出来后被推翻的旧测量必须显式声明"被更新的结果取代",而不是直接展示旧表;
- 火焰图表述必须落到"顶部采样增量 + 与查询/PR 的关系",而不是笼统的"flamegraph diff exists and contains relevant samples"。
延伸阅读
- perf-comparison/SKILL.md:本 API 参考所属的技能总入口,定义核心原则、证据阶梯与数据源优先级;
- verdict-rules.md:判定标签与报告措辞的权威规则;
- local-perf.md:本地
perf.py与可选 server 启动、数据集、profile 流程(当 CI 证据不足需本地复现时使用); - perf_api.py:上述全部 API 的 stdlib-only 封装实现;
- test_perf_api.py:对 TSV 解析与 compare.sh 判定规则一致性的回归测试;
- compare.sh:性能对比 CI 的底层实现(阈值判定、profile 采集、change 确认机制);
- perf.py:性能测试执行脚本,本地复现与 CI 均依赖它。
【免费下载链接】ClickHouseClickHouse® is a real-time analytics database management system项目地址: https://gitcode.com/GitHub_Trending/cli/ClickHouse
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考