先测再改,是因为改错的代价比测高。
一、他把首屏图片压了 40%,LCP 只动了 0.1 秒
2026 年 8 月的一个下午,他决定先修首屏。理由看起来很硬:首屏传输 412 KB,其中图片 61 KB,报告写着「可压缩约 40%」。他把首屏图片换成新格式,体积降了四成,重新上线,用同一台手机、同一张 4G 卡测:4.2 秒变成 4.1 秒。
以下情境为教学假设,人物、引语和数字不代表经核实的真实案例。
那份报告是按体积排序的,图片排第一,字体排第三。他按顺序做,是因为「先做最大的」听起来最合理;报告的排序依据是静态体积,不是动态耗时,这一点他当时没有注意。
他不信,又测了三次,三次都是 4.1 秒。压缩确实生效了:首屏传输从 412 KB 降到 387.6 KB,省下 24.4 KB。可 LCP 只挪了 0.1 秒。用比例算,这条路径上 1 KB 只值 0.004 秒;按这个汇率,把首屏所有图片都删掉也换不回 1 秒。
0.1 秒不是噪声。同一台设备、同一条网络、同一个账号,三次复测都是 4.1,重复性说明这个差值是真的。真实但没用,比测出噪声更让人难受,因为它证明力气花对了方向却花错了位置。
那天他在笔记里写下第一句真话:他测的是传输体积,优化的是传输体积,但用户感受到的是「内容出现的时间」。两个指标之间的换算关系,他一次都没有测过。
第二周他做了一件更慢的事:先不优化,只测量。他把 RUM、慢查询日志和一条合成测试接上,花了 5 小时,得到的第一份结论是「首页慢与数据库无关」——首页接口根本不在慢查询日志里,1,200 次查询全在项目列表那条路径上。
二、三个测量口径与各自的盲区
| 口径 | 采样方式 | 能回答的问题 | 盲区 |
|---|---|---|---|
| RUM p75 | 全量真实会话,每天 420 次浏览 | 哪条路径在真实网络与真实数据量下慢 | 慢到打不开的会话不上报,最差的一批天然缺失 |
| 慢查询日志 ≥ 500 ms | 数据库侧全量记录 | 哪条 SQL 在拖,重复了多少次 | 不知道这条 SQL 对哪个页面贡献多少 |
| 合成测试 | 每周 5 条路径各 1 次 | 改动前后同一条路径是否退化 | 固定环境与固定数据量,抓不到长尾 |
三个口径不是三选一,而是互相证伪。RUM 说「首页慢」,慢查询日志说「首页接口不在慢名单里」,合成测试说「我复现不出来」——三句话放在一起,才能把嫌疑从数据库挪到浏览器。
三个口径的接法很土:前端一小段上报脚本,在页面隐藏时把 LCP、路径名、网络类型和数据量发出去;后端在查询日志里加一行耗时;合成测试用无头浏览器跑固定脚本。没有采购,没有 SDK,也没有看板厂商。
RUM 的价值在于它是唯一带真实数据量的口径。实验室里一条空账号的列表页,和客户那条 100 个项目的列表页,是两条完全不同的路径。RUM 不告诉你原因,但它告诉你「谁在什么条件下慢」。
用 p75 而不是均值,是因为均值会被大量快会话拉平。p75 的读法是「每四个用户里,最慢的那一个有多慢」;预算要盯的正是这个人,而不是那个网络最好的自己。
慢查询日志的价值在于它不需要前端配合。阈值定在 500 ms,是因为低于这个数的查询即使重复一千次,也还能被缓存和索引吃掉;超过 500 ms 的查询,重复十次就足以让一个页面从「慢」变成「打不开」。500 ms 是排序线,不是及格线。
但它的盲区恰好是 N+1:列表页 1,200 次查询总耗时 8.0 秒,摊到每次是 8,000 ÷ 1,200 = 6.7 ms,没有一条越过 500 ms 的阈值。每一条都合格,合起来是 8 秒。日志干净只证明没有单条慢查询,不证明数据库没有问题。
合成测试的价值在于它可重复。它每周固定跑 5 条路径各 1 次,环境、数据、网络都一样,所以两次结果之间的差就是代码的差。它答不了「用户慢不慢」,只答「这次改动有没有让事情变坏」。
三个口径的读数要放在一张纸上对照。只看 RUM,你会去改最慢的页面;只看慢查询,你会去加索引;只看合成测试,你会以为一切正常。三张纸叠在一起,才能看出「用户慢在哪、代码慢在哪、哪一次改动让它变慢」。
三、一次误判的复盘:字体 1.8 秒与图片 61 KB
| 资源 | 体积 | 在 4.2 秒里的角色 | 压掉四成后的变化 |
|---|---|---|---|
| 首屏图片 | 61 KB | 传输 61 × 8 ÷ 1,600 = 0.305 s | 省 0.122 s |
| 字体 | 96 KB | 阻塞渲染 1.8 s | 未动,仍 1.8 s |
| JS | 214 KB | 阻塞资源合计 1.8 s 的一部分 | 未动 |
复盘是从 LCP 元素开始的。RUM 明细里,LCP 元素的 startTime 落在 4.2 秒的后段,而它前面有 1.8 秒没有任何内容上屏——那 1.8 秒不是在等图片,是在等字体。字体是渲染阻塞资源,没有加 font-display,也没有预加载,浏览器宁可空白也不肯用后备字体画第一屏。
图片那 61 KB 在 1.6 Mbps 下只需要 0.305 秒,压掉四成只省 0.122 秒。这 0.122 秒解释了他看到的 0.1 秒:不是压缩没生效,是这条路径上图片本来就不是瓶颈。把 1.8 秒的字体阻塞和 0.305 秒的图片传输放在一起,优先级不需要讨论。
字体那 96 KB 的传输时间只有 96 × 8 ÷ 1,600 = 0.48 秒,阻塞却有 1.8 秒。多出来的 1.32 秒不是传输,是浏览器在等字体、不敢先用后备字体画第一屏。体积账在这里彻底失效:同一个文件的「传输时间」和「阻塞时间」差了近四倍。
真正的动作是两件:字体子集化,96 KB 降到 24 KB;给首屏字体加预加载,让它在阻塞窗口开始前就到。阻塞从 1.8 秒降到 0.5 秒,LCP 从 4.1 秒降到 2.8 秒——单这一项就抵得上图片压缩的十三倍(1.3 ÷ 0.1)。剩下的 1.0 秒要靠 CSS、图片与渲染环节的其他动作,那是后一篇的账。
如果只按体积验收,子集化省下 96 − 24 = 72 KB,按 0.004 秒每 KB 的汇率只值 0.288 秒;实际这一项拿到的是 1.3 秒,差了四倍半。同一个动作,两种口径给出两种估值,选错口径就会在复盘时把功劳记到错误的地方。
这次误判的根因不是技术判断错,而是测量顺序错。他先看了「哪个文件大」,再决定改哪个;正确的顺序是先看「哪一段耗时最长」,再决定改哪个文件。文件大小是静态属性,耗时分段是动态事实,两者经常不一致。
复盘里他留了一条规则:任何优化上线前,先写下「我预期它让哪一段减少多少秒」。写不出来,说明还没测清楚;写出来但没发生,说明测错了。这条规则后来让他省掉了至少两次同类改动。
这也解释了为什么合成测试没有报警:合成环境的字体缓存是热的,1.8 秒的冷启动阻塞只在真实首访里出现。三个口径里最像「一切正常」的那个,恰恰漏掉了真因。
四、测量本身的成本:12,600 条、5.0 MB 与 5 小时
RUM 的成本比想象中低。每天 420 次页面浏览,30 天是 12,600 条;每条 0.4 KB,一个月 5,040 KB,约 5.0 MB。摊到每天是 168 KB,还不到首屏一次传输 412 KB 的一半,却能让「哪条路径慢」从猜测变成排序。
合成测试的成本是时间而不是存储:每周 5 条路径各 1 次,48 周 240 次。它不需要浏览器农场,一台机器串行跑就够;真正要花心思的是让它跑在和生产接近的数据量上,否则它只会告诉你「本地很快」。
5 小时诊断的分配是:RUM 上报与看板 2 小时,慢查询日志接进现有日志 1 小时,合成脚本 1.5 小时,读第一份数据并写下结论 0.5 小时。这 5 小时占全年 40 小时的 12.5%,却决定了剩下 35 小时花在哪里。
12,600 条里真正被用来做决定的只有很少一部分。真正起作用的不是「全量」,而是按路径分组之后能随时回头看:看到首页慢,就去翻首页那批会话里的慢样本;看到列表页慢,再去对慢查询日志。全量采集买的是回看能力。
5 小时里最贵的其实是最后 0.5 小时——把数据压缩成一句可执行的话:「首页慢在字体,不在图片。」这句话值 5 小时,因为它让后面 35 小时不必再猜。
复算:
图片:61 × 40% = 24.4 KB;61 − 24.4 = 36.6 KB
传输:61 × 8 ÷ 1,600 = 0.305 s;36.6 × 8 ÷ 1,600 = 0.183 s
差:0.305 − 0.183 = 0.122 s ≈ 0.1 s
首屏:412 − 24.4 = 387.6 KB
汇率:0.1 ÷ 24.4 = 0.004 s/KB
阻塞:1.8 − 0.5 = 1.3 s;LCP 4.1 − 1.3 = 2.8 s
阻塞占比:1.8 ÷ 4.2 = 42.9%
RUM:420 × 30 = 12,600 条/月;12,600 × 0.4 = 5,040 KB ≈ 5.0 MB
合成:5 × 48 = 240 次
诊断占比:5 ÷ 40 = 12.5%
复算里最有用的是「汇率」那一行:0.004 秒每 KB。理论汇率是 8 ÷ 1,600 = 0.005 秒每 KB,实测 0.004,差的那一点是测量粒度。它把「压图片」从直觉变成了算术——省 24 KB 最多换 0.12 秒。任何声称「压缩能让首屏快 1 秒」的建议,都可以先用这个汇率验一遍。
五、行动清单与复算验收
- 先把 RUM 接上,只采 LCP、路径名、网络类型与数据量四个字段。
- 给慢查询日志定一个 500 ms 阈值,先看一周,不改任何 SQL。
- 为五条关键路径各写一条合成测试,固定环境与数据量。
- 把三份读数放在同一张纸上,标出互相矛盾的结论。
- 任何优化前,写下预期减少的秒数与所在分段,写完再动手。
- 优化后用同一口径复测,把「预期」和「实际」并排记下来。
- 每月核对一次 RUM 体积,超过 10 MB 就减少采样字段而不是减少天数。
- 每季度重算一次「汇率」,网络与资源结构变了,汇率也会变。
验收标准:任取一次优化,都能说出它预期减少哪一段、实际减少多少、以及这个结论由哪个口径验证。
前四条是「把仪器装上」,后四条是「让仪器说真话」。如果只有一天,先做第一条和第三条;没有 RUM 与合成测试,后面的复盘只能靠记忆。
清单里最容易被跳过的是第五条。写下预期只要两分钟,但它把「改完看看」变成了可证伪的假设;没有这句话,一次失败的优化会被记成「有效但幅度不大」。
预期要写到秒,而不是写到方向。不是「提升首屏」,而是「阻塞从 1.8 秒降到 0.5 秒」;写不出秒数,就等于没有假设,改完只能靠感觉宣布成功。
使用边界与实操验收
三个口径都不能单独用。 RUM 缺最差样本,慢查询日志缺页面上下文,合成测试缺真实数据量。它们的组合不是流程洁癖,而是互相纠错的唯一办法。
RUM 要采样就采字段,不要采天数。 12,600 条是 30 天的全量;如果体积吃紧,先砍字段,因为「哪一天慢」比「慢在哪一段」更少被用到。
0.004 秒每 KB 是这条链路的汇率。 它由 1.6 Mbps 推出,换了网络、换了 CDN、开了压缩,汇率就会变,不能当成常数到处套。
测之前先清缓存。 他自己的浏览器缓存让第一次测量给出一个比真实值好得多的结论;后来固定用无痕窗口加同一张 4G 卡,才把变量压到只剩页面本身。
不要为测量引入重型工具。 一个人的产品,二十行上报加一行日志就够用;工具本身的接入、维护与学习时间都要算进那 40 小时里,否则测量会挤掉优化。
5 小时与全部数字是教学假设。 420 次浏览、0.4 KB、500 ms 阈值都取自本组统一基线,不构成对任何产品流量或日志成本的预测。
关键参考与核验范围
- MDN: PerformanceObserver :用于说明 RUM 侧如何订阅 LCP 等性能条目。
- MDN: Resource Timing API :用于核对单个资源的传输耗时如何取得,本文 0.305 秒的算法基于此。
- PostgreSQL: Using EXPLAIN :用于说明慢查询的定位入口,不用于推导 500 ms 阈值。
- Use The Index, Luke :用于核对索引与查询结构的基本关系,本文不涉及具体索引设计。
以上链接于 2026-10-26 实测可公开访问;未核验版本、功能范围与政策更新。核验日期:2026-10-26。
范围上只讨论一个人的测量口径、误判复盘与测量成本,不讨论 APM 厂商选型、分布式追踪与服务端 profiling 工具。
系列导航
- 上一篇:性能是一份预算:四项指标、二十格与四十小时
- 下一篇:首屏:412 KB 的去处与一次 1.8 秒的字体阻塞
- 回到目录:一个人的性能与账单:4.2 秒的首屏、一千二百次查询与每月 210 美元
- 相关阅读:最小监控:四个信号、五条探针与三分钟发现
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。