☰
ClickHouse 性能结论判定规则:如何从 CI 噪声中识别真实回归与真实优化
2026/10/25 7:05:10 网站建设 项目流程

ClickHouse 性能结论判定规则:如何从 CI 噪声中识别真实回归与真实优化

【免费下载链接】ClickHouseClickHouse® is a real-time analytics database management system项目地址: https://gitcode.com/GitHub_Trending/cli/ClickHouse

本文围绕 ClickHouse 仓库中.claude/skills/perf-comparison性能对比技能的判定参考文档verdict-rules.md展开,系统讲解 ClickHouse 在评估 PR 性能测试结果时使用的一套证据驱动判定框架:判定工作的职责是判断一个性能结果是否“可采取行动”(actionable),而不是机械复述仪表盘上的某一行数据。读完本文,你将掌握一张完整的证据核对表、七种判定结论的适用条件、跨多次 PR 运行与 master 历史趋势的解读规则,以及如何写出可评审、可追溯的性能结论报告——这套规则同时适用于阅读 CI/仪表盘数据与本地perf.py复现结果两种场景。

判定任务的定位:结论不等于复述仪表盘

在 ClickHouse 的研发流程中,.claude/skills/perf-comparison/SKILL.md定义了一个核心问题:

Is this ClickHouse performance result real, flaky/noisy, already known from history, or worth deeper investigation?

即:这次性能结果是真实变化、偶发噪声、历史上已知波动,还是值得深入调查?判定技能的职责由此明确为判断结果是否 actionable——当一份性能测试表被呈现在你面前,你的任务不是重新声明“这一行比那一行慢/快”,而是综合 CI 差分、多次运行、置信度、master 历史、覆盖率与火焰图等多路证据,给出一个有依据的判定。

因此 verdict-rules 文档首先强调一个纪律:没有任何单条证据可以单独定案。原文档(.claude/skills/perf-comparison/references/verdict-rules.md)与 SKILL 都指出两类典型错误:

  • 仅凭一个 changed row 就判定回归为真实(未核对重复运行与历史趋势);
  • 没有任何历史/趋势证据就武断地把回归当成 flaky。

下文的所有规则,都是为了让结论有据可查、可复现、可被同行评审。

证据核对表(Evidence checklist)

verdict-rules 给出了一张面向每个可疑变化(suspicious change)的八行证据核对表。判定者应尽量为每一行填上“强信号”或“弱/噪声信号”,而不是只挑对自己结论有利的一列:

EvidenceQuestionStrong signalWeak/noisy signal
Changed metric原始差分是否超过阈值?差异大,方向一致仅勉强超过阈值
Repeated PR runs该变化是否在多天/多次运行中复现?同一 query/metric/arch 反复出现只出现一次/消失/方向翻转
Confidence置信度保持还是下调?tier/reason 支持原始变化;命名的检查项解释了原因tier 为 noise/suspect;自适应阈值或原始样本将证据降级
Trend/history所选运行是否落在正常散布范围之外?超出 p95/近期区间,历史稳定落在近期 p95 以内,存在频繁尖峰/突变点
Related metricsCPU/real/client/memory 是否彼此一致?各项指标方向一致指标互相矛盾
Coverage/PR diffPR 是否触达了被测试执行到的代码?改动的文件与覆盖文件相交无重叠或文件不相关
Flamegraph栈增量能否解释指标变化?热点与 PR/查询吻合热点缺失/无关/混杂
Local run受控的本地对照能否复现?可重复,p 值显著一次性结果或受工具链偏差影响

值得注意的是原文档的措辞:表格既问了“是否有强证据”,也明确列出“弱/噪声信号”是什么样——例如“barely above threshold”(仅勉强超阈值)、“appears once / disappears / flips”(出现一次/消失/翻转)等。填表过程中若多数行落在弱信号列,判定自然倾向噪声或需重跑;若多行落在强信号列,才足以支撑真实回归。

七种判定结论(Verdict labels)

real regression(真实回归)

当大部分下列条件成立时使用:

  • CI 差分高于阈值;
  • 同一 query/metric/arch 在多次 PR 运行中重复出现,或置信度数据支持;
  • 历史数据中并不常见同样的随机波动;
  • 相关指标(CPU/real/client/memory)方向一致;
  • PR/覆盖率关联合理,或火焰图指向被改变的行为。

