结论先行:你们卡住的根因不是"阈值没调对",而是预算的锚点放错了地方——预算应该锚在线上 RUM P75 的业务目标上,用一次"斜率校准"把它翻译成 CI 侧的 ms 预算,而不是反过来拿 CI 分数去猜线上。分数(0-100)只是展示层,门禁一律用原始指标(ms / KB / 比值)。下面给一套我们团队在网关 + B 端控制台跑过 8 个月的具体做法,中间件视角,重点讲"怎么从线上反推 CI 预算"和"分级门禁"。
一、校准:一次回归,不是每月回归
不要指望建一张"分数→P75 映射表"(映射天然一对多,林寒山那条回答说得对)。要建的是单变量斜率,它才是可复用的常数:
- 每次发版打一条样本(CI 侧 + 上线后 7 天 RUM 侧):
- CI 侧:
lcp_ms(Lighthouse JSON 的audits["largest-contentful-paint"].displayValue,不是分数)、critical_js_kb(关键路径 JS:HTML 同步 script + 首屏必需 chunk 的 gzip 体积,用vite-plugin-visualizer或自写 rollup 插件按入口模块图算,不要用 total bundle) - RUM 侧:
rum_lcp_p75_ms,只取 7 天窗口 + 同设备分桶(把 4G/5G 和 WiFi 分桶后各自算 P75,再按流量占比加权),否则网络分布漂移会把斜率糊掉——你们"分数 75 对应 1.8s 或 2.5s"很可能一半就是这个没分桶。
- CI 侧:
- 攒 15~20 个发版样本后,跑一条 OLS:
rum_lcp_p75 = a + b × ci_lcp_ms。B 端页面 JS 主导、无图片瀑布流,b通常落在 0.8~1.3 之间(CI 的 4G throttle 模拟比多数用户真实网络还差一点,所以 b 略小于 1 是常见值)。 - 斜率 b 是常数,截距 a 是漂移项。以后每次校准只重算 a,b 半年复核一次即可。你们 6 个月找不到稳定映射,大概率是在用"分数"做 x 轴——分数是非线性压分的,斜率本身就是变的,怎么拟合都不稳。
二、预算反推:从业务目标倒着算
有了 b 和 a,预算的算法就变成一道减法:
- 业务目标:核心看板页
rum_lcp_p75 ≤ 2.2s(你们现在的基线上沿,先守住再谈优化) - 反推 CI 预算:
ci_lcp_budget = (2200 - a) / b,再乘 0.9 的安全系数(给 b 的置信区间留余量,用回归的 t 分布 95% 上界代替点估计更稳) - 落到 CI 门禁:
Lighthouse LCP displayValue > ci_lcp_budget → block
以 b=1.0、a=300ms 为例:预算 = (2200-300)/1.0 × 0.9 ≈ 1710ms。你们现在 70~85 分对应的 LCP 大概在 2.0~2.8s,这条线会卡住 +75KB 那个 PR(LCP 2.4s→2.8s 量级),但不会卡 12KB 日期选择器那种(LCP 动 <100ms,在噪声带里——见第三节的分级)。
三、分级门禁:硬卡 / 软卡 / 仅记录,解决"信任裂缝"
你们两个误伤/漏放案例,本质是用同一把尺子量了两种不同性质的增量。分级之后:
| 级别 | 指标 | 判据(示例,数字按你们回归结果套) | 动作 |
|---|---|---|---|
| 硬卡 | critical_js_kb 增量 | > 30KB | block,必须 PR 说明 |
| 硬卡 | lcp_ms 绝对值 | > 预算×1.15(约 1970ms) | block |
| 软卡 | lcp_ms 增量 | > 150ms 且 ≤ 预算 | 不 block,但必须贴 RUM 7 天对比数据 + 性能 owner review |
| 仅记录 | TBT、CLS、total bundle | 超阈值 | 出报告,进周会,不 block |
关键设计:
- 12KB 日期选择器案例:critical_js_kb +12KB < 30KB、LCP 增量大概率 <150ms → 走"仅记录",不再误伤,开发不会绕过。
- +75KB vendor 案例:critical_js_kb +75KB 直接硬卡 block——这类问题根本不需要 LCP 分数出场,体积硬卡比 LCP 分数可靠得多,因为体积是确定性的,LCP 分数有 ±4 的噪声。
- 150ms 的软卡线怎么定:取你们 CI 环境 LCP 的同页重复跑 3 次的标准差 × 3(我们量出来是 40~90ms,取 150ms 留余量),这是"噪声上界",低于它说明不了问题。
四、30 个页面不要一个预算
B 端 30 个页面里,核心看板、报表页、表单页的 LCP 结构完全不同(一个图片瀑布流、一个纯表格、一个重表单)。每页独立回归、独立预算,CI 里用 lighthouseci 的 assertions 按 URL pattern 分别配阈值,别用全局一条线——全局线必然对重页面过松、对轻页面过紧,这就是你们"又松又紧"的第三重来源。
五、避坑清单(我们踩过的)
- RUM 窗口取 7 天且剔除灰度期:发版后 48h 内的 RUM 混着老版本缓存数据,P75 会被拉低,别拿 3 天数据当"线上结果"。
- CDN 命中率进分桶:首访(CDN miss)和回访(hit)的 LCP 能差 500ms+,混在一起 b 会虚高。
- Lighthouse 重复跑取中位数:headless 环境单次跑噪声 ±4 分很正常,CI 里跑 3 次取 P50 再进门禁,成本 +2 分钟,误报率降一半。
- 门禁豁免要留痕:
--no-block必须带 reason + 到期日(我们要求 ≤2 周),否则"软卡"会慢慢烂成"没卡"。
一句话总结:分数用来给人看,ms 用来给机器判;预算锚在 RUM 业务目标上,用斜率 b 翻译成 CI 侧的 ms 线,再用"体积硬卡 + LCP 分级 + 分页面"三层结构把误伤和漏放分开治理。 你们 +75KB 那个漏放,任何一套分级体系里都会被 critical_js_kb 硬卡拦下——所以先上体积硬卡,校准回归可以慢慢攒样本。