一个向浏览器分发 5.5 MB Python 运行时的团队发现,在最近的一个 sprint 中记录的错误里,有 69% 都属于同一个具有误导性的标题,而其中 89% 实际上都是网络超时。这种错误的报告方式误导了开发人员的调试方向,并导致大量用户遭遇静默下载失败——任何打包了大型资产的 Web 应用都可能很快遇到这个问题。

仪表板产生了误导

错误追踪系统会自动根据错误首次出现的代码位置对事件进行分组。生成的标题看起来像是运行时加载器(runtime loader)中的一个简单 Bug,因此整个 sprint 的时间都花在了寻找那些从未发生过超时的代码路径上。当团队抽样查看底层元数据时,真相才浮出水面:大多数失败根本不是 Bug,而是触发了超时的停滞网络连接。

启示: 错误标题是为了方便,而非诊断。应定期深入挖掘原始数据,以验证标题实际代表的内容。

浏览器连接 API 提供的是占位符

为了避免让网络慢的用户经历漫长的 5.5 MB 下载,开发人员查阅了浏览器的 Network Information API (navigator.connection)。该 API 为每一位首次访问的用户都报告了恒定的 1.7 Mbps 带宽。

当浏览器对新用户没有历史数据时,会发出一个默认值。这个默认值只是一个提示,而非确切的速度。当每个新会话都出现相同的占位符时,这表明该 API 尚未针对该受众群体完成校准。

启示: 对于任何从不变化的网络信号,应将其视为兜底方案,而非确切的指标。

单次快照是不可靠的

在丢弃了不可靠的带宽提示后,团队转向了另一种在他们的测试套件中似乎有效的信号。一次测试运行通过了,但连续重复三次测试,每次都会失败。网络速度是持续波动的。之前的代码只是获取了一个单次快照,做出了永久性的决定,然后即使连接在片刻之后发生变化,也会继续执行。

启示: 不要基于对移动目标的单次读取来做出永久性的决策。应该订阅变更事件,而不是进行单次轮询。

团队实施的实际修复方案

  • 订阅连接变更。 代码不再只是读取一次 navigator.connection,而是现在监听 change 事件,并在下载过程中带宽下降或上升时做出反应。
  • 添加“无进度”看门狗。 设置一个定时器,如果某个请求在短时间内没有取得任何进展,则中止该请求,从而让浏览器能够重试或执行回退方案。
  • 停止在下载中途切换 CDN。 在慢速链路上切换大文件的来源会导致传输从零开始,浪费已经接收到的字节。现在的下载会在整个过程中坚持使用最初选择的 CDN。
  • 推迟繁重的缓存工作。 将向缓存写入大量数据的任务推迟到运行时加载完成后再进行,从而缩短关键路径。

如果你的仪表板呈现出一幅异常整洁的图景,请深入挖掘。如果网络测量值从未变动,请将其视为占位符。如果仅凭一次快照就决定了一个数兆字节下载的命运,那你就是在赌一场幻觉。这些赌注最终会表现为侵蚀用户信任的静默失败——这种损失在事后是任何巧妙的代码都无法完全弥补的。