小块写缓存优化

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

194
plan/codexplan.md Normal file
View File

@@ -0,0 +1,194 @@
# 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 Planeworker**
- 处理 read/pread/write/pwrite/fsync 的数据 IO。
- 每个 worker 持有:`spdk_thread + io_channel + submission_queue + completion_queue`
- **Persistence Plane元数据持久化**
- 元数据 WALappend-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 提升 >= 2x4~16 线程场景)
- P99 延迟下降 >= 30%
- CPU busy-poll 占比显著下降(可通过 perf/top 观测)
- `test_single_file_perf``test_single_file_random_perf` 在同配置下持续稳定,无明显长尾抖动。