Skip to content

Commit 5092b3d

Browse files
committed
Merge branch 'dev': 诊断经验沉淀
2 parents dedc67e + 434847f commit 5092b3d

3 files changed

Lines changed: 95 additions & 3 deletions

File tree

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
# 知识卡片 · 踩坑记录
2+
3+
## 基本信息
4+
5+
| 字段 | 内容 |
6+
|------|------|
7+
| 标题 | 下载慢诊断中的三次误判:对照实验缺失与观测行为污染测量 |
8+
| 日期 | 2026-07-31 |
9+
| 类型 | 踩坑记录 |
10+
| 标签 | #性能诊断 #CDN #测量方法 #对照实验 #复盘 |
11+
12+
---
13+
14+
## 症状
15+
16+
官网安装包(172MB)下载极慢,浏览器单连接约 140KB/s,用户需等约 20 分钟。目标是定位瓶颈并提速。
17+
18+
真实根因(最终结论):**CDN 对大文件按分片缓存,未预热时首批请求实时回源,被源站带宽拖垮**。已缓存分片实测可达 12–38MB/s,未缓存分片仅约 110KB/s。
19+
20+
但抵达该结论前经历了三次误判,每次都被下一步证据推翻。**本卡片的价值不在结论,而在这三次误判的成因。**
21+
22+
## 环境
23+
24+
| 项目 | 信息 |
25+
|------|------|
26+
| 源站 | 阿里云 ECS + Nginx(`/downloads/` 静态托管) |
27+
| 加速 | 阿里云 CDN(大文件下载加速,回源 ECS) |
28+
| 客户端 | Windows,RTT 至源站 21ms、至 CDN 节点 5ms |
29+
| 文件 | 172MB `.exe` 安装包 |
30+
31+
## 三次误判与推翻过程
32+
33+
### 误判一:「ECS 出网带宽约 4.4Mbps 封顶」
34+
35+
- **依据**:单连接 143KB/s、3 并发 551KB/s,推断 551KB/s 即带宽上限。
36+
- **推翻**:后续 8 并发达 768KB/s,说明 551 并非上限。
37+
- **成因**:**用少量样本点外推容量上限**。并发数不足时得到的吞吐不能代表带宽天花板。
38+
39+
### 误判二:「单流劣化源于跨网丢包,BBR 可解」
40+
41+
- **依据**:单流仅占多流总吞吐的 1/5,符合丢包导致的 TCP 单流劣化特征;且上游有相似结论。
42+
- **推翻**:在生产启用 BBR 后**单连接速率毫无改善**(143→133KB/s);且实测 RTT 仅 21ms,不存在高延迟。
43+
- **成因**:**把上游 issue 的相似结论当成本案结论**。改完仍失败时应立即放弃该假设,而非继续在错误方向加码。
44+
- **补救**:被证伪的 Defender/BBR 相关注释已按实测改写,避免留下错误认知。
45+
46+
### 误判三:「瓶颈在客户端本地网络」
47+
48+
- **依据**:CDN 与 ECS 的单连接速率几乎相同(145 vs 110KB/s)、8 并发也几乎相同(773 vs 768KB/s),两者共同上限暗示瓶颈在客户端。
49+
- **推翻**:同一客户端测第三方源(npmmirror CDN)单连接达 **23.8MB/s**,客户端能力高出 160 倍。
50+
- **成因**:**缺少对照实验**。只在自己的两个服务间比较,无法区分「两端都慢」与「客户端封顶」。
51+
52+
### 额外陷阱:对照实验本身一度产生虚假数据
53+
54+
首次对照测得「8 并发 3316KB/s」,实为**无效数据**:目标 URL 返回 302,而 `curl` 未加 `-L` 不跟随重定向,实际只下载了 97 字节的重定向响应,耗时几乎全是进程启动开销。加 `-L` 后才得到真实的 23.8MB/s。
55+
56+
### 最后一个陷阱:观测行为污染了被观测对象
57+
58+
为判断预热是否覆盖全文件,用小 Range 请求(1KB)探测各偏移点的 `X-Cache`,结果**全部显示 HIT**,据此以为预热完成。但随后大范围下载仍只有 224KB/s。
59+
60+
原因:**小 Range 探测请求本身就会导致该分片被拉取并缓存**,于是探测后必然显示 HIT——HIT 是探测造成的,而非预热造成的。
61+
62+
正确判据只能是**终端指标**:不同请求量下的实际下载速率。该指标清楚显示 1MB→12.9MB/s、5MB→38.3MB/s、20MB→224KB/s,即缓存只覆盖了前若干 MB。
63+
64+
## 根因
65+
66+
阿里云 CDN 对大文件采用分片缓存,仅被请求过的分片进入缓存。未预热时首批用户实时回源,速度等同直连源站——**接了 CDN 却毫无效果**。
67+
68+
## 解决方案
69+
70+
1. CDN 侧执行**预热**(`PushObjectCache`),把整个安装包推到边缘节点;
71+
2. 在 `release.yml` 中**自动化预热**,每次发版同步完安装包后调用预热接口,避免新版本首批用户仍走回源;
72+
3. 预热对象仅需 `.exe` 与 `.blockmap`;`latest.yml/json` 按设计不缓存且体积极小。
73+
74+
详见技术方案:[安装包分发加速:CDN 接入方案与下载源可切换设计](../solutions/2026-07-installer-cdn-distribution.md)
75+
76+
## 教训
77+
78+
- **性能诊断必须有对照实验**:只测自己的服务无法区分「服务端慢」与「客户端/链路慢」。用同一客户端测一个已知高速的第三方源,一次请求即可排除整类因素——这一步若提前做,可直接跳过误判二和三。
79+
- **警惕观测行为改变被观测对象**:缓存命中状态、连接数、预取行为等都可能被探测请求本身影响。判断应基于**终端指标**(用户实际感受到的速率),而非中间态指示器。
80+
- **对照实验也要验证自身有效性**:留意重定向(`curl -L`)、超时截断(`--max-time` 导致只下载了部分数据)、以及"速率"是否由真实数据量算出。
81+
- **上游相似结论只是候选假设**:按其修改后若无改善,应立即放弃并回到证据,同时清理被证伪的改动及其注释,避免给后人留下错误认知。
82+
- **少量样本不能外推容量上限**:并发不足时的吞吐不代表带宽天花板。
83+
- **分层排除优于猜测**:客户端 → 链路 → CDN 边缘 → 回源 → 源站,逐层用可否证的实验切分,比依据现象"像什么"去猜快得多。
84+
85+
## 参考
86+
87+
- 通用误区已补充至 [Debug 标准操作流程 · 常见误区](../../standards/debug-sop.md)
88+
- 相关实现:`.github/workflows/release.yml`(自动预热)、`server/nginx/nginx.conf`(`/downloads/` 限速与并发)

