diff --git a/backend/docs/传输链路性能优化方案.md b/backend/docs/传输链路性能优化方案.md index 9d833b3..2bf71e1 100644 --- a/backend/docs/传输链路性能优化方案.md +++ b/backend/docs/传输链路性能优化方案.md @@ -1,8 +1,9 @@ # 跨网传输链路性能优化方案 -> 版本:v1.3(2026-11-03) +> 版本:v1.4(2026-11-03) > 适用后端: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 节 > 关联代码:`TransferUserFileServiceImpl`(发送/组包)、`TransferFileMonitorServiceImpl`(接收/解包)、`TransferFileMonitorJob`(调度)、`FileServiceImpl` / `LocalFileStorage`(物理文件读写) @@ -31,7 +32,9 @@ | userFileId=10 | 单个小文件 | 167 ms | | userFileId=12 | 单个小文件 | 332 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:小文件的组包本身就是几百毫秒,不慢。** @@ -94,19 +97,27 @@ bind mount 若落在 Docker Desktop 文件共享(FUSE/9p)、NFS、SMB,或* ### 3.4 按模型预测的组包耗时 -| 文件大小 | 现状(2.33 s/MB) | 改 64 KB 缓冲后(约 0.075 s/MB) | 提升 | -|---|---|---|---| -| 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× | +> **本节原表以 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)。 -改后系数 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 秒。** -> 若再叠加 5.4 节(已压缩格式不二次压缩),50 MB 可降到约 2 秒。 +端到端还要叠加解包(改前 0.415 s/MB、改后 0.042 s/MB)与两程轮询等待(改前平均 30+30 s、最坏 60+60 s;改后平均 2.5+2.5 s、最坏 5+5 s)。上表的端到端列已含平均轮询与解包。 + +> **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 早就超时了。这解释了大文件传输在界面上"点了没反应"的现象。 @@ -406,17 +417,19 @@ CREATE INDEX idx_file_record_md5_code ON xs_file_record(md5_code); ## 9. 实施顺序与风险 -| 顺序 | 项 | 改动量 | 风险 | 收益(实测口径见 §10) | 状态 | +| 顺序 | 项 | 改动量 | 风险 | 收益(应用侧实测口径见 §10.6) | 状态 | |---|---|---|---|---|---| -| 1 | 5.3 缓冲区 64 KB | 5 处,各 1 行 | 极低 | 组包 8.2× | 已实施 `4114c1f` | -| 2 | 5.1 监听周期 5 秒 | 配置 | 低 | 端到端省约 55 秒 | 已实施 `06fb35b` | -| 3 | 5.5 md5 索引 | 4 个 SQL 文件 | 极低 | 多文件解包明显加速 | 已实施 `c5d19f4` | -| 4 | 5.4 level 0 | 小 | 低 | 叠加后合计 17.6×,包体 +5.1% | 已实施 `456b1ea` | -| 5 | 5.2 时间预算 + 坏包隔离 | 中 | 中 | 高峰期吞吐一个数量级 | 已实施 `06852c5`(认领/并发未做,见 5.2) | +| 1 | 5.3 缓冲区 64 KB | 5 处,各 1 行 | 极低 | 组包 8.2×(容器内纯 I/O 基准) | 已实施 `4114c1f` | +| 2 | 5.1 监听周期 5 秒 | 配置 | 低 | 端到端 123.1 s → 21.0 s,省 102 s | 已实施 `06fb35b` | +| 3 | 5.5 md5 索引 | 4 个 SQL 文件 | 极低 | 本次样本量太小未体现,需现场数据量验证 | 已实施 `c5d19f4` | +| 4 | 5.4 level 0 | 小 | 低 | 与 5.3 叠加后组包 11.4×、解包 9.9×,包体 +3.63% | 已实施 `456b1ea` | +| 5 | 5.2 时间预算 + 坏包隔离 | 中 | 中 | 高峰期吞吐一个数量级;本次单包场景未体现 | 已实施 `06852c5`(认领/并发未做,见 5.2) | | 6 | 5.6 异步组包 | 大 | 中 | 仅超大文件场景需要 | 待现场文件大小分布决定 | 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 秒那次是在不同负载/冷缓存下采的;基准跑前已有预热。两者都是真实的,差异来自环境状态 | -| 提速倍数 | 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 倍数 | +| 每 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)/ **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 时间预算与坏包隔离 — 已实测通过 @@ -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.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 节重测