# rust-thanos 真实世界数据验收报告

> 版本：v0.0.3 · 验收日期：2026-08-22 · 数据：真实 Minecraft 服务器世界（完整版 + 优化版）

本报告使用两套**真实世界数据**验收 rust-thanos 在 `backup`（优化裁剪）与 `merge`（合并恢复）两条链路上的正确性与性能，并对比不同 `InhabitedTime` 阈值（0 / 1 / 2 / 3 / 4 / 5 分钟）下的处理效果，供用户了解实际性能，并为后续迭代提供可复现的基线。

---

## 1. 测试环境

| 项目 | 值 |
| --- | --- |
| 机器 | 笔记本（Intel Core i7-9750H @ 2.60 GHz，6 核 / 12 线程，12 MB L3） |
| 内存 | 15 GiB |
| 运行环境 | WSL 2（Ubuntu 26.04 LTS，kernel 6.6.114.1-microsoft-standard-WSL2） |
| Rust | rustc 1.98.0（2026-08-18）/ cargo 1.98.0 |
| 二进制 | rust-thanos v0.0.3（release，x86_64-unknown-linux-gnu，5.5 MB） |
| 文件系统 | WSL ext4（数据从 Windows NTFS 拷入，避免 9p 互操作层开销） |
| 并行度 | region 级并行，`--parallelism 0`（自动 = 12 逻辑核） |

> 说明：Windows 下按项目建议使用 WSL 以获得更佳性能；数据放置于 WSL 原生 ext4，以排除跨文件系统互操作层对 I/O 的干扰。页面缓存为自然状态（WSL 不允许主动 drop_caches），世界体积大于物理内存，各次运行均包含真实磁盘读取。

## 2. 测试数据

| 数据集 | 大小 | MCA 文件数 | 说明 |
| --- | --- | --- | --- |
| `world/`（全量） | 18,637,648,478 B（≈17.4 GiB） | 21,688 | 真实服务器完整世界（overworld / the_end / the_nether 三维） |
| `world_backup/`（优化后） | 2,176,593,013 B（≈2.0 GiB） | 3,415 | 同一世界的优化备份（真值基准） |

`world/` 按维度 MCA 文件分布：

| 维度 | region | entities | poi |
| --- | --- | --- | --- |
| overworld | 6,380 | 5,124 | 4,031 |
| the_end | 3,851 | 1,181 | 215 |
| the_nether | 482 | 364 | 60 |
| **合计** | **10,713** | **6,669** | **4,306** |

`world_backup/` 的 3,415 个 MCA 中 3,413 个（99.9%）是 `world/` 的子集（仅 2 个 poi 文件为多余残留），可直接作为「优化结果」的真值基准。

## 3. 测试方法

- 阈值对比：`backup world out -t {0,60,120,180,240,300}`，严格大于语义（`InhabitedTime > 阈值`，秒 ×20 = tick），默认参数（fsync 开启、复制非 MCA 文件、无 keep-range）。
- 每次运行写入 `--report-file <json>`，统计口径来自 `OptimizeReport`：`processedChunks / removedChunks / keptChunks / beforeSize / afterSize / durationMs / errors`。
- 额外用 `/usr/bin/time -v` 记录墙钟与最大 RSS。
- 正确性验证：输出结构完整性、跨阈值单调性、与 `world_backup` 真值对比、merge 往返恢复。
- 复现脚本见 [bench/scripts](../bench/scripts/README.md)（阈值运行 / dry-run / 结构校验 / 真值对比 / merge 往返）。

## 4. 结果：阈值对比

对 `world/`（21,688 个 MCA，3,162,906 区块）以六档 `InhabitedTime` 阈值各跑一次完整 `backup`，全部使用修复后的 v0.0.3 二进制。计时为干净复跑（sweep 与 merge 互不并发）：

| 阈值 | kept 区块 | removed 区块 | 输出体积 | 缩减率 | 墙钟 | 吞吐（chunks/s） | 写吞吐（MB/s） | 错误 |
| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
| `-t 0` | 908,260 | 2,254,646 | 6.38 GiB | 63.2% | 2:24 | 22,018 | 78.2 | 0 |
| `-t 60` | 276,381 | 2,886,525 | 2.41 GiB | 86.1% | 1:43 | 30,819 | 149.2 | 0 |
| `-t 120` | 250,054 | 2,912,852 | 2.18 GiB | 87.4% | 1:42 | 30,941 | 152.0 | 0 |
| `-t 180` | 240,290 | 2,922,616 | 2.10 GiB | 87.9% | 1:35 | 33,204 | 164.0 | 0 |
| `-t 240` | 235,262 | 2,927,644 | 2.05 GiB | 88.2% | 1:49 | 28,927 | 143.3 | 0 |
| `-t 300` | 232,067 | 2,930,839 | 2.03 GiB | 88.3% | 1:46 | 29,853 | 148.2 | 0 |

