小块写缓存优化

This commit is contained in:
2026-03-03 14:24:17 +00:00
parent c33a694bd8
commit 975afaf3f0
14 changed files with 690 additions and 1298 deletions

View File

@@ -1,249 +1,216 @@
# ZVFS -> RocksDB (LD_PRELOAD) 分阶段改造计划(可重入)
# ZVFS 分阶段改造计划(可重入,用户验收版
本文档是给后续编码代理(我)使用的执行计划。目标是:在不破坏现有功能的前提下,逐步把 `zvfs.c + zvfs_hook.c` 改造成可支撑 RocksDB 的 LD_PRELOAD 存储层。
> 目标:把当前实现改造成可并发扩展、高性能且语义完整的架构。
> 约束:我无法使用 root所有阶段验收由你执行。
## 通用约定(所有阶段)
- 建议先记录基线:`git rev-parse --short HEAD`
- 每阶段都保持“可编译 + 可回归”。
- 每阶段完成后打一个里程碑 tag例如 `phase1-done`),中断后可从最近 tag 继续。
- 验收命令默认:
```bash
make -C zvfs -j4
make -C test -j4
env LD_PRELOAD=/home/lian/share/10.1-spdk/zvfs/zvfs/libzvfs.so ./test/bin/test_phase2_posix /zvfs
```
## 已落地变更2026-03-03
1. **stale blob 自愈修复(已完成)**
- `open(O_CREAT)` 遇到元数据引用失效 blob 时自动重建并回写元数据。
- `unlink/close(rename 覆盖)` 删除失效 blob 时容忍 `ENOENT/EINVAL`,避免误报 `EIO`
2. **小块写合并(已完成)**
- hook 层新增 per-fd writeback buffer默认 128KB连续小写先合并再 `pwrite`
-`read/pread/lseek/fsync/fdatasync/close/ftruncate/fallocate/unlink/rename/sync_file_range` 前补齐 flush保证可见性。
3. **当前观察**
- 小块写已提升,但小块读仍偏低;读优化作为后续阶段重点。
---
## 0. 使用规则(可重入协议)
## Phase 0基线与护栏
每次开始新一轮开发时,严格按以下顺序执行:
### 要做的事情
1. 固化当前行为基线功能、性能、CPU 占用。
2. 在代码中加入轻量统计框架(计数器/延迟桶/开关),不改变行为。
3. 增加最小并发回归入口(并行跑现有测试)。
1. 读取本文件,定位“当前阶段”和“未完成任务”。
2. 先做“状态核对”:
- 代码是否已存在同名结构/函数;
- 测试是否已覆盖该阶段验收项;
- 若已完成则跳过,不重复改动(幂等)。
3. 只推进一个阶段内的最小闭环,不跨阶段大改。
4. 每完成一个任务,立即更新本文件:
- 勾选完成项;
- 记录关键变更文件;
- 记录剩余风险;
- 记录本阶段“正确性验证”执行结果
5. 若发现与计划冲突的现实约束SPDK限制/接口差异/语义冲突),先在“变更记录”追加说明,再调整后续子任务,不直接删原计划。
6. 非代码文档统一写入 `plan/` 目录(用户约束)。
### 用户验收
```bash
make -C zvfs -j4 && make -C test -j4
env LD_PRELOAD=/home/lian/share/10.1-spdk/zvfs/zvfs/libzvfs.so ./test/bin/test_phase2_posix /zvfs
env LD_PRELOAD=/home/lian/share/10.1-spdk/zvfs/zvfs/libzvfs.so ./test/bin/test_single_file_perf /zvfs
env LD_PRELOAD=/home/lian/share/10.1-spdk/zvfs/zvfs/libzvfs.so ./test/bin/test_single_file_random_perf /zvfs
```
### 通过标准
- 功能测试通过。
- 有一份可复用的“基线性能记录”IOPS/BW/延迟)
### 可重入说明
- 仅增量加观测代码,可重复执行,不影响后续阶段。
---
## 1. 总目标与约束
## Phase 1全局运行时与并发安全
### 1.1 总目标
- 支持 RocksDB 关键 I/O 路径(优先 db_bench 可运行并稳定)
- 提供正确 POSIX 语义(至少覆盖 RocksDB 依赖子集)
- 从单线程串行模型演进到可并行数据面
-证崩溃后一致性(元数据持久化可恢复)
### 要做的事情
1. 引入 `zvfs_runtime_t`,统一管理 mount/init 状态与全局资源
2.`pthread_once + mount mutex` 保护初始化/挂载过程
3. 给 inode/path/fd/dirs 操作补齐锁rwlock + 细粒度 mutex
4.持接口不变:`open/read/write/...` 行为兼容
### 1.2 当前主要问题(已确认)
- 单线程 + busy wait + 全局串行,队列深度近似 1。
- hook 覆盖不足,缺少 pread/pwrite/fsync/ftruncate/openat/stat 等。
- I/O 参数耦合在 `zvfs_file_t`,不利于 pread/pwrite 和并发。
- 全局状态无并发保护(`g_fs/fd_table/dirents/open_count` 等)。
- open/write/close/unlink errno 与 POSIX 语义不完整。
- 元数据依赖宿主文本文件,容量小、非原子、不可恢复。
- 配置路径硬编码,部署迁移性差。
### 用户验收
```bash
make -C zvfs -j4 && make -C test -j4
env LD_PRELOAD=/home/lian/share/10.1-spdk/zvfs/zvfs/libzvfs.so ./test/bin/test_phase2_posix /zvfs
for i in $(seq 1 8); do
env LD_PRELOAD=/home/lian/share/10.1-spdk/zvfs/zvfs/libzvfs.so ./test/bin/test_dual_open_same_file /zvfs &
done
wait
```
### 1.3 强制要求(新增)
- 每个阶段都必须定义“正确性验证方案”,且在阶段结束前执行并记录结果
- 未完成正确性验证,不得将该阶段标记为完成
### 通过标准
- 无崩溃/死锁
- 并发场景不出现随机 EBADF/ENOENT/元数据错乱
### 可重入说明
- 锁与 runtime 框架可独立提交;若中断,重新进入本阶段不会破坏状态。
---
## 2. 阶段总览
## Phase 2Worker 化 IO 通路(替换单 global_thread
- 阶段0基线与兼容清单先知道 RocksDB 真实需要什么)
- 阶段1架构解耦file vs io_req
- 阶段2补齐关键 hook 与 POSIX 语义
- 阶段3并发模型升级控制面/数据面分离)
- 阶段4元数据持久化重构super blob / 日志)
- 阶段5性能与稳定性验收
### 要做的事情
1. 实现 worker 池(默认 N:M支持配置 1:1
2. 每 worker 持有独立 `spdk_thread + io_channel`
3. read/write/pread/pwrite 路径改为“提交到绑定 worker 执行”。
4. 保留同步 POSIX 语义,但去掉全局单线程瓶颈。
### 用户验收
```bash
make -C zvfs -j4 && make -C test -j4
env LD_PRELOAD=/home/lian/share/10.1-spdk/zvfs/zvfs/libzvfs.so ./test/bin/test_phase2_posix /zvfs
for i in $(seq 1 4); do
env LD_PRELOAD=/home/lian/share/10.1-spdk/zvfs/zvfs/libzvfs.so ./test/bin/test_single_file_random_perf /zvfs &
done
wait
```
### 通过标准
- 功能与 Phase 1 一致。
- 并发压测吞吐明显高于基线(目标 >= 1.5x,先达成趋势)。
### 可重入说明
- worker 与旧路径可通过编译开关共存,出现问题可快速切回旧路径继续调试。
---
## 3. 详细阶段计划
## Phase 3完成等待机制与批处理
## 阶段0基线与兼容清单
### 要做的事情
1. 用“提交队列 + 完成通知”替换纯 busy-poll `waiter`
2. 增加批量 poll 与背压(队列满、超时、错误传播)。
3. 补齐延迟与队列深度指标,定位长尾。
4. 引入读路径流水线(允许并发 in-flight read把有效 QD 从 1 提升到可配置值。
### 目标
- 建立“RocksDB syscall 最小集合”和“当前实现缺口表”。
### 用户验收
```bash
make -C zvfs -j4 && make -C test -j4
env LD_PRELOAD=/home/lian/share/10.1-spdk/zvfs/zvfs/libzvfs.so ./test/bin/test_single_file_perf /zvfs
env LD_PRELOAD=/home/lian/share/10.1-spdk/zvfs/zvfs/libzvfs.so ./test/bin/test_single_file_random_perf /zvfs
```
### 任务
- [x]`strace` 或等效方式采集 `db_bench` syscall含失败分支
- [x] 形成兼容矩阵:必须实现 / 可降级透传 / 暂不支持
- [x] 固化第一批验收用例(最小 db_bench 参数 + 现有单测集合)。
### 通过标准
- 在同等负载下 CPU 空转显著下降
- P99 延迟较 Phase 2 收敛(无明显长尾恶化)
### 交付物
- [x] `plan/rocksdb_syscall_matrix.md`
- [x] `plan/baseline_commands.md`
### 阶段验收
- [x] 能明确列出阶段2必须完成的 hook 列表。
### 正确性验证方案
- [x] 验证矩阵中的每个“必须实现 syscall”都有最小复现样例命令或小程序
- [x] 基线命令可重复执行,输出包含 syscall 统计与错误码分布。
- [x] 验证结果记录到 `plan/baseline_commands.md`(含日期和环境信息)。
### 可重入说明
- 队列与等待层可单独演进;可先只替换 read再替换 write。
---
## 阶段1架构解耦关键
## Phase 4页缓存与写回合并
### 目标
- 把 I/O 请求参数从 `zvfs_file_t` 中拆出,建立请求对象,先打通 pread/pwrite 内核路径
### 要做的事情
1. 引入 per-inode 4KB 页缓存dirty/clean 状态)
2. 小写走 cache + 延迟刷盘,大写/顺序写支持直写或批量写。
3. 引入 flush 策略阈值、定时、fsync 强制。
4. 缩减 `resize + sync_md` 频率chunk 预分配)。
5. 读性能专项:
- 增加顺序读 readahead如 128KB~1MB 窗口自适应)。
- 对齐读支持“直接读到用户缓冲”快路径,减少一次 memcpy。
- 引入 clean page cache读热点复用避免重复 blob read
### 任务
- [x] 新增 `zvfs_io_req`字段至少op/buf/len/offset/flags/result/errno/finished
- [x] 重构 `zvfs_read/zvfs_write` 为基于 `zvfs_io_req` 的通用入口。
- [x] 实现内部 `zvfs_pread_internal/zvfs_pwrite_internal`,不改 hook 先可调用。
- [x] 移除 `zvfs_file_t` 中仅一次请求有效的临时字段(或标记弃用)。
### 用户验收
```bash
make -C zvfs -j4 && make -C test -j4
env LD_PRELOAD=/home/lian/share/10.1-spdk/zvfs/zvfs/libzvfs.so ./test/bin/test_phase2_posix /zvfs
env LD_PRELOAD=/home/lian/share/10.1-spdk/zvfs/zvfs/libzvfs.so ./test/bin/test_single_file_random_noaligned_perf /zvfs
```
### 交付物
- [x] `zvfs/zvfs.h` 新结构与接口
- [x] `zvfs/zvfs.c` 读写路径重构完成
- [x] `plan/phase1_validation.md`(用户侧验证步骤)
### 通过标准
- 功能语义不回退truncate/sparse/rename/fstat 通过)。
- 小块随机写吞吐继续提升,写放大降低。
### 阶段验收
- [x] 现有 read/write/lseek 测试全通过
- [x] 新增 pread/pwrite 单测通过(可先直连接口,不经 hook
### 正确性验证方案
- [x] 新旧接口结果一致性校验:同一输入下 `read/write``pread/pwrite` 数据一致。
- [x] 边界测试offset=0、EOF、跨 page、非对齐、空写入。
- [x] 失败路径测试:非法 fd/参数时返回值与 `errno` 符合预期。
### 可重入说明
- cache 可先只支持 write-through再切 write-back两步都可单独验收
---
## 阶段2补齐 hook 与 POSIX 语义
## Phase 5元数据日志化与 fsync 语义闭环
### 目标
- 满足 RocksDB 最小兼容 API 集,保证语义与 errno 正确
### 要做的事情
1. `meta_save/load` 从文本快照升级为 WAL + checkpoint带 CRC/版本)
2. 明确并实现 `fdatasync/fsync` 语义:
- fdatasync 保证数据持久化;
- fsync 额外保证必要元数据持久化。
3. 补齐崩溃恢复流程checkpoint + replay
### 首批必须实现
- [x] `pread/pwrite`(及 `pread64/pwrite64` 视平台符号而定)
- [x] `open/open64/openat`
- [x] `fsync/fdatasync`
- [x] `ftruncate`
- [x] `fstat/stat/lstat`(至少满足 RocksDB 元数据查询)
- [x] `rename`(原子替换语义)
### 用户验收
```bash
make -C zvfs -j4 && make -C test -j4
env LD_PRELOAD=/home/lian/share/10.1-spdk/zvfs/zvfs/libzvfs.so ./test/bin/test_phase2_posix /zvfs
# 建议补充一次“异常退出后重启读取”的恢复验证(手工执行)
```
### 语义修复
- [x] `open` 支持 `O_TRUNC/O_APPEND/O_EXCL`
- [x] 权限检查(`O_RDONLY/O_WRONLY/O_RDWR`
- [x] 返回值与 `errno` 映射统一(失败路径不可吞错)
### 通过标准
- 重启后目录项与文件大小不丢失、不错乱。
- 数据库关键路径fsync/fdatasync语义满足预期。
### 交付物
- [x] `zvfs/zvfs_hook.c`phase2 hook 覆盖与语义修复)
- [x] `test/test_phase2_posix.c`phase2 POSIX 回归用例)
- [x] `plan/phase2_validation.md`(用户侧验证步骤)
### 阶段验收
- [ ] db_bench 基础 workload 可跑通(单线程先行)。
- [ ] 不支持的 syscall 必须明确透传且行为可解释。
### 正确性验证方案
- [ ] 每个新增 hook 至少 1 个正例 + 1 个反例(权限/参数/不存在文件)。
- [ ] `open` 语义验证:`O_TRUNC/O_APPEND/O_EXCL` 行为与本地文件系统对齐。
- [ ] `errno` 对照验证:对关键失败场景做预期值断言。
- [ ] 跑最小 `db_bench`,确认无 “Function not implemented/Bad file descriptor” 类错误。
### 可重入说明
- WAL 与 checkpoint 支持并存迁移;可先双写验证,再切主读路径。
---
## 阶段3并发模型升级
## Phase 6性能收敛与上线门槛
### 目标
- 保持 hook 层阻塞语义,但底层可并行提交处理
### 要做的事情
1. 清理临时开关,保留必要调优参数
2. 整理性能报告(与 Phase 0 基线对比)。
3. 做最终回归矩阵(功能 + 并发 + 性能 + 恢复)。
### 任务
- [ ] 设计并实现 `控制面线程 + N个数据面worker`
- [ ] 每 worker 使用独立 io_channel。
- [ ] 引入并发保护fd_table、dirents、open_count、全局挂载状态。
- [ ] 修复生命周期竞态close/unlink 并发、延迟删除)。
### 用户验收
```bash
make -C zvfs -j4 && make -C test -j4
env LD_PRELOAD=/home/lian/share/10.1-spdk/zvfs/zvfs/libzvfs.so make -C test run-test
env LD_PRELOAD=/home/lian/share/10.1-spdk/zvfs/zvfs/libzvfs.so ./test/bin/test_single_file_perf /zvfs
env LD_PRELOAD=/home/lian/share/10.1-spdk/zvfs/zvfs/libzvfs.so ./test/bin/test_single_file_random_perf /zvfs
```
### 阶段验收
- [ ] 多线程压测下无崩溃/无明显数据错乱
- [ ] QD>1 场景吞吐显著高于阶段2
### 通过标准
- 全量功能测试通过
- 多线程性能达到 `codexplan.md` 目标(或给出量化偏差与原因)
### 正确性验证方案
- [ ] 多线程读写一致性校验(文件内容 hash 或区块比对)
- [ ] 并发场景稳定性:长时间压测无崩溃、无死锁、无句柄泄漏。
- [ ] 竞态回归:`close/unlink`、双开同文件、并发 append 场景正确。
### 可重入说明
- 本阶段仅收敛与验收,不引入架构性变更;可反复执行直到指标稳定
---
## 阶段4元数据持久化重构
## root 权限与运行建议
### 目标
- 去掉宿主文本元数据文件,转向 blobstore 内部可恢复元数据。
### 任务
- [ ] 使用 super blob或单独 metadata blob管理目录与 inode-like 信息。
- [ ] 建立日志或 copy-on-write 更新流程,支持崩溃恢复。
- [ ]`create/unlink/rename/truncate` 实现原子更新策略。
### 阶段验收
- [ ] 强制中断后重启可恢复一致目录和文件大小信息。
- [ ] 不再依赖固定绝对路径元数据文件。
### 正确性验证方案
- [ ] 故障注入:在 create/write/rename/truncate 中间点中断后重启验证一致性。
- [ ] 元数据回放验证目录项数量、文件大小、blob_id 映射正确。
- [ ] 对比验证:与中断前快照(或日志)比对差异可解释。
---
## 阶段5性能与稳定性验收
### 目标
- 形成“可用 + 可解释 + 可回归”的最终版本。
### 任务
- [ ] buffer 管理优化(池化、减少拷贝、减少重复 preread
- [ ] 完整回归:现有单测 + 新增 hook 语义测试 + db_bench 组合。
- [ ] 输出性能报告吞吐、延迟、CPU、错误率
### 阶段验收
- [ ] 关键 workload 稳定运行,结果可复现。
### 正确性验证方案
- [ ] 全量回归连续执行至少 3 轮,结果一致且无新增失败。
- [ ] 性能结果包含波动范围(平均值与离散度),可复现实验命令。
- [ ] 最终发布前执行一次“从空盘到回归完成”的冷启动验证流程。
---
## 4. 当前阶段状态
- 当前阶段:`阶段2`
- 阶段状态:`pending_user_validation`
- 本轮目标:用户按 `plan/phase2_validation.md` 执行 phase2 正确性验证
---
## 5. 每轮执行后必须更新的记录
## 5.1 变更记录(按时间追加)
- [x] 2026-03-02: 完成 phase0db_bench syscall 基线 + 失败分支 + 兼容矩阵);文件:`plan/plan.md``plan/rocksdb_syscall_matrix.md``plan/baseline_commands.md`;风险:当前环境 SPDK 初始化失败IOVA PA 不可用),需在 phase1 前确定运行环境策略
- [x] 2026-03-02: 完成 phase1 代码改造file/io_req 解耦 + 新增 pread/pwrite API文件`zvfs/zvfs.h``zvfs/zvfs.c``plan/plan.md``plan/phase1_validation.md`;风险:本轮未在 root+LD_PRELOAD 环境完成端到端验证
- [x] 2026-03-02: 根据用户反馈修复 phase1 边界问题preread 失败时将临时缓冲区清零,避免洞区读到脏数据);文件:`zvfs/zvfs.c``plan/phase1_validation.md`;风险:仍需用户侧端到端复测确认
- [x] 2026-03-02: 根据用户复测继续修复 sparse hole 问题offset>EOF 时清零本次覆盖页内 gap 区间);文件:`zvfs/zvfs.c``plan/plan.md`;风险:跨多页大洞写入语义仍需在 phase2 做专项覆盖
- [x] 2026-03-02: 完成 phase2 代码改造(补齐 openat/pread/pwrite/fsync/ftruncate/stat/rename/fcntl/mkdir/rmdir 等 hook 与关键语义);文件:`zvfs/zvfs_hook.c``zvfs/zvfs.h``test/test_phase2_posix.c``test/Makefile``plan/phase2_validation.md``plan/plan.md`;风险:尚未在用户环境完成 db_bench 端到端验收
- [x] 2026-03-02: 根据用户 db_bench 反馈修复目录打开语义(目录允许 O_RDONLY 打开,不再强制要求 O_DIRECTORY文件`zvfs/zvfs_hook.c`;风险:仍需用户复测 db_bench
- [x] 2026-03-02: 根据用户 db_bench 反馈补齐 fadvise/fallocate/sync_file_range hook避免 pseudo-fd 被内核路径误判为 EBADF文件`zvfs/zvfs_hook.c``zvfs/zvfs.h`;风险:仍需用户复测 db_bench
## 5.2 风险清单(持续维护)
- [ ] 线程模型改造可能引入 SPDK thread affinity 问题
- [ ] rename/truncate 语义与 blobstore 能力映射复杂
- [ ] LD_PRELOAD 多符号拦截顺序可能受 libc/应用实现影响
## 5.3 决策记录ADR-lite
- [ ] D-001: 为什么选择控制面/数据面分离
- [ ] D-002: 为什么选择 super blob 元数据格式
- [ ] D-003: 不支持 syscall 的透传策略与边界
---
## 6. 完成定义DoD
满足以下条件才可标记“计划完成”:
- [ ] 阶段0~5全部验收项勾选完成
- [ ] RocksDB db_bench 至少 3 类 workload 稳定通过
- [ ] 关键 POSIX 语义测试通过并有失败用例说明
- [ ] 文档包含部署方式、限制项、回归命令
- 若 NVMe/SPDK 环境需要 root请在你本机按现有流程执行验收。
- 若希望无 root 回归,建议补一个 `Malloc` bdev 的 JSON 配置,并将 bdev 名改为可配置(环境变量优先)。