# 上传批次实时监控 + 「CPU 空转」真身定位（io_uring SQPOLL）

- 日期：2026-09-12 22:16 ~ 22:30
- 对象：本机下载中心 8899，上传批次：`用户上传/models/Stable-diffusion/**`（45 个模型）
- 触发：用户「我发起了新的上传，监控一下」
- 相关前置：《2026-09-12-上传并发卡死根因与修复.md》《2026-09-12-僵死半开连接治理与卡死现场取证.md》

---

## 一、监控概况（22:30）

| 指标 | 数值 |
|---|---|
| 会话 | 45 个（延续原批次，断点续传） |
| 已收 / 目标 | **66.99GB / 139.7GB = 48.0%**（剩余 72.7GB） |
| 实测吞吐 | 2.9 ~ 3.9 MB/s（6 条并发连接） |
| 总体 ETA | 按当前速率约 **5.5 小时** |
| 已完成落盘 | 13 个模型（本次会话中尚无新完成，见第二节说明） |
| 服务状态 | `active`，RSS 80~85MB **稳定无泄漏**，`staleUploads=0`，无 `mergeActive` 堆积 |
| 日志告警 | 仅 1 条（此前测试产生的僵死连接清理），**无事件循环阻塞告警、无 fatal** |
| 队列推进 | 150 秒内 10 个文件新进入上传，180 秒内 10 个文件各 +64MB → **确在推进，无死锁** |

### 上传行为特征（值得知道）
浏览器仍在跑**旧版前端**，它把 45 个文件的分块请求一起丢进连接池，Chrome 用 6 条连接 **FIFO 轮转**：每轮每个文件只推 1 个 64MB 分块。
后果：**所有文件齐头并进（都卡在 ~22~26 块），要到队列尾部才会集中完成**，中途看不到「一个个完成」。新版前端（刷新后生效）同时只传 3 个文件，会**逐个完成**，失败也只影响单个文件。

## 二、重大发现：CPU「空转」的真身是 io_uring SQPOLL（不是连接）

监控中发现进程 **单核占用 ~100%（整机 12%）**，而吞吐只有 3MB/s。逐层定位：

1. **用户态 vs 内核态**：utime 12%，**stime 99%** → 不是 JS 空转，是内核态。
2. **存储栈排查**：`/home` 为普通 ext4（`/dev/sda5`），**无加密、无 FUSE、无压缩**；排除文件系统开销。
3. **是否有分块被反复重写**：盯住单个 `.part` 逐秒采样 30 次，**大小只增不减、无截断**，`cancelled_write_bytes` 30 秒零增长 → 排除重复/截断重写。
4. **逐线程归因（决定性）**：
   ```
   2491739 2491748 iou-sqp-2491739  58.7  00:08:57  Ssl
   ```
   10 秒窗口内该线程吃掉 **545 tick = 54.5% 单核**，占进程总 CPU 的 **82%**，累计已达 **约 10 分钟** CPU 时间。
   `iou-sqp-*` = **io_uring 的 SQPOLL 忙轮询线程**；本机内核 `6.6.143-amd64-desktop-hwe`，libuv 默认启用了 io_uring 的 SQPOLL 提交队列轮询，文件流式写入期间该线程持续忙等。
5. **A/B 验证**：同一 Node 二进制跑异步写测试 ——
   - 默认：进程内出现 `iou-sqp-*` 线程；
   - `UV_USE_IO_URING=0`：**`iou-sqp` 线程完全消失**（回落线程池）。

### 与历史故障的对应关系（修正之前的判断）
- 「CPU ~94% 且连接零吞吐」中，**94% 这个数字主要由 io_uring SQPOLL 贡献**（它与连接数无关，只要有异步文件 IO 就持续烧约 1 个核，即使上传其实已暂停）；
- 而「连接零吞吐 / 卡死」仍主要由**同步合并阻塞事件循环**解释（已在上轮改为流式异步）；
- 两者叠加，才形成「连接都在、全都没吞吐、CPU 却打满」的观感。**结论：那不是连接造成的，是内核态轮询 + 事件循环被独占。**

### 修复（已装载，待重启生效）
`/etc/systemd/system/dl-server.service` 增加：
```ini
Environment=UV_USE_IO_URING=0
Environment=UV_THREADPOOL_SIZE=8
```
- `UV_USE_IO_URING=0`：关掉 io_uring，改走线程池 → 消除 SQPOLL 忙轮询（预计省下约 1 个核）；
- `UV_THREADPOOL_SIZE=8`：默认 4 线程，多路上传+合并时异步 fs 不再排队。

⚠️ **本次没有重启服务**：重启会中断当前 6 条在传分块，你的上传行会报错需重新选择（数据不丢，断点续传）。该配置**已在下次重启 / 服务崩溃自动拉起 / 开机时生效**。

## 三、建议

1. **CPU 修复**：想立刻生效就说一声，我重启服务（代价：当前上传行需重新选择一次，进度全部保留）；否则让它自然在下次重启生效，不影响上传。
2. **刷新上传页面**：可立即获得 ①已完成的 13 个模型**秒跳**（不再重传）②同时只传 3 个文件、逐个完成 ③单文件失败不拖累其他文件。代价同样是当前行需重新选择。
   - 证据：页面仍在跑旧版 —— 6 条并发上传连接（新版上限 3），且 `models/` 下的占位文件 `Put VAE here.txt` 等被**重新写了一遍**（新版同名同大小会跳过）。