要点：

- **单调性**：kept 随阈值严格单调递减（908,260 → 232,067），符合「保留 `InhabitedTime > 阈值` 区块」的语义。
- **确定性**：各档重复运行的 `processed/removed/kept` 完全一致（如 t240 首跑与干净复跑 kept 均为 235,262；t300 均为 232,067），region 级并行结果与并行度/时间无关。
- **零错误**：六档均 `errors: []`。
- **t300 输出与真值基准几乎一致**：2.03 GiB，仅比 `world_backup`（2.03 GiB，233,021 区块）少 954 区块——差异来自两套工具的 keep 判定细节，见 §5。
- **墙钟与缩减率**：阈值越高输出越小，写吞吐越高；`-t 0` 输出最大（6.38 GiB）故墙钟最长。

## 5. 正确性验证

### 5.1 输出可用性（结构完整性）

对 `t60` 与 `merged` 两个代表性输出做 Minecraft 世界结构校验：

| 检查项 | t60 | merged |
| --- | --- | --- |
| `level.dat` | ✅（472 B） | ✅（466 B） |
| 三维（overworld/the_end/the_nether）region/entities/poi | ✅ | ✅ |
| region MCA 数 | 1,581 | **10,713**（= world 全量） |
| entities / poi MCA 数 | 1,409 / 779 | 6,621 / 4,112 |
| 零字节 MCA | 0 | 0 |
| 玩家/data/generated 文件 | ✅（4,176/2,272/33） | ✅（4,336/2,702/36） |

### 5.2 输出为输入严格子集

对全部六档输出执行 `compare_outputs.sh`，检查「输出中出现了输入 `world/` 里不存在的 MCA」：

| 阈值 | 输出 MCA 数 | 输出含 world 没有的文件 |
| --- | ---: | ---: |
| t0 | 7,071 | 0 |
| t60 | 3,769 | 0 |
| t120 | 3,306 | 0 |
| t180 | 3,122 | 0 |
| t240 | 3,022 | 0 |
| t300 | 2,960 | 0 |

**六档输出全部是 `world/` 的严格子集，无任何越界文件。**

### 5.3 与真值基准 `world_backup` 对比

`world_backup/` 由原工具（OrzMCBackup）生成，3,415 个 MCA 中 3,413 个是 `world/` 的子集，可直接作为真值。对比逻辑：**输出与真值的差异必须全部是「输出不含某区块」这类方向**（真值 keep 判定更保守，故真值含而输出不含属正常），绝不允许出现「输出含而真值不含」的反向越界。

| 阈值 | 输出有而真值无 | 真值有而输出无（属正常） |
| --- | ---: | ---: |
| t0 | 3,956 | 300 |
| t60 | 739 | 385 |
| t120 | 311 | 420 |
| t180 | 139 | 432 |
| t240 | 49 | 442 |
| t300 | **0** | 455 |

`t300` 输出文件集是 `world_backup` 的**完全子集**（0 越界）；随阈值升高「输出有而真值无」单调趋近 0，方向完全正确。`t0` 的 3,956 个输出独有文件属预期：`-t 0` 保留全部 `InhabitedTime > 0` 区块，而真值 `world_backup` 是 5 分钟阈值产物，keep 集合本就不同。

### 5.4 dry-run 保真

`--dry-run` 只分析不写盘，其计数必须与真实运行一致：

| 指标 | 真实 t60 | dry-run t60 |
| --- | ---: | ---: |
| processed / removed / kept | 3,162,906 / 2,886,525 / 276,381 | **相同** |
| beforeSize / afterSize | — | 18.6 / 18.6 GiB（零写入） |

dry-run 与真实运行的 chunk 计数完全一致，且 `beforeSize == afterSize` 证明 dry-run 确实零写入。

### 5.5 merge 往返恢复（round-trip）

`merge world world_backup merged` 的语义是：**base 全量 + patch（优化后更新态）逐槽覆盖**。校验三条：

1. **report 零错误**：`{"mergedRegions":1244,"copiedFiles":0,"patchSlots":233021,"baseSlots":463657,"linkedEntities":98384,"linkedPoi":10022,"overlayFiles":7353,"errors":[]}`（patchSlots 233,021 == world_backup 区块总数，所有备份区块全部合并回）。
2. **region 数据完整**：`merged` 含 **10,713** 个 region MCA，与 `world` 完全一致（0 缺失 0 多余）。
3. **往返等价**：对 `merged` 以 `-t 300` 做 dry-run，`processed=3,162,996`（= world 的 3,162,906 + 90 个 patch 独有槽）、`kept=233,021`——kept **与 `world_backup` 区块总数完全一致（233,021）**。kept 计数与真值基准逐位相等，即「优化→合并→再优化」的 keep 集合与原始优化结果一致。