verdict-rules 特别强调:不必要求每一项都满足;对缺失的证据要明确说明为什么缺失、为何不影响判定。比如没有火焰图数据时,可写“flamegraph not available”,而不是装作它支持了结论。

likely noise(疑似噪声)

当出现以下情形时使用:

  • 变化的行只在一次 PR 运行中出现、后续运行中消失;
  • 所选数值落在正常趋势/历史散布区间内(如近期 p95 以内);
  • 置信度模块下调了原始证据的等级;
  • 相关指标与头条指标互相矛盾;
  • PR 与测试之间没有合理解释,也没有 profile 证据支持。

一个关键纪律:即便如此,仍然要把证明它是噪声的数据展示出来(原文档原文 "Still show the data that proves this"),而不是一句“可能是噪声”带过。类似地,SKILL 也强调"dismissal needs evidence"——降噪同样要给出历史或重复运行数据。

needs rerun(需要重跑)

当以下情形出现时使用:

  • 原始差分很大,但置信度/历史/重复运行结果彼此矛盾;
  • 只有一次运行存在;
  • 结果接近阈值(near threshold);
  • 环境或基础设施可能扭曲了测量(机器争抢、构建差异等);
  • 本地结果与 CI 结果不一致。

推荐动作(Recommended action)是:重跑性能 CI,或做一次聚焦的本地对照实验。从判定语义看,needs rerun 是“证据冲突但值得再确认”,而不是放弃。

unstable test(测试本身不稳定)

当以下情形出现时使用:

  • 趋势/历史显示频繁出现同等量级的尖峰;
  • 同一查询在不同运行中时而变慢、时而变快(direction flips);
  • 仪表盘将该指标标记为 unstable;
  • 自对照或重复运行本身就噪声很大。

verdict-rules 特意写了一条使用约束:不要仅仅因为结果不合意(inconvenient)就使用此标签。unstable 必须由可展示的运行间波动数据支撑——比如后文 Report wording 一节给出的跨运行差分序列例子。

real improvement(真实优化)

判定门槛与real regression对称:

  • 加速幅度高于阈值;
  • 重复运行/置信度/历史数据支持;
  • 它并不是随机不稳定的简单反向翻转(即不是噪声的偶然回落)。

同时ci-api.md提醒一个细节:当前置信度模块的 tier 名称/reason 可能是按变慢(slowdown)方向措辞的,不要把速度提升行打印成confirmed_regression;只有当周边证据支持时,才把加速行称为 stable speedup/change。

local-only evidence(仅有本地证据)

当本地perf.py显示出有意义的结果,但 CI/仪表盘证据缺失或尚未核对时使用。它的语义是:本地结果在当前阶段只是本地证据,直到与 CI/history 对照后才能升级判定。

not enough evidence(证据不足)

当缺少关键的“身份信息”(identity)时使用,例如:

  • run 未知(unknown run);
  • query index 未知;
  • 指标/架构选错(wrong metric/arch);
  • 历史不可得,且没有重复运行或本地证据。

这一类是最低信息条件下的兜底结论:不要硬凑一个方向性结论。

重复 PR 运行规则(Repeated PR run rules)

检查某个 PR 时,应列出该 PR 的全部 performance.ci 运行,然后在不同运行间比较同一个 test/query/metric/arch:

curl -fsS "$BASE/runs?q=$PR" | jq .

其中$BASE为 performance.ci 的标准 API 前缀(具体端点形态可参见 .claude/skills/perf-comparison/references/ci-api.md),?q=$PR返回该 PR 的所有运行,注意搜索可能带回超集,务必按identity.prNumber == PR过滤。

原文档给出的解读规则非常明确:

  • N/N 次重复出现且 diff 相似→ 强信号(strong);
  • 仅 1/N 出现一次→ 弱信号(weak),很可能需要重跑或属噪声,除非 diff 极大且 profile 能解释;
  • 方向反复翻转→ 测试不稳定(unstable);
  • 只有旧运行有该变化、最新运行干净→ 很可能已解决或属噪声;
  • 最新运行在早期干净运行之后新出现了该变化→ 需要调查:可能是 PR 新改动引入,也可能是 master 基线移动导致。

这些规则直接对应到 helper 脚本,可对每个 PR 做跨运行对比:

