# ZVFS 高性能框架设计(修订版) ## 0. 当前实现进展(2026-03-03) - 已落地: - stale blob 自愈(open/create/unlink/rename 路径) - hook 层小写合并(per-fd writeback buffer,默认 128KB)+ 关键系统调用前 flush - 仍待重点优化: - 小块读路径仍是“同步提交 + 单请求往返 + 拷贝返回”,延迟和吞吐偏弱 ## 1. 现状代码中的关键问题(先于方案) 基于 `zvfs.c`、`zvfs.h`、`zvfs_hook.c`,当前主要瓶颈和风险如下: 1. **单全局执行上下文串行化** - 所有 IO 都通过 `global_thread` + `waiter()` 同步等待,天然把多线程请求串到一个 SPDK thread。 - `zvfs_t` 里只有一个 `channel`,读写都走这一个 channel,无法利用多核并行。 2. **等待模型是忙轮询,CPU 成本高** - `waiter()` 用紧循环 `spdk_thread_poll()`,没有阻塞等待/退避策略。 - 在高并发小 IO 下,系统容易进入“高 CPU + 低有效 QD”。 3. **全局元数据无并发保护** - `dirents/fd_table/g_dirs/g_dirfd_table/open_count/file_size` 读写没有统一锁。 - hook 层是多线程入口,当前实现有明显竞态和可见性问题。 4. **持久化与语义不完整** - `fsync/fdatasync/sync_file_range` 对 zvfs fd 基本直接返回 0,和数据库预期不一致。 - `meta_load()` 只读固定 4KB 文本,规模稍大就截断;`meta_save()` 也无崩溃一致性保证。 5. **数据路径的放大和额外开销** - 小块随机写依赖 read-modify-write;无写回缓存、无批量提交、无 IO 合并。 - per-file `dma_buf` 增长时可能反复 realloc,缺少池化和复用策略。 6. **可扩展性不足** - `dirent_find/fd_alloc` 等是线性扫描。 - 元数据、目录结构、fd 分配都偏“单点共享结构”,随着文件数/线程数增长会抖动。 --- ## 2. 对 userplan.md 的补全与修正 `plan/userplan.md` 的方向(TLS + per-thread channel + 缩小全局锁)是正确的,但有几个需要补全的点: 1. **“每个 pthread 一个 spdk_thread”要可配置** - 对 MySQL 这类线程数可能很大的进程,严格 1:1 会导致线程对象和 channel 爆炸。 - 建议改为:默认“线程绑定 worker 池(N:M)”,支持配置成 1:1 调试模式。 2. **需要明确“文件句柄跨线程访问”的所有权规则** - 同一 fd 可能被不同 pthread 使用,必须定义 offset、cache、flush 的同步策略。 3. **batch poll 需要配套“提交队列 + 背压 + 超时”** - 仅有 `pending_queue` 不够,必须定义入队失败/队列满/超时处理。 4. **必须补上 fsync/fdatasync 的严格语义** - 尤其面向数据库:fsync 成功后应保证数据页 + 必要元数据已持久化。 5. **元数据持久化需要从“文本快照”升级为“日志+检查点”** - 否则崩溃恢复和规模都不可靠。 --- ## 3. 新框架设计(面向高性能与可重入改造) ### 3.1 分层与职责 - **Control Plane(全局)** - 管理 mount/unmount、命名空间、inode 元数据、fd 表、恢复日志。 - 低频操作(open/create/unlink/rename/mkdir/rmdir)在此层处理。 - **Data Plane(worker)** - 处理 read/pread/write/pwrite/fsync 的数据 IO。 - 每个 worker 持有:`spdk_thread + io_channel + submission_queue + completion_queue`。 - **Persistence Plane(元数据持久化)** - 元数据 WAL(append-only)+ 周期 checkpoint。 - 保障崩溃恢复和 fsync 语义。 ### 3.2 全局运行时结构 ```c typedef struct { // init/mount 生命周期 pthread_once_t init_once; pthread_mutex_t mount_mu; _Atomic int mount_state; // UNINIT/INITING/READY/FAILED/STOPPING // core spdk objects struct spdk_blob_store *bs; struct spdk_bs_dev *bs_dev; // metadata indexes pthread_rwlock_t inode_rwlock; inode_table_t *inode_by_path; // hash map: path -> inode inode_table_t *inode_by_blobid; // hash map: blobid -> inode pthread_rwlock_t fd_rwlock; fd_table_t *fd_table; // pseudo fd -> file handle // durability meta_journal_t *journal; // WAL + checkpoint // worker routing worker_pool_t *workers; // configurable N workers } zvfs_runtime_t; ``` ### 3.3 worker 模型(建议默认 N:M,可切 1:1) - 默认:`worker_count = min(online_cpu, ZVFS_IO_WORKERS)`。 - 线程第一次进入时做 TLS 绑定:`pthread_id -> worker_id`(固定绑定,减少迁移)。 - 每个 worker 独占一个 io_channel,避免全局 channel 争用。 - 等待机制:优先 `eventfd/futex + poll` 混合,避免纯忙轮询。 > 说明:若用户确认线程数有限,可配置 `ZVFS_WORKER_MODE=THREAD_LOCAL` 切 1:1,以追求极致低延迟。 ### 3.4 元数据模型 - `inode`(文件级共享对象) - `blob_id, logical_size, allocated_clusters, link/open_ref, flags` - 每 inode 一把细粒度锁(mutex/spin + 原子字段)。 - `file handle`(open 实例) - `inode*`, `flags`, `current_offset`, `handle-local state`。 - 路径索引与 blob 索引用哈希表替代线性数组。 - 目录树从 `g_dirs[]` 升级为前缀树或 hash+parent 索引,避免全表扫描。 ### 3.5 IO 路径设计 #### Read/Pread - 快路径:命中页缓存(clean/dirty)直接拷贝。 - 慢路径:提交到绑定 worker。 - 对齐大读支持直接 DMA 到用户对齐缓冲(满足约束时)。 #### Write/Pwrite - 小块随机写:写入 per-inode 页缓存(4KB 粒度),标记 dirty。 - 大块或顺序写:绕过缓存直写(或写穿策略),减少二次拷贝。 - 扩容策略:按 chunk 预分配(例如 1~8MB)减少 `resize + sync_md` 频率。 - flush 策略: - 后台刷脏(阈值/时间) - 前台 fsync 强制刷 - 合并连续页为 writev/batch IO ### 3.6 fsync/fdatasync 语义(数据库场景) - `fdatasync(fd)`: 1) 刷新该 fd 对应 inode 的脏数据页; 2) 若发生扩容,确保 blob 元数据同步完成; 3) 返回前确认提交完成。 - `fsync(fd)`: - 在 `fdatasync` 基础上,额外保证需要的命名空间/元数据日志落盘(如 size、rename 可见性)。 ### 3.7 崩溃一致性与恢复 - `meta_journal.log`(append-only,带 magic/version/CRC/seq)。 - 操作记录:`CREATE/UNLINK/RENAME/TRUNCATE/SIZE_UPDATE/ALLOC_UPDATE`。 - 启动恢复:`checkpoint -> replay WAL`。 - 周期 checkpoint(按时间或日志大小触发),避免恢复时间无限增长。 ### 3.8 锁策略与死锁规约 - 固定锁顺序:`fd_table lock -> inode lock -> journal lock`。 - IO 快路径不拿全局写锁。 - 元数据读多写少:读写锁 + inode 细粒度锁组合。 ### 3.9 可观测与调优 - 统计项(至少): - read/write IOPS、带宽、P50/P99 延迟 - cache hit ratio、dirty page 数 - flush 次数、merge 比例、resize 次数 - queue depth、排队延迟 - debug 开关: - `ZVFS_TRACE_IO=1` - `ZVFS_TRACE_META=1` - `ZVFS_WORKER_MODE`, `ZVFS_IO_WORKERS` --- ## 4. 关键行为约束(必须保持) 1. POSIX 语义不回退:`openat/rename/unlink/ftruncate/fstat/fsync` 的错误码与行为保持一致或更严格。 2. 在无 root 环境下可跑功能测试(至少支持 Malloc bdev 或已有可用 SPDK 配置)。 3. 旧接口兼容:外部仍通过 `LD_PRELOAD=.../libzvfs.so` 使用。 4. 改造过程可分阶段落地,任何阶段都可独立编译、回归、继续下一阶段。 --- ## 5. 性能目标(建议) - 与当前实现相比: - 多线程随机写 IOPS 提升 >= 2x(4~16 线程场景) - P99 延迟下降 >= 30% - CPU busy-poll 占比显著下降(可通过 perf/top 观测) - `test_single_file_perf`、`test_single_file_random_perf` 在同配置下持续稳定,无明显长尾抖动。