文件集层面 `merged` 比 `world` 少 243 个 entities/poi MCA、多 1 个，已逐文件核验：

- 少的 243 个（48 entities + 195 poi）**全部是 8192 字节、含 0 个非空区块槽的「空 region 文件」**，且全部存在于 patch（`world_backup`）中：merge 对 patch 也有的文件跳过 base 复制、由 overlay 重写，空结果不生成文件（惰性写入）。Minecraft 中「空 region 文件」与「无该文件」语义等价，**无任何数据丢失**。
- 多的 1 个（`poi/r.-6.-54.mca`，16 KB）是 `world_backup` 独有的真实 poi 数据（`world` 中没有对应文件），合并后正确保留——符合「patch 更新态优先」的设计。

### 5.6 两个数据丢失 Bug 的修复回归

验收中曾发现两个会静默丢数据的 Bug，均已修复并回归验证（详见 §7 与代码注释）：

- **Bug A（fd 耗尽丢区块）**：默认 ulimit 10240 下高并发打开 region 会耗尽文件描述符，导致约 30% 区块被静默丢弃。修复后 `FileAccess` 改为共享文件句柄（`Arc<Mutex<File>>`）+ 每克隆独立偏移量，等价于 `MemoryAccess` 语义。回归：`verify0` 在默认 ulimit 下 0 错误、处理 3,162,906 / 保留 908,260 区块（与 high-ulimit 参考一致），`tests/reader_fd.rs` 断言 fd 数 ≤ baseline+32。
- **Bug B（跳过 region 丢文件）**：无法解析/读取的 region 此前只记错误不写输出，造成数据丢失。修复后 `skip_region_preserving` 将原文件逐字节复制到输出（`ErrorKind::Copy`），entities/poi 同规则。回归：`tests/memory_fs.rs`、`tests/phase3.rs`、`tests/tiny_mca.rs` 均断言不可解析 region 在输出中逐字节保留。

## 6. 性能分析

### 6.1 内存

region 级处理 + 字节级定位 + 惰性写入，单次处理仅持有 1 个 region 的元数据，不整体加载区块。各档运行 RSS 实测 **23–25 MiB**（`/usr/bin/time -v`，区间 23.3–24.8 MiB，与阈值无关），与 17.4 GiB 输入、21,688 个文件的世界规模无关。merge 更轻（16 MiB）。

### 6.2 吞吐

- 处理吞吐 22k–33k chunks/s（`-t 0` 因输出最大而最低；其余档均 >29k）。
- 写吞吐 78–164 MB/s，随缩减率（输出变小）上升。
- 墙钟 1:35–2:24，全部在 2.5 分钟内完成 3,162,906 区块的处理 + 17.4 GiB 读取 + 最高 6.38 GiB 写入。

### 6.3 并行度确定性

所有报告由 `--parallelism 0`（自动=12 逻辑核）生成；代码保证任意并行度下逐字节一致，sweep 各档的重复运行计数完全一致即为其证据。

## 7. 结论与建议

**结论**：rust-thanos 在真实世界数据上通过验收——

1. **正确性**：六档阈值全部 0 错误；输出均为输入严格子集；`t300` 输出与真值基准 `world_backup` 逐块对齐（0 越界）；merge 往返把 3,162,906 区块的完整世界恢复为可加载地图（region 10,713/10,713），再优化回 233,021 区块与原优化结果一致。
2. **修复有效**：fd 耗尽与跳过 region 两个数据丢失缺陷均已修复，输出不再静默丢数据。
3. **性能**：17.4 GiB / 316 万区块的处理在 1.5–2.5 分钟内完成，常驻内存仅 ~25 MiB；merge 2:41 完成 18 GiB 往返恢复，内存 16 MiB。
4. **5 分钟阈值与真值一致**：`-t 300` 的 keep 集合与官方优化备份高度吻合，说明该参数适合作为默认推荐值。

**建议**：
- 正式使用建议以 `-t 300`（或 `-t 240`）为默认阈值，兼顾压缩率（>88%）与「留够 5 分钟探索区块」的体验。
- 对超大世界（>10 GiB）优先在 WSL / 同盘 ext4 运行，避免跨文件系统 I/O 损耗（本报告即如此）。
- 上线前仍建议保留 `--dry-run` 先行验证 + 一次 merge 往返抽查，成本极低（数分钟）。