3. **吞吐瓶颈 = 无线链路（此处 22:45 更正原判断）**：
   - 原判断「走有线 `enp3s0f1`」是**错的** —— 那是依据 `ip route get`（只看路由表对新连接的选路），实际已建立的连接走的是**无线 `wlp4s0`**。
   - 实测（22:45）：`wlp4s0` 接收 **3.82 MB/s**，`enp3s0f1` 接收 **0.02 MB/s**；上传连接本端地址为 **192.168.31.76（无线 IP）**。
   - 根因：下载中心是用 **192.168.31.76**（无线地址）访问的，所以流量只走无线。
   - 链路质量对比（同一客户端 192.168.31.110）：
     | 出口 | 丢包 | RTT |
     |---|---|---|
     | 有线 `enp3s0f1`（192.168.31.114，1000Mb/s） | **0%** | **1.3ms** |
     | 无线 `wlp4s0`（192.168.31.76） | **66.7%** | **2925ms** |
   - TCP 侧印证：`rcv_ooopack` 高达 1.5 万+（大量乱序包）、`delivery_rate` 仅 0.83~1.1 Mbps/连接。
   - **建议改用有线地址 `http://192.168.31.114:8899`**（并让客户端走有线），预期提速幅度很大。
   - 附带隐患：本机 **两个网卡同处 192.168.31.0/24**（有线 192.168.31.114 / 无线 192.168.31.76），属于双归属同网段配置，容易出现 ARP 抖动与路径切换（客户端在有线/无线间漫游时旧连接不释放 → 正是此前「ESTAB 但零吞吐」的成因之一）。
4. **`wd-radiance-fp16.safetensors`** 停在 7/39 块已 4.5 小时（旧批次崩溃时的残留行），需要重新选择该文件才会继续。

## 四、修复前后 A/B 实测（22:32 重启生效，监控日志天然跨重启）

同一份 40 条采样日志（每 30s 一条）恰好横跨修复重启，构成同源对照：

| 指标 | 修复前（io_uring 开启）22:16:38~22:32:09 | 修复后（`UV_USE_IO_URING=0`）22:32:39~22:36:10 |
|---|---|---|
| 吞吐 | 3.21 MB/s | **3.44 MB/s（+7%）** |
| 进程 CPU | `ps %cpu` 累计均值 21.3% → 76.6%，**瞬时实测 106.7% 单核**（内核态占 99%） | **27% 单核**（内核态仅 10%） |
| `iou-sqp` 线程 | 1 个（累计 9 分 57 秒 CPU，占进程 CPU 的 82%） | **0 个** |
| RSS | 85MB | 67MB（新进程） |
| 传输量 | 2.92GB | 0.71GB（窗口更短） |

**结论：白赚回约 0.73 个核，吞吐不降反升。** 压测（640MB 分块上传+合并）1.1 秒完成、约 600MB/s，说明关掉 io_uring 对本场景没有性能损失。

## 四点五、改走有线之后：瓶颈转移到磁盘（22:57 实测）

用户改用**有线地址 `http://192.168.31.114:8899`** 后，网络侧立竿见影：

| 指标 | 无线（.76） | 有线（.114） |
|---|---|---|
| 网卡接收速率 | 3.8~4.2 MB/s（30~35 Mbps） | **65.5 MB/s（549 Mbps）** |
| 上传连接本端地址 | 192.168.31.76 | 192.168.31.114 |
| 实测落盘速率 | 3.6~4.0 MB/s | **40.6 MB/s** |

**网络提速约 16 倍后，新的瓶颈是这台机器的机械硬盘**（直接 I/O 绕过页缓存实测，`rotational=1`）：

| 直接 I/O 测试 | 实测 |
|---|---|
| 单路顺序读 | 54.6 MB/s |
| 单路顺序写 | **23.0 MB/s** |
| 2 路并发读+写（等价于 2 个合并同时跑） | 各 10 MB/s，**合计约 20 MB/s** |

原因：合并落盘 = 读 `.part`（1×）+ 写目标文件（1×），整个流程对磁盘是 **3 倍 I/O**（上传写 1× + 合并读 1× + 合并写 1×）。实测 2 路合并合计仅 11 MB/s，正好落在这块盘的能力范围内 —— **合并实现没问题，是盘的物理极限**。

顺带观察到的流水线现象：客户端要等 `complete` 响应才释放上传名额，所以合并期间网络空闲（实测接收 0 MB/s、磁盘忙 23.9 MB/s）。由于磁盘已是全局瓶颈，把上传与合并重叠也不会减少总 I/O，因此**不能靠调度优化提速，只能减少 I/O 总量**。

- 当前进度（22:57）：已落盘 **17 个模型**（起始 13），41 个会话在册、剩余 63.9GB
- 预估剩余时间：约 **2 小时**（磁盘综合约 25MB/s × 3 倍 I/O）
- 磁盘余量：`/home` 余 480GB，充足

**后续可做的结构性优化**（预期 2~3 倍）：分块**直接写入目标文件的偏移位置**（先 `ftruncate` 出稀疏文件，写完 rename 落盘），彻底省掉「合并拷贝」这一读一写，把 3 倍 I/O 降到 1 倍。对已上传一半的存量会话（`.part` 已在盘上）收益只有 7%~33%，因此更适合**在下一批上传前**落地并测试。

## 五、监控工具

```bash
bash ~/Downloads/dl-upload-monitor.sh 30 40    # 每 30s 采样，写入 /tmp/dl-monitor.log
curl -s --noproxy '*' http://127.0.0.1:8899/healthz   # 连接数/上传连接/合并队列/内存
```

## 五、相关文件
- 单元：`~/Downloads/dl-server.service`（→ `/etc/systemd/system/`）
- 监控脚本：`~/Downloads/dl-upload-monitor.sh`，日志 `/tmp/dl-monitor.log`
- 服务端：`~/Downloads/download-server.js`