# 列出某 PR 的所有 performance.ci 运行 python3 .claude/skills/perf-comparison/scripts/perf_api.py runs --pr "$PR" # 纵向对比某 test/query/metric/arch 在该 PR 全部运行中的表现(oldest→newest diff 图表) python3 .claude/skills/perf-comparison/scripts/perf_api.py pr-query-history \ --pr "$PR" --test aggregation_in_order_2 --query-index 4 \ --metric client_time --arch arm

master / history 趋势规则

判定噪声与真实效果的关键差异在于:把所选运行放到 master 长期历史里看,而不是凭感觉。verdict-rules 要求直接使用仪表盘的 trend/history 数据。

支持 flakiness/noise 的证据:

  • 所选点落在近期正常区间内,例如低于/接近 p95;
  • 没有该 PR 时也会出现同等量级尖峰;
  • 置信度自适应阈值(adaptive threshold)大于原始效应;
  • changePoints显示 master 在 PR 运行前后存在基线突变;
  • periodComparisons显示历史上频繁出现类似的波动。

支持“新 PR 效应”的证据:

  • 所选点以有意义的倍数超出近期 p95;
  • 历史中不存在可比的尖峰;
  • 多次 PR 运行呈现同样的变动;
  • PR 确实改动了被覆盖且相关的代码。

历史描述语句的“允许/禁止”清单

verdict-rules 对报告措辞立了硬性格式规范:

✅ 必须使用的历史描述格式(required wording),形如:

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.

即:必须说明点的数量与时间跨度、给出历史总体分位数(p05/p50/p95)与近期窗口(如最近 30 个点及其时间跨度)的分位数,并把候选运行与 p95 做倍数比较。

❌ 禁止使用的历史描述(forbidden wording):

min=..., median-ish=..., max=..., last=... .

只报 min/max/median/last 无法反映散布形态,无法支撑噪声与真实变化的区分。这条禁止项与ci-api.md的“required summary shape”互相印证:要报周期、全量历史 p05/p25/p50/p75/p95、近期窗口 p05/p50/p95,并将选中点的 old/new 值与 p50、p95 比较。

覆盖率 / PR 关联规则

Coverage 接口回答的问题是:这个测试是否执行了 PR 所改动的代码?它不能回答:PR 是否导致了变慢?——原文档用两句话划清了这条因果边界。

因此 Coverage 只用于“表述可能性”(phrase plausibility):

  • intersectingFiles > 0:PR 改动与测试覆盖相交 → 关联合理,应进一步查看改动 hunk;
  • PR 只改了文件、但与测试覆盖无重叠 → 关联不那么直接;
  • 只有测试覆盖文件、PR 并未改动它们 → 可能是间接行为变化;
  • 覆盖率数据不可用 → 明确写 unavailable,退而求其次用 PR diff 与查询文本人工比对。

对应的抓取方式(fileSet=intersecting返回 PR 改动与测试覆盖相交的文件集,可取files[].path、files[].patch、coverageRanges等字段):

curl -fsS "$BASE/runs/$RUN_ID/tests/$TEST/coverage?fileSet=intersecting" | jq .

一个已观测到的 API 陷阱(记录在 .claude/skills/perf-comparison/references/ci-api.md):当相交文件数为 0 时,响应可能省略totals.intersectingFiles,需用len(files)作为 0 的兜底,不要误判为“数据缺失”。

火焰图(Flamegraph)规则

火焰图只能用于精确匹配的证据组合:run id、test、query index、metric、arch、trace type 全部一致,跨维使用没有意义。

解读方向:

  • CPU diff 栈在候选侧增长 → 计算热点(compute hotspot)候选;
  • Real 时间增长但 CPU 不增长 → 更可能是等待/I/O/锁/调度(wait/lock/scheduler);
  • Memory trace 增长 → 更可能是内存分配压力(allocation pressure)。

最重要的边界是:如果栈增量无法与 query/PR 改动产生关联,就不要声称找到了根因(Do not claim root cause if stack delta does not connect to query/PR changes)。

报告措辞同样有硬性要求(required wording):

  • 首先给出 UI 查询页链接(格式形如/runs/<runId>/tests/<test>/queries/<queryIndex>),因为该页面内含 Flamegraphs 卡片;
  • 原始 API 链接只作为机器可读的辅助证据(backup);
  • 列出 top sample deltas;
  • 明确说明哪些栈帧是缺失的(absent)。

❌ 禁止的模糊措辞(forbidden wording):

flamegraph diff exists and contains relevant samples.

