233 lines
8.6 KiB
Markdown
233 lines
8.6 KiB
Markdown
# ZVFS LD_PRELOAD -> RocksDB 加速实施计划(给 Codex 执行)
|
||
|
||
> 目标:基于当前 `zvfs/zvfs.c` + `zvfs/zvfs_hook.c`,从“单线程 + 接口不全”演进到“可支撑 RocksDB 多线程并有性能收益”。
|
||
>
|
||
> 约束:**分阶段、可中断、可重入**;每阶段都必须有独立可验证的正确性门槛。
|
||
|
||
---
|
||
|
||
## 0. 执行规则(必须遵守)
|
||
|
||
1. 一次只做一个阶段;阶段内通过验收前不得进入下一阶段。
|
||
2. 每阶段结束后必须更新本文“阶段状态”。
|
||
3. 每阶段保留一个“恢复锚点”:
|
||
- 可编译的代码状态;
|
||
- 一组固定验证命令;
|
||
- 一条阶段 commit(建议)。
|
||
4. 出现阻塞时只回滚当前阶段改动,不回滚已完成阶段。
|
||
5. 非 `/zvfs` 路径行为必须始终透传到 libc,不得回归。
|
||
|
||
---
|
||
|
||
## 1. 阶段状态(可重入入口)
|
||
|
||
- [ ] Phase 1: 建立基线与 syscall 覆盖清单(RocksDB 实际需求)
|
||
- [ ] Phase 2: 修正现有 hook 语义缺口(不改线程模型)
|
||
- [ ] Phase 3: 补齐 RocksDB P0 接口集(单线程可跑通)
|
||
- [ ] Phase 4: 补齐 RocksDB P1 接口集(目录/锁/截断)
|
||
- [ ] Phase 5: 多线程执行模型改造(Worker + 请求队列)
|
||
- [ ] Phase 6: 并发正确性与崩溃恢复强化
|
||
- [ ] Phase 7: 面向 RocksDB 的性能优化与验收
|
||
|
||
> 重入方式:中断后先看本区,继续第一个未完成阶段。
|
||
|
||
---
|
||
|
||
## 2. 当前代码关键问题(来自 `zvfs.c`/`zvfs_hook.c`)
|
||
|
||
1. 单线程瓶颈:所有请求依赖 `global_thread + waiter` 同步轮询,调用线程直接 `spdk_thread_poll`,不适合并发。
|
||
2. hook 覆盖不足:当前仅 `open/read/write/close/unlink/lseek`,RocksDB 常用接口大量缺失(如 `pread/pwrite/fstat/fsync/fdatasync/ftruncate/rename/openat/fcntl` 等)。
|
||
3. 语义缺口:`O_TRUNC/O_APPEND/errno` 等语义不完整;元数据保存与文件操作一致性较弱。
|
||
4. 线程安全缺口:`g_fs`、`fd_table`、`dirent`、`open_count` 等无锁并发访问。
|
||
|
||
---
|
||
|
||
## 3. 分阶段计划
|
||
|
||
## Phase 1: 建立基线与 syscall 覆盖清单(RocksDB 实际需求)
|
||
|
||
### 目标
|
||
明确 RocksDB 在当前环境下真实调用了哪些文件接口,得到“必须实现”的优先级列表。
|
||
|
||
### 任务
|
||
1. 编译基线:`make`、`make -C test`。
|
||
2. 跑现有回归:`make run-test`(普通路径和 `/zvfs` 路径各一轮)。
|
||
3. 用 `strace -f` 跑 `db_bench`(或最小 RocksDB workload),导出 syscall 统计。
|
||
4. 产出 `docs/rocksdb-syscall-matrix.md`(P0/P1/P2 分类 + 是否已支持)。
|
||
|
||
### 验收
|
||
- 能给出可复现命令与 syscall 清单。
|
||
- 明确哪些接口是“阻塞 RocksDB 跑通”的 P0。
|
||
|
||
### 中断/重入
|
||
- 产物文件存在:`docs/rocksdb-syscall-matrix.md`。
|
||
- 下一次从该清单继续,不需要重跑全量分析。
|
||
|
||
---
|
||
|
||
## Phase 2: 修正现有 hook 语义缺口(不改线程模型)
|
||
|
||
### 目标
|
||
在保持单线程架构不变的前提下,先把已有接口的 POSIX 语义修正到可用状态。
|
||
|
||
### 任务
|
||
1. 修复 `open` 标志位语义:至少覆盖 `O_CREAT/O_EXCL/O_TRUNC/O_APPEND`。
|
||
2. 统一返回值与 `errno`:`zvfs_*` 失败路径映射到标准 errno。
|
||
3. 修复元数据 I/O 基础问题(如加载/保存边界、错误传播、close 使用 real 函数)。
|
||
4. 增加小型语义回归测试(可放 `test/`)。
|
||
|
||
### 验收
|
||
- 现有 `test` 全通过。
|
||
- 新增语义测试通过。
|
||
- 非 `/zvfs` 路径行为无回归。
|
||
|
||
### 中断/重入
|
||
- 保留旧逻辑兼容开关(如宏开关)直到本阶段稳定。
|
||
- 提交后可独立回退,不影响后续接口扩展。
|
||
|
||
---
|
||
|
||
## Phase 3: 补齐 RocksDB P0 接口集(单线程可跑通)
|
||
|
||
### 目标
|
||
先实现 RocksDB “必须有才能启动并跑基础 workload” 的接口集合。
|
||
|
||
### P0 接口(优先)
|
||
- 打开类:`open64/openat/openat64/__open_2/__open64_2`
|
||
- 偏移 I/O:`pread/pread64/pwrite/pwrite64`
|
||
- 元数据:`stat/lstat/fstat/fstatat/access`
|
||
- 持久化:`fsync/fdatasync`
|
||
- 变更:`rename/renameat/unlinkat`
|
||
|
||
### 任务
|
||
1. 在 `zvfs_hook.c` 增加 real 函数指针与统一初始化(建议 `pthread_once`)。
|
||
2. 所有新 hook 必须支持“路径过滤 + 非 `/zvfs` 透传”。
|
||
3. 对不支持的语义明确返回 `ENOTSUP/EOPNOTSUPP`,禁止静默成功。
|
||
4. 增加 `pread/pwrite` 偏移语义测试。
|
||
|
||
### 验收
|
||
- `db_bench` 单线程基础项可跑:`fillseq/fillrandom/readrandom`。
|
||
- `strace` 显示 P0 接口已被正确接管或透传。
|
||
|
||
### 中断/重入
|
||
- 每新增一类 hook 单独 commit(open/io/meta/sync/rename)。
|
||
- 中断后按未完成类别继续,不影响已完成类别。
|
||
|
||
---
|
||
|
||
## Phase 4: 补齐 RocksDB P1 接口集(目录/锁/截断)
|
||
|
||
### 目标
|
||
支持 RocksDB 更完整运行路径,尤其是锁文件、目录操作、截断相关语义。
|
||
|
||
### P1 接口
|
||
- `ftruncate/truncate`
|
||
- `fcntl`(至少 `F_SETLK/F_SETLKW/F_GETLK/F_UNLCK`)
|
||
- `mkdir/rmdir/opendir/readdir/closedir`
|
||
- `link/symlink/readlink`(按 strace 结果决定)
|
||
|
||
### 任务
|
||
1. 为目录与锁引入最小可用实现(先保证正确,再优化)。
|
||
2. 对暂不支持特性返回明确 errno,不可假成功。
|
||
3. 补充目录/锁语义测试(多进程或多线程最小场景)。
|
||
|
||
### 验收
|
||
- `db_bench --threads=4` 可稳定执行基础 workload。
|
||
- 无明显语义错误(锁冲突、目录丢失、truncate 异常)。
|
||
|
||
### 中断/重入
|
||
- `fcntl` 与目录接口分成两个子里程碑。
|
||
- 任一子里程碑完成即可落盘并停在该点。
|
||
|
||
---
|
||
|
||
## Phase 5: 多线程执行模型改造(Worker + 请求队列)
|
||
|
||
### 目标
|
||
把当前“调用线程主动 poll”的模式改为“专用 worker poll + 线程安全请求提交”,解决单线程瓶颈。
|
||
|
||
### 任务
|
||
1. 新增 `zvfs_worker`:专用线程、请求队列、完成通知(cond/futex/eventfd 均可)。
|
||
2. 将 `waiter` 路径替换为 `submit_and_wait` 路径;调用线程不再直接 `spdk_thread_poll`。
|
||
3. 增加并发保护:
|
||
- 全局锁:`g_fs/mount/fd_table/dirent`;
|
||
- 文件锁:`offset/blob 生命周期`;
|
||
- 明确锁顺序避免死锁。
|
||
4. 保留回退开关(例如 `ZVFS_USE_LEGACY_WAITER`)直到压测稳定。
|
||
|
||
### 验收
|
||
- 线程数 4/8 下功能测试稳定,无死锁/崩溃。
|
||
- CPU 火焰图或日志能证明调用线程不再承担 SPDK poll。
|
||
|
||
### 中断/重入
|
||
- 先实现 worker 生命周期,再迁移 read/write,再迁移 open/close。
|
||
- 每迁移一类操作即可独立验证与提交。
|
||
|
||
---
|
||
|
||
## Phase 6: 并发正确性与崩溃恢复强化
|
||
|
||
### 目标
|
||
在多线程基础上补齐一致性:元数据、删除/关闭竞态、异常退出后的可恢复性。
|
||
|
||
### 任务
|
||
1. 元数据持久化改为“原子写入流程”(临时文件 + fsync + rename)。
|
||
2. 修复 `unlink/close/open` 并发竞态(引用计数与删除时机)。
|
||
3. 建立故障注入测试:`kill -9`、中途断电模拟(最小可复现脚本)。
|
||
4. 明确恢复策略与错误可观测日志。
|
||
|
||
### 验收
|
||
- 故障注入后可重新挂载并读到一致元数据。
|
||
- 并发 `open/unlink/close` 压测无崩溃无悬挂。
|
||
|
||
### 中断/重入
|
||
- 先落地元数据原子写入,再处理并发删除,再做故障注入。
|
||
|
||
---
|
||
|
||
## Phase 7: 面向 RocksDB 的性能优化与验收
|
||
|
||
### 目标
|
||
在正确性稳定后进行性能优化,并给出“确实加速 RocksDB”的证据。
|
||
|
||
### 任务
|
||
1. 优化优先级:
|
||
- 对齐写 fast-path 与非对齐 RMW 优化;
|
||
- DMA buffer 复用/池化;
|
||
- 减少全局锁粒度;
|
||
- 批量/延迟元数据刷新策略。
|
||
2. 构建统一 benchmark 脚本:
|
||
- 对照组 A:不使用 `LD_PRELOAD`;
|
||
- 对照组 B:`LD_PRELOAD=./libzvfs.so` + `/zvfs` 路径。
|
||
3. 指标:吞吐(ops/s)、P99 延迟、CPU 使用率、失败率。
|
||
|
||
### 验收(最终目标)
|
||
- 在至少一个 RocksDB workload 上达到可重复的性能提升(建议目标 >= 1.3x,最终以实测为准)。
|
||
- 提供完整报告:命令、环境、结果表、结论与剩余瓶颈。
|
||
|
||
### 中断/重入
|
||
- 每个优化项必须可单独开关,可单独回滚。
|
||
|
||
---
|
||
|
||
## 4. 阶段验收矩阵(执行时打勾)
|
||
|
||
- [ ] P1 已产出 syscall matrix 且可复现。
|
||
- [ ] P2 已完成现有语义修复且回归通过。
|
||
- [ ] P3 已实现 P0 接口并单线程跑通 RocksDB。
|
||
- [ ] P4 已实现 P1 接口并多线程稳定运行。
|
||
- [ ] P5 已切换到 worker 并通过并发稳定性测试。
|
||
- [ ] P6 已通过崩溃恢复与并发竞态测试。
|
||
- [ ] P7 已完成性能对照并证明加速收益。
|
||
|
||
---
|
||
|
||
## 5. 每阶段固定输出模板(执行时复用)
|
||
|
||
1. 改动清单(文件 + 关键点)。
|
||
2. 验证命令与结果。
|
||
3. 风险/已知问题。
|
||
4. 阶段状态勾选更新。
|
||
5. 下一阶段入口条件是否满足。
|
||
|