docs(传输): 补基线 jar 对照实验,作废旧的 45.9 秒基线与 52×/31× 口径

用 4eb67e2 单独构建基线镜像跑 A/B 两组:A 组把监听周期摁成 5 秒隔离 I/O 改动,
B 组用基线自带 60 秒周期量真实改前端到端。

实测:组包 10,055/10,622 ms → 881 ms(11.4×),解包 7,777 → 785 ms(9.9×),
端到端 123.1 s → 9.9 s。原 45,934 ms 基线不可复现,§2.1/§3.4/§10.3/§10.5
中据此推算的数字全部改正。B 组 123 秒正好复现客户「两分钟传不到内网」的说法,
且其中 102 秒是轮询等待——瓶颈主要是 5.1 而非组包。
This commit is contained in:
KodouWZF
2026-11-03 23:47:46 +08:00
parent 2c93c545b1
commit fa77c561c0

View File

@@ -1,8 +1,9 @@
# 跨网传输链路性能优化方案 # 跨网传输链路性能优化方案
> 版本:v1.3(2026-11-03) > 版本:v1.4(2026-11-03)
> 适用后端:s-file(alita-bjy-uygur) > 适用后端:s-file(alita-bjy-uygur)
> 状态:5.1/5.2/5.3/5.4/5.5 已实施,本地已完成一次真实端到端传输实测(§10.5)→ 待现场验证;5.6 视现场文件大小分布决定 > 状态:5.1/5.2/5.3/5.4/5.5 已实施;已完成真实端到端实测(§10.5)与基线 jar 对照实验(§10.6)→ 待现场验证;5.6 视现场文件大小分布决定
> **实测结论:端到端 123.1 s → 9.9 s(8–12×);组包 11.4×、解包 9.9×、包体 +3.63%。§2.1 的 45,934 ms 基线与 §3.4 原预测表已作废,一律以 §10.6 为准。**
> 实施与实测记录见第 10 节 > 实施与实测记录见第 10 节
> 关联代码:`TransferUserFileServiceImpl`(发送/组包)、`TransferFileMonitorServiceImpl`(接收/解包)、`TransferFileMonitorJob`(调度)、`FileServiceImpl` / `LocalFileStorage`(物理文件读写) > 关联代码:`TransferUserFileServiceImpl`(发送/组包)、`TransferFileMonitorServiceImpl`(接收/解包)、`TransferFileMonitorJob`(调度)、`FileServiceImpl` / `LocalFileStorage`(物理文件读写)
@@ -31,7 +32,9 @@
| userFileId=10 | 单个小文件 | 167 ms | | userFileId=10 | 单个小文件 | 167 ms |
| userFileId=12 | 单个小文件 | 332 ms | | userFileId=12 | 单个小文件 | 332 ms |
| userFileId=13 | 单个小文件 | 543 ms | | userFileId=13 | 单个小文件 | 543 ms |
| userFileId=31 | 文件夹,9 个文件,合计约 19.6 MB | **45,934 ms** | | userFileId=31 | 文件夹,9 个文件,合计约 19.6 MB | ~~**45,934 ms**~~ 见下方注 |
> **注(2026-11-03 对照实验后作废)**:45,934 ms 这个数字**不可复现**。§10.6 用同一份基线代码(`4eb67e2`)重测两次,组包 `useTime` 分别是 10,055 ms 和 10,622 ms,互差仅 5.6%。45.9 秒那次应是在冷缓存或有并发负载的状态下采的。**本节及 §2.2、§3.4 中所有以 45,934 ms / 2.33 s/MB 为基数的推算一律以 §10.6 的实测为准。**
**结论 1:小文件的组包本身就是几百毫秒,不慢。** **结论 1:小文件的组包本身就是几百毫秒,不慢。**
@@ -94,19 +97,27 @@ bind mount 若落在 Docker Desktop 文件共享(FUSE/9p)、NFS、SMB,或*
### 3.4 按模型预测的组包耗时 ### 3.4 按模型预测的组包耗时
| 文件大小 | 现状(2.33 s/MB) | 改 64 KB 缓冲后(约 0.075 s/MB) | 提升 | > **本节原表以 2.33 s/MB 为基数,已作废**(该基数来自不可复现的 45,934 ms,见 §2.1 注)。
|---|---|---|---| > 下表按 §10.6 对照实验的实测系数重算:改前 **0.524 s/MB**(18.7 MiB / 10,055 ms),改后 **0.046 s/MB**(18.7 MiB / 881 ms)。
| 100 KB(一页文档) | 0.23 s | 0.008 s | 29× |
| 10 MB | 23 s | 0.75 s | 31× |
| **50 MB** | **117 s(约 2 分钟)** | **3.8 s** | **31×** |
| 200 MB | 466 s(7.8 分钟) | 15 s | 31× |
| 500 MB | 1165 s(19 分钟) | 38 s | 31× |
| 1 GB | 2330 s(39 分钟) | 76 s | 31× |
改后系数 0.075 s/MB 的构成:I/O 约 0.035 s/MB(每 MB 只剩 32 次 syscall)+ 压缩 CPU 约 0.04 s/MB。 | 文件大小 | 改前组包 | 改后组包 | 提升 | 改前端到端(含平均 60 s 轮询) | 改后端到端(含平均 5 s 轮询) |
|---|---|---|---|---|---|
| 100 KB(一页文档) | 0.05 s | 0.005 s | 11× | ~60 s | ~5 s |
| 10 MB | 5.2 s | 0.46 s | 11× | ~66 s | ~6 s |
| **50 MB** | **26 s** | **2.3 s** | **11×** | **~88 s** | **~8 s** |
| 200 MB | 105 s | 9.2 s | 11× | ~167 s | ~15 s |
| 500 MB | 262 s | 23 s | 11× | ~324 s | ~29 s |
| 1 GB | 524 s | 46 s | 11× | ~586 s | ~52 s |
> **50 MB 的回答:现状约 2 分钟,改完约 4 秒。** 端到端还要叠加解包(改前 0.415 s/MB、改后 0.042 s/MB)与两程轮询等待(改前平均 30+30 s、最坏 60+60 s;改后平均 2.5+2.5 s、最坏 5+5 s)。上表的端到端列已含平均轮询与解包。
> 若再叠加 5.4 节(已压缩格式不二次压缩),50 MB 可降到约 2 秒。
> **50 MB 的回答(修正版):改前组包约 26 秒、端到端约 1.5 分钟(最坏 2.4 分钟);改后组包约 2.3 秒、端到端约 8 秒。**
>
> **关键修正:客户说的「两分钟」主要不是组包慢,是轮询等待慢。** 50 MB 场景下改前 88 秒里有 60 秒是两程轮询,只有 26 秒在组包。这解释了为什么 5.1(60 秒 → 5 秒)单独就能把端到端从 123 秒压到 21 秒(§10.6 B 组 vs A 组)。
> **系数适用范围**:0.524 / 0.046 s/MB 采自 99.94% 为已压缩格式(pptx/xlsx/png)的样本。
> 若内容以 txt/doc/源码为主,改前 level 6 的每 MB 成本更高、改后拿不到 level 0 收益,倍数会回落到 5.3 单独的 **8×** 量级,但改前的绝对秒数也会更大。
> 现场必须用第 4 节的方法按真实文件分布重测。
> >
> **重要:500 MB 以上的文件现状组包需要 19 分钟,而这段时间是占用在一个 HTTP 请求 + 一个数据库事务里的**,浏览器/nginx 早就超时了。这解释了大文件传输在界面上"点了没反应"的现象。 > **重要:500 MB 以上的文件现状组包需要 19 分钟,而这段时间是占用在一个 HTTP 请求 + 一个数据库事务里的**,浏览器/nginx 早就超时了。这解释了大文件传输在界面上"点了没反应"的现象。
@@ -406,17 +417,19 @@ CREATE INDEX idx_file_record_md5_code ON xs_file_record(md5_code);
## 9. 实施顺序与风险 ## 9. 实施顺序与风险
| 顺序 | 项 | 改动量 | 风险 | 收益(实测口径见 §10) | 状态 | | 顺序 | 项 | 改动量 | 风险 | 收益(应用侧实测口径见 §10.6) | 状态 |
|---|---|---|---|---|---| |---|---|---|---|---|---|
| 1 | 5.3 缓冲区 64 KB | 5 处,各 1 行 | 极低 | 组包 8.2× | 已实施 `4114c1f` | | 1 | 5.3 缓冲区 64 KB | 5 处,各 1 行 | 极低 | 组包 8.2×(容器内纯 I/O 基准) | 已实施 `4114c1f` |
| 2 | 5.1 监听周期 5 秒 | 配置 | 低 | 端到端省约 55 秒 | 已实施 `06fb35b` | | 2 | 5.1 监听周期 5 秒 | 配置 | 低 | 端到端 123.1 s → 21.0 s,省 102 s | 已实施 `06fb35b` |
| 3 | 5.5 md5 索引 | 4 个 SQL 文件 | 极低 | 多文件解包明显加速 | 已实施 `c5d19f4` | | 3 | 5.5 md5 索引 | 4 个 SQL 文件 | 极低 | 本次样本量太小未体现,需现场数据量验证 | 已实施 `c5d19f4` |
| 4 | 5.4 level 0 | 小 | 低 | 叠加后合计 17.6×,包体 +5.1% | 已实施 `456b1ea` | | 4 | 5.4 level 0 | 小 | 低 | 与 5.3 叠加后组包 11.4×、解包 9.9×,包体 +3.63% | 已实施 `456b1ea` |
| 5 | 5.2 时间预算 + 坏包隔离 | 中 | 中 | 高峰期吞吐一个数量级 | 已实施 `06852c5`(认领/并发未做,见 5.2) | | 5 | 5.2 时间预算 + 坏包隔离 | 中 | 中 | 高峰期吞吐一个数量级;本次单包场景未体现 | 已实施 `06852c5`(认领/并发未做,见 5.2) |
| 6 | 5.6 异步组包 | 大 | 中 | 仅超大文件场景需要 | 待现场文件大小分布决定 | | 6 | 5.6 异步组包 | 大 | 中 | 仅超大文件场景需要 | 待现场文件大小分布决定 |
1–5 已全部实施。**每一项单独提交**,现场出问题时可按提交号逐项回退定位。 1–5 已全部实施。**每一项单独提交**,现场出问题时可按提交号逐项回退定位。
**5.1 + 5.3 + 5.4 合计:端到端 123.1 s → 9.9 s(12.4×,本次实测;按平均轮询等待算约 7.9×)。现场汇报用 8–12× 区间。**
**每一项都必须单独验证并记录数字**,不要合并提交,否则现场出问题时无法定位是哪一项引起的。 **每一项都必须单独验证并记录数字**,不要合并提交,否则现场出问题时无法定位是哪一项引起的。
--- ---
@@ -485,10 +498,10 @@ CREATE INDEX idx_file_record_md5_code ON xs_file_record(md5_code);
| 项 | 预测 | 实测 | 说明 | | 项 | 预测 | 实测 | 说明 |
|---|---|---|---| |---|---|---|---|
| 每 MB 组包成本(旧) | 2.33 s/MB(应用日志基线) | **0.47 s/MB**(基准 old 模式) | 基线 45.9 秒那次是在不同负载/冷缓存下采的;基准跑前已有预热。两者都是真实的,差异来自环境状态 | | 每 MB 组包成本(旧) | 2.33 s/MB(应用日志基线) | **0.47 s/MB**(基准 old 模式)、**0.524 s/MB**(§10.6 应用侧 A 组实测) | 2.33 s/MB 来自不可复现的 45.9 秒那次,**已作废**。容器基准与应用侧对照实验两个独立测法给出 0.47 / 0.524,互差 11%,这才是可信的改前系数 |
| 提速倍数 | 31× | **8.2×**(level 6)/ **17.6×**(level 0) | 31× 是纯 I/O 的理论值。实测 new-l6 的 1022 ms 里约 500 ms 是 deflate CPU(因为 new-l0 只要 509 ms),缓冲改完后 CPU 占比升到一半,总倍数自然低于纯 I/O 倍数 | | 提速倍数 | 31× | **8.2×**(level 6)/ **17.6×**(level 0)/ **11.4×**(§10.6 应用侧端到端组包) | 31× 是纯 I/O 的理论值。应用侧 11.4× 落在 8.2× 与 17.6× 之间——因为应用侧组包含了 SQL、读原文件、元数据序列化这些不受缓冲区影响的固定开销,把倍数往 8.2× 那头拉 |
**结论:预测方向正确、量级偏高。** 现场汇报口径建议用实测的 **8 倍(仅改缓冲)/ 17 倍(含 level 0)**,不要用 31 倍。第 3.4 节的表格在现场按 4.2 的系数重算后再对外给数。 **结论:预测方向正确、量级偏高。** 现场汇报口径建议用 **11 倍(应用侧组包实测)/ 8 倍(仅改缓冲的下限)**,不要用 31 倍,也不要用 §10.5 一度写过的 52 倍。第 3.4 节的表格已按 §10.6 实测系数重算。
### 10.4 5.2 时间预算与坏包隔离 — 已实测通过 ### 10.4 5.2 时间预算与坏包隔离 — 已实测通过
@@ -547,13 +560,13 @@ CREATE INDEX idx_file_record_md5_code ON xs_file_record(md5_code);
#### 与基线对比 #### 与基线对比
组包 45,934 ms(§10.3 记录的改造前应用侧基线,同一文件夹)→ **881 ms,52×**。 见 §10.6 的对照实验。**本节原先写的「45,934 ms → 881 ms,52×」已作废**:那个 45,934 ms 是改造前在一次未知负载状态下测的,用同一份基线代码重测两次只有 10,055 ms 和 10,622 ms,**不可复现,弃用**。同条件下的真实倍数是 **11.4×**。
这个 52× 高于 §10.3 容器内基准测到的 17.6×,原因是**本次样本对 5.4 极度有利**:9 个文件里 pptx/xlsx/png 占 19,635,943 / 19,647,211 = **99.94%**,全部命中 level 0,deflate 几乎不消耗 CPU;包体 19,631,241 B 对原始 19,647,211 B 只有 0.08% 的缩减,正是「存原样」的预期表现。 包体:基线 level 6 产出 18,943,562 B,改后 level 0 产出 19,631,241 B,**+3.63%**(§10.3 容器基准估的是 +5.1%,实测更小)。样本里 pptx/xlsx/png 占 19,635,943 / 19,647,211 = 99.94%,全部命中 level 0,所以包体几乎不缩——这正是「存原样」的预期表现。
**汇报口径**:现场若以 pptx/图片/压缩包为主,可引用 50× 量级;若以 txt/doc/源码为主,只有 5.3 的缓冲区收益,应按 8× 量级说。不要用 52× 做通用承诺。 **汇报口径**:现场若以 pptx/图片/压缩包为主,组包按 **11× 量级**说;若以 txt/doc/源码为主,只有 5.3 的缓冲区收益,按 **8× 量级**说。不要引用 52×。
解包侧无改造前基线(§10.3 的基准只覆盖组包),本次 785 ms 是改后的绝对值。18.7 MiB / 9 文件 → 约 **42 ms/MB**,与组包侧 881 ms(47 ms/MB)同量级,符合对称预期。 解包侧 785 ms 是改后绝对值,改前对照见 §10.6(7,777 ms)。18.7 MiB / 9 文件 → 改后约 **42 ms/MB**,改前约 **415 ms/MB**。
#### 本次实测暴露的一个既有缺陷:包完整性校验空转 #### 本次实测暴露的一个既有缺陷:包完整性校验空转
@@ -577,7 +590,66 @@ CREATE INDEX idx_file_record_md5_code ON xs_file_record(md5_code);
改完后 §10.5 表格里 ③′ 的 87 ms 校验段才会真正开始比对字节数,届时需重测该段(预期增量很小:size 来自 ZIP 中央目录,`ZipFile` 已经打开过一次)。 改完后 §10.5 表格里 ③′ 的 87 ms 校验段才会真正开始比对字节数,届时需重测该段(预期增量很小:size 来自 ZIP 中央目录,`ZipFile` 已经打开过一次)。
### 10.6 尚未验证的部分 ### 10.6 对照实验:基线 jar 实测「改前」(2026-11-03 23:35–23:42)
§10.5 只有改后的绝对值,「改前」一直是推算。为坐实,用 `git worktree` 在 `4eb67e2`(所有优化提交之前的最后一个提交)单独构建了一份基线 jar,打成 `sfile-app:baseline` 镜像,通过 compose override 只替换 `app-iw` / `app-ow` 两个服务,数据库、配置、摆渡目录、发送内容全部不动。跑完 `docker compose up -d --force-recreate` 还原为 `sfile-app:local`。
发送内容与 §10.5 完全一致:同一个文件夹 userFileId=31,同样 9 个文件通过检测,原始 19,647,211 B。
跑了两组,用来分离两个变量:
- **A 组**:基线 jar + `FILE_TRANSFER_MONITOR_CRON=0/5 * * * * ?`,把监听周期摁成和优化版一样的 5 秒 → 差异只来自 5.3/5.4/5.5/5.2(I/O 与逻辑)
- **B 组**:基线 jar + 基线自带的 60 秒周期(不设环境变量)→ 真实「改前」端到端
#### 实测数据
| 段 | B 组 改前(60s) | A 组 改前(5s) | 改后(5s) §10.5 |
|---|---|---|---|
| ① 内网组包 `useTime` | 10,622 ms | 10,055 ms | **881 ms** |
| 包体 | 18,943,562 B (level 6) | 18,943,562 B | 19,631,241 B (level 0) |
| ② 去程轮询等待 | 50.3 s | 1.09 s | 3.87 s |
| ③ 外网大小校验段 | 81 ms | 173 ms | 87 ms |
| ③ 外网解包入库 | 7,728 ms | 7,777 ms | **785 ms** |
| ③′ 外网整轮 job | 7,940 ms | 8,200 ms | **1001 ms** |
| ④ 回程轮询等待 | 52.1 s | 1.83 s | 4.04 s |
| ④ 回程处理 | 12 ms | 9 ms | 21 ms |
| **端到端** | **123.1 s** | **21.0 s** | **9.9 s** |
#### 结论
**I/O 改动(5.3 + 5.4)的净效果**,A 组 vs 改后,周期相同:
- 组包 10,055 → 881 ms = **11.4×**
- 解包入库 7,777 → 785 ms = **9.9×**
- 接收侧整轮 8,200 → 1,001 ms = **8.2×**
- 代价:包体 +3.63%
两次基线组包(10,055 / 10,622 ms)互相只差 5.6%,说明这个数字是稳的;**§10.3 记的 45,934 ms 应用侧基线不可复现,已作废**。11.4× 也落在 §10.3 容器内基准的 8.2×(纯缓冲区)与 17.6×(叠加 level 0)之间,三者互相印证。
**周期改动(5.1)的净效果**,B 组 vs A 组,代码相同:端到端 123.1 → 21.0 s,省下的 102 s 全是轮询等待。B 组这次两程都撞上刚错过的分钟边界(50.3 s + 52.1 s),接近最坏的 120 s;平均情况应为 30 + 30 = 60 s,即端到端约 78 s。
**端到端总提升**:
- 按本次实测:123.1 → 9.9 s = **12.4×**
- 按平均轮询等待:约 78 → 9.9 s = **7.9×**
- 现场汇报建议用 **8–12×** 这个区间,不要给单点值——轮询等待占比随文件大小变化,小文件时 5.1 几乎是全部收益,大文件时 5.3/5.4 占比上升。
**B 组的 123 秒正好落在客户说的「一页文件两分钟都传不到内网」上。** 且客户那边走光闸,摆渡本身还要额外加时间,现场只会比 123 秒更慢。这条可以作为「问题已定位并复现」的证据。
#### 顺带复现了 §10.4 修掉的日志副作用
A 组(基线 jar + 5 秒周期)在包还在写的时候就轮询到了它,外网侧连续打出 2 条
`ERROR ... 文件大小校验失败: 1_65.zip` 带完整堆栈。这正是 §10.4 降为 debug 的那个问题——
基线在 60 秒周期下每个在途文件刷 5 条/5 分钟,改 5 秒后会变成 60 条/5 分钟。对照实验直接把改前状态复现了出来。
#### 实验残留
- 镜像 `sfile-app:baseline`(834 MB)保留着,方便后续复测;不需要时 `docker rmi sfile-app:baseline` 清掉
- `sfile-app:optimized` 是 `sfile-app:local` 的同 ID 别名,纯保险,可随时删
- worktree `D:/dev/baseline-4eb67e2` 已 `git worktree remove` 清理
- override 文件在 `D:/dev/ab-test/baseline-{5s,60s}.yml`(仓库外,未提交)
### 10.7 尚未验证的部分
- [ ] **外传内方向**:本次只跑了内穿外,回程方向(外网组包 → 内网解包)未实测 - [ ] **外传内方向**:本次只跑了内穿外,回程方向(外网组包 → 内网解包)未实测
- [ ] **现场系数**:客户环境是 Linux + 光闸监控目录,syscall 单价与本地 Docker Desktop 不同,必须按第 4 节重测 - [ ] **现场系数**:客户环境是 Linux + 光闸监控目录,syscall 单价与本地 Docker Desktop 不同,必须按第 4 节重测