原文档评价这种说法 "too vague to review"(过于含糊,无法评审)。SKILL 的推荐写法是给出一张“叶函数/子系统 | baseline samples | candidate samples | delta | interpretation”的表格。

另外ci-api.md记录:火焰图端点可能返回 404 或空帧——缺失火焰图数据是“证据缺席”,不代表噪声,报告里应写no frames returned/not available,不能反过来当噪声证据用。

本地 perf.py 规则

本地复现只有在满足严格条件时才“有意义”(meaningful):

  • 足够的运行次数;
  • p 值 ≤ 0.05;
  • diff 超过测试阈值;
  • 结果重要时能重复稳定;
  • 数据集完全一致、服务器设置可比(identical datasets and comparable server settings)。

本地结果在被 CI/history 核对之前,只能按 local-only 上报("Local-only result should be reported as local-only until checked against CI/history")。这与判定标签local-only evidence呼应。

本地对照的标准姿势(详见 .claude/skills/perf-comparison/references/local-perf.md)是对旧/新两个二进制各起一个隔离 server,再用仓库自带的tests/performance/scripts/perf.py跑同一份性能测试 XML:

mkdir -p tmp tests/performance/scripts/perf.py \ --host 127.0.0.1 127.0.0.1 \ --port 9000 9001 \ --runs 7 \ tests/performance/aggregation_in_order_2.xml | tee tmp/aggregation_in_order_2.perf.tsv

然后用本技能的解析器把输出转成可读报告:

python3 .claude/skills/perf-comparison/scripts/parse_perf_py.py tmp/aggregation_in_order_2.perf.tsv

解析器(.claude/skills/perf-comparison/scripts/parse_perf_py.py)实现了几条与本规则一致的判定逻辑,值得对照阅读源码:

  • 阈值取自 perf.py 输出的report-threshold行;
  • 判定函数verdict(diff, pvalue, threshold):abs(diff) < threshold→ within threshold;pvalue > 0.05→ not statistically significant;否则按 diff 正负判 candidate slower / candidate faster;
  • diff 行为(new_median - old_median) / old_median:正为变慢、负为变快;
  • 无diff行时提示 "either only one server was used, too few samples were collected, or no query passed perf.py's diff/p-value gate"。

也就是说,“超过阈值 + p≤0.05 + 足够运行数 + 可重复”这些门槛都被写进了工具层,而非仅停留在文档约定。

报告措辞示例:好写法与坏写法

verdict-rules 用成对的正反例说明了什么叫“可评审的报告措辞”。以下引用均来自原文档,逐条保留了格式要求。

好写法(Good)——结论由证据链导出,而非表格复述:

The latest PR run showsaggregation_in_order_2/4client_time/armat+23.2%, above a21.7%threshold. The same query appears in 3/4 PR runs, history does not show comparable spikes, and coverage intersectssrc/AggregateFunctions/...; verdict: real regression, medium confidence.

这条例文完整串联了证据核对表的多个维度:原始 diff 与阈值、跨运行复现次数、历史无同类尖峰、覆盖率与src/AggregateFunctions/...相交——最后才落到 verdict 与 confidence 等级。

坏写法(Bad)——直接把表格行为当结论:

This is a regression because the table says slower.

坏写法(Bad)——输出仪表盘内部代码而不解释(SKILL 的第 4 条核心原则也禁止这一点):

Confidence:M1:downgrade, M2:neutral, M3:downgrade.

好写法(Good)——把置信度翻译成 tier + reason + 具名检查项:

Confidence tier isnoise: History Adaptive Threshold downgraded because observed 47.2% is below the 94.8% threshold from recent master noise; Raw Sample Evidence also downgraded.

坏写法(Bad)——无数据支撑的模糊断言:

Cross-run behavior is unstable.

好写法(Good)——用完整跨运行序列证明不稳定:

Cross-run chart oldest→newest: -15.3%, +19.8%, +6.6%, -10.7%, +15.3%, -15.6%, -3.1%, +22.5%; direction flips repeatedly, so verdict: unstable.

好写法(Good)——用更新的重跑结果取代旧数据,并说明取代理由:

The latest rerun no longer reproduces the earlier+24%row: the same test/query/metric/arch is now+1.2%, below threshold. The older row is superseded, so verdict: likely noise or resolved.

坏写法(Bad)——在更新的干净重跑之后仍展示旧的+24%表格而不加任何解释。