‎docs/knowledge/index.md‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
| 日期 | 标题 | 标签 |
88
|------|------|------|
9+
| 2026-07-31 | [下载慢诊断中的三次误判:对照实验缺失与观测行为污染测量](./bugs/2026-07-download-slow-misdiagnosis.md) | #性能诊断 #CDN #测量方法 #对照实验 #复盘 |
910
| 2026-07-30 | [Git LFS 图标未在 CI 拉取致 electron-builder 打包报 `ERR_ELECTRON_BUILDER_CANNOT_EXECUTE`](./bugs/2026-07-git-lfs-icon-electron-builder-ci-failure.md) | #CI #GitLFS #electron-builder #GitHubActions #发布 |
1011
| 2026-07-30 | [reduced-motion 用 `animation-play-state: paused` 冻结入场动画致内容不可见](./bugs/2026-07-reduced-motion-fade-in-up-invisible.md) | #CSS #a11y #reduced-motion #animation |
1112

@@ -23,6 +24,6 @@ _(暂无)_
2324

2425
## 标签速查
2526

26-
- **技术**:#CSS #a11y #React #Vite #Tailwind #CI #GitLFS #electron-builder #GitHubActions
27-
- **类型**:#bug #方案 #学习
28-
- **模块**:#启动仪式 #reduced-motion #animation #发布 #安装包
27+
- **技术**:#CSS #a11y #React #Vite #Tailwind #CI #GitLFS #electron-builder #GitHubActions #CDN #阿里云
28+
- **类型**:#bug #方案 #学习 #复盘
29+
- **模块**:#启动仪式 #reduced-motion #animation #发布 #安装包 #性能诊断 #测量方法

‎docs/standards/debug-sop.md‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,9 @@ git bisect reset # 结束
173173
| 修完就忘了 | 记录下来,避免重复踩坑 |
174174
| 一次改太多 | 最小改动,一次修一个问题 |
175175
| 忽略"偶现"问题 | 偶现往往是并发/竞态,更严重 |
176+
| 根据上游 issue 的相似结论直接下结论 | 那只是候选假设;按其修改后仍失败就应**立即放弃**并回到堆栈证据,同时清除被证伪的修改与其误导性注释 |
177+
| 性能问题只测自己的服务就断定服务端慢 | 必须做**对照实验**(同一客户端测一个已知高速的第三方源)以排除客户端/链路因素;对照时注意重定向(curl 需 `-L`)否则只测到几十字节的 301/302 响应,得出虚假数字 |
178+
| 用探测请求查看缓存命中状态并据此下结论 | 探测本身可能**改变被观测对象**(如小 Range 请求会使该分片被缓存,之后总显示 HIT);应以**终端指标**(如大范围实际下载速率)作为判据 |
176179

177180
## 相关文档
178181

0 commit comments

Comments
 (0)