Files
zvfs/plan.md
2026-03-02 07:27:48 +00:00

347 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ZVFS -> RocksDB LD_PRELOAD 可重入改造计划(给 Codex 执行)
> 目标:这是一份“可中断、可恢复、可分段提交”的实施计划。
> 原则:每阶段都能独立编译、独立验证、独立回滚,不要求一次性改完。
---
## 0. 执行约束(必须遵守)
1. **一次只做一个阶段**,每阶段结束后必须通过该阶段验收再进入下一阶段。
2. **不跨阶段混改**:例如阶段 1 不改 hook 覆盖,阶段 3 不重构线程模型。
3. **每阶段必须留下恢复锚点**
- 文档状态更新(本文件“阶段状态”区)
- 代码中保留临时兼容开关(若有)
- 能独立提交 commit建议
4. **失败可回退**:阶段内失败仅回退本阶段改动,不影响已完成阶段。
5. **默认保持旧行为可用**,新增能力通过新路径启用,逐步替换旧路径。
---
## 1. 阶段状态(可重入)
- [ ] Phase 1: IO 请求结构体落地(单线程兼容模式)
- [ ] Phase 2: Worker + 多线程执行模型落地
- [ ] Phase 3: 偏移型 IO 完整化pread/pwrite 语义)
- [ ] Phase 4: RocksDB 核心 hook 补齐P0
- [ ] Phase 5: 目录/锁/截断接口补齐P1
- [ ] Phase 6: 元数据一致性与崩溃恢复增强
- [ ] Phase 7: 性能增强与观测
> 重入方式:中断后先看此状态区 + 对应阶段“完成定义”,从未完成阶段继续。
---
## 2. 基线与分支策略
### 2.1 基线要求
- 当前 `libzvfs.so` 可正常构建。
- 当前 `func_test` 可运行。
- 已确认 `/zvfs` 路径仍由现有 hook 接管。
### 2.2 分支与提交建议
- 每阶段 1 个分支或 1 个 commit
- `phase1-req-struct`
- `phase2-worker-thread`
- `phase3-offset-io`
- ...
- commit message 模板:
- `phaseX: <what> + <compat mode/DoD>`
---
## 3. 分阶段实施
## Phase 1: IO 请求结构体落地(不改线程模型)
### 目标
先把“每次系统调用复用 file 临时字段”的问题解开;引入可扩展请求对象,为后续多线程做准备。
**本阶段仍允许单线程执行路径**(内部可继续使用现有 waiter
### 改动范围
- `zvfs/zvfs.h`
- `zvfs/zvfs.c`
- 尽量不改 `zvfs/zvfs_hook.c` 的接口覆盖面(只做必要适配)
### 任务清单
1. 新增 `zvfs_req_t`(最小字段):
- `op`, `file`, `buf`, `count`, `offset`, `ret`, `op_errno`, `done`
- 可选:`need_copy_back`, `actual_io_count`
2. 给现有读写流程加“请求入参”版本:
- 新增 `zvfs_read_req(req)` / `zvfs_write_req(req)` 内部函数
- 旧 API `zvfs_read/zvfs_write` 变成薄包装(构造 req 后调用)
3. 去除对 `file->io_count/write_staging_buf/finished` 的强依赖:
- 保留字段可兼容,但主流程改为优先使用 req 字段
4. 错误返回统一:
- 内部负 errno -> 外部返回值 + errno 映射规则固定
### 完成定义DoD
- 编译通过。
- 现有 `func_test` 全通过。
- 无并发改造前提下,行为与旧版一致(回归结果一致)。
### 重入点
- 代码里出现 `zvfs_req_t``zvfs_read/zvfs_write` 已封装请求对象。
- 若中断,优先检查旧路径是否仍可用,再继续替换剩余 call path。
---
## Phase 2: Worker + 多线程执行模型
### 目标
将“调用线程主动 poll SPDK”改为“专用 worker 线程 poll + 请求队列”。
建立线程安全基础,不追求本阶段 hook 覆盖齐全。
### 改动范围
- `zvfs/zvfs.h`
- `zvfs/zvfs.c`
- `zvfs/zvfs_hook.c`(仅初始化、提交流程相关)
### 任务清单
1. 新增 `zvfs_worker_t`
- `pthread_t tid`
- 请求队列(先用 mutex + list + cond后续可换 MPSC
- `running/stopping` 状态
2. 新增生命周期函数:
- `zvfs_worker_start()`
- `zvfs_worker_stop()`
- 在 mount/init 时启动,在 atexit/unmount 时停止
3. 新增提交流程:
- `zvfs_submit_and_wait(req)`:调用线程阻塞等待完成
- worker 线程执行 SPDK 异步链并回填 req
4. 替换 `waiter()` 主路径:
- 保留 waiter 仅作为 fallback编译开关控制
5. 锁与并发保护(最小闭环):
- 全局状态锁:`g_fs/g_mounted/fd_map`
- 文件级锁:`offset` 更新、close 与 io 竞争
### 完成定义DoD
- `db_bench --threads=4` 不崩溃、不死锁(先小规模)。
- 旧单线程 case 仍通过。
- 调用线程不再直接 `spdk_thread_poll`
### 重入点
- worker 生命周期已接管主流程(可在日志中看到 worker 启动/停止)。
- 若中断,优先确保 stop 路径可达,避免进程退出挂死。
---
## Phase 3: 偏移型 IO 完整化pread/pwrite 语义)
### 目标
补齐 RocksDB 高频调用语义:`pread/pwrite` 不改文件偏移。
`read/write` 变成 `pread/pwrite + offset 管理` 的包装。
### 改动范围
- `zvfs/zvfs.h`
- `zvfs/zvfs.c`
- `zvfs/zvfs_hook.c`
### 任务清单
1. 新增核心接口:
- `zvfs_pread(file, buf, count, offset)`
- `zvfs_pwrite(file, buf, count, offset)`
2. 改造 read/write
- `read`:在 file lock 下读取并推进 `current_offset`
- `write`:在 file lock 下决定 offset含 O_APPEND
3. 明确并发语义:
- 同一 fd 并发 pread/pwrite 允许并行(不同 offset
- 同一 fd 的 read/write 偏移操作受 file lock 串行
### 完成定义DoD
- `pread/pwrite` 回归用例通过。
- 同一 fd 的并发 `pread` 不互相污染 offset。
### 重入点
- hook 层可区分 `read/write``pread/pwrite` 路径。
- 若中断,先保证 `read/write` 仍有正确 fallback。
---
## Phase 4: RocksDB 核心 hook 补齐P0
### 目标
先覆盖 RocksDB “能跑起来”最关键接口。
### 改动范围
- `zvfs/zvfs_hook.c`
- 必要时 `zvfs/zvfs.h` 新增声明
### 任务清单(按优先级)
1. 打开类:
- `open/open64/openat/openat64/__open_2/__open64_2`
2. IO 类:
- `pread/pread64/pwrite/pwrite64`
3. 元数据类:
- `fstat/stat/lstat/fstatat/access`
4. 持久化类:
- `fsync/fdatasync`
5. 文件变更类:
- `rename/renameat/renameat2/unlink/unlinkat`
### 实现要求
-`/zvfs` 路径必须透传 real libc。
- 所有失败分支必须设置 errno。
- 统一 dlsym 初始化:`pthread_once`,避免递归。
### 完成定义DoD
- RocksDB 基础 workload单线程可跑通
- `fillseq`, `fillrandom`, `readrandom`
- `strace -f` 观察 `/zvfs` 关键 syscall 已被接管。
### 重入点
- 每新增一组 hook 独立提交,失败时可只回退该组。
---
## Phase 5: 目录/锁/截断接口补齐P1
### 目标
补齐 RocksDB 稳定运行需要的辅助接口。
### 改动范围
- `zvfs/zvfs_hook.c`
- `zvfs/zvfs.c`(必要后端支撑)
### 任务清单
1. 截断:
- `ftruncate/truncate`
2. 锁:
- `fcntl` 最少支持 `F_SETLK/F_SETLKW/F_UNLCK/F_GETLK`
3. 目录:
- `mkdir/rmdir/opendir/readdir/closedir`
4. 容量提示(可选但建议):
- `fallocate/posix_fallocate`(不支持时明确 `EOPNOTSUPP`
### 完成定义DoD
- RocksDB 多线程基础 workload 可稳定运行(`--threads=4/8`)。
- LOCK 文件语义满足单进程多线程正确性。
### 重入点
- `fcntl` 与目录接口可拆成两个子阶段提交。
---
## Phase 6: 元数据一致性与崩溃恢复增强
### 目标
解决 `zvfs_meta.txt` 全量覆盖、崩溃窗口、恢复不确定问题。
### 改动范围
- `zvfs/zvfs_hook.c`meta load/save
- `zvfs/zvfs.c`(关键操作强制 flush 时机)
- 文档与测试脚本
### 任务清单
1. 元数据写入策略:
- 从“每次全量覆盖”改为“批量 + 关键点强制刷盘”
2. 元数据文件格式增强:
- 增加版本号 + CRC
3. 恢复路径:
- 加载失败时回退到最近完整版本(若实现双文件/快照)
4. 关键操作一致性点:
- `rename/unlink/truncate/fsync` 后策略明确
### 完成定义DoD
- 人工崩溃注入后kill -9可重新打开 DB。
- 元数据损坏能被检测并报错,不默默产生脏状态。
### 重入点
- 先做“格式版本化 + 校验”,再做“批量策略”。
---
## Phase 7: 性能增强与观测
### 目标
在正确性稳定后提性能,确保收益可量化。
### 改动范围
- `zvfs/zvfs.c`
- 性能脚本与统计输出
### 任务清单
1. 请求对象池 / DMA buffer 复用
2. 减少全局锁竞争(热点路径细化锁)
3. 元数据 debounce 与批量刷盘参数化
4. 增加指标:
- op 次数、失败率、平均/尾延迟、队列深度
### 完成定义DoD
- 与旧版本对比,多线程 `db_bench` 吞吐显著提升(目标 >1.5x,按实测调整)。
- 无新增数据一致性回归。
### 重入点
- 每个优化项独立开关,可单独启停做 A/B。
---
## 4. 阶段间依赖关系(严格)
1. 必须先完成 Phase 1 再做 Phase 2。
2. 必须先完成 Phase 2 再做 Phase 3/4。
3. Phase 4核心 hook完成后再做 Phase 5辅助 hook
4. Phase 6/7 必须基于 Phase 5 稳定分支。
---
## 5. 验收矩阵(执行时打勾)
### Phase 1
- [ ] 编译通过
- [ ] `func_test` 通过
- [ ] 请求对象已接管 read/write 内部主流程
### Phase 2
- [ ] worker 模型接管
- [ ] 多线程 smoke 测试通过
- [ ] 无死锁/退出挂死
### Phase 3
- [ ] `pread/pwrite` 语义正确
- [ ] offset 并发污染问题消失
### Phase 4
- [ ] P0 hook 集合已实现
- [ ] RocksDB 单线程 workload 跑通
### Phase 5
- [ ] P1 hook 集合已实现
- [ ] RocksDB 多线程 workload 稳定
### Phase 6
- [ ] 元数据校验与恢复策略生效
- [ ] 崩溃恢复测试通过
### Phase 7
- [ ] 关键指标可观测
- [ ] 性能目标达成或给出瓶颈结论
---
## 6. 风险与应对(执行版)
1. **接口补齐不完整**
- 应对:每阶段跑 `strace -f`,统计 `/zvfs` 相关未接管 syscall。
2. **并发死锁**
- 应对固定锁顺序debug 模式打印锁获取日志。
3. **行为回归**
- 应对保留兼容开关old path直到该阶段验收通过。
4. **性能退化**
- 应对:优化项独立开关,逐项 A/B不一次性合并。
---
## 7. 给 Codex 的执行模板(每阶段复用)
1. 阅读本阶段“目标/任务/DoD/重入点”
2. 只改本阶段文件与最小依赖
3. 本地编译 + 对应测试
4. 更新“阶段状态”勾选
5. 输出:
- 改了什么
- 测了什么
- 还剩什么
6. 等待用户确认再进入下一阶段