这组正反例背后是一条贯穿始终的原则:报告必须区分“当前被接受的有效证据”与“已被取代的探索性旧数据”,新旧矛盾时要亮出更可信的一方并说明为什么。

判定规则在仓库中的工程支撑

这套判定规则并非孤立的文档,而是 ClickHouse 仓库中一整套“性能对比技能”的组成部分,各文件职责如下:

仓库文件职责
.claude/skills/perf-comparison/SKILL.md技能入口:总体原则、数据源优先级、证据阶梯(11 步)、报告模板、常见错误清单
.claude/skills/perf-comparison/references/verdict-rules.md本文主体:判定标签与各维度证据解读规则
.claude/skills/perf-comparison/references/ci-api.mdperformance.ci API 的端点、字段与实测陷阱(confidence、trend、history、coverage、flamegraph)
.claude/skills/perf-comparison/references/local-perf.md本地 perf.py、server 启动、数据集与二进制来源约定
.claude/skills/perf-comparison/scripts/perf_api.py汇总 CI/API 数据的 helper(runs、pr-inventory、query、pr-query-history、master-checks 等)
.claude/skills/perf-comparison/scripts/parse_perf_py.py解析本地 perf.py TSV 输出并给出 verdict
.claude/skills/perf-comparison/scripts/test_perf_api.py对分类逻辑的测试,复刻了ci/jobs/scripts/perf/compare.sh的 changed/unstable 判定
tests/performance/aggregation_in_order_2.xml例文中的具体性能测试(含 substitution、create/fill/query 结构)
tests/performance/scripts/perf.py本地性能测试运行器,输出report-threshold/diff ... pvalue供判定
tests/performance/scripts/compare_perf_tsv.pyTSV 级比较工具

例文中的aggregation_in_order_2是仓库里真实存在的性能测试:该 XML 将optimize_aggregation_in_order等设置写入<settings>,用<substitution>把uniqs取 100/10000/1000000 三种规模,对一张 30 分区的 MergeTree 表执行sum/groupArray/uniqExact三种分组聚合查询(tests/performance/aggregation_in_order_2.xml)。因此aggregation_in_order_2/4这样的标识、以及src/AggregateFunctions/...的覆盖率关联,都能在真实数据中落地验证。

值得说明的数据分类边界(记录在 .claude/skills/perf-comparison/scripts/test_perf_api.py 中):is_changed = abs(diff) > changed_threshold and abs(diff) >= stat_threshold,is_unstable = (not is_changed) and stat_threshold > unstable_threshold,方向direction = slowdown if diff > 0 else speedup。这个逻辑告诉我们:“变化行”与“不稳定行”在分类层面就是互斥的两类——这正解释了判定规则为何反复强调不要用 unstable/high-noise 行去混充已变更行,也印证了 verdict-rules 中“unstable test 不得因结果不合意而滥用”的约束。

一份合格结论报告的最小结构

把以上规则汇总成可执行的最小流程(与 SKILL 的 Required report format 一致),一份合格报告应包含:

  1. Verdict 头:结论标签(七个之一)+ 置信度等级(high/medium/low);
  2. 已核对证据清单:run/API URL、test/query/metric/arch、old/new/diff/threshold、重复 PR 运行结果、置信度 tier/reason、历史趋势(含分位数)、覆盖率/PR 关联、火焰图(UI 查询页链接 + top sample deltas,或明确 not available)、本地 perf.py(若有);
  3. Changed metrics 表:清晰区分信号行与噪声/不确定行,不让 unstable 行混入变更表;
  4. 结论理由:说明为何当前有效证据支持该结论,旧数据如何被取代;
  5. 推荐下一步动作:no action / rerun performance CI / run local perf.py / inspect flamegraph hotspot / inspect PR code around covered files / bisect or targeted local validation。

这套判定框架的本质是“证据加权”:真实回归需要跨运行复现、历史无同类尖峰、相关指标一致、PR 关联合理等条件互相支撑;而噪声、不稳定与证据不足也各有其必须展示的“反证数据”。无论数据来自 CI 仪表盘还是本地perf.py,规则都要求把结论写成人可复核、机器可追溯的语句——这正是 ClickHouse 长期演进过程中控制性能回归工程实践的核心方法论。

【免费下载链接】ClickHouseClickHouse® is a real-time analytics database management system项目地址: https://gitcode.com/GitHub_Trending/cli/ClickHouse

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询