# 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: + ` --- ## 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. 等待用户确认再进入下一阶段