PLAN — 完成宗旨
宗旨: 自研 Rust Swift-native FUSE client。 ProxyFS distributed-client/lease + JuiceFS chunk/slice/block+compaction
- rclone native Swift + GeeseFS async/parallel IO + Mountpoint 高吞吐 data path ⟹ metadata 与 data 分离 / control plane 强一致 / data plane 完全 scale-out / object 永远 immutable / mutation 只发生在 metadata
Phase 1(thin adapter,已交付)达成度 ~45%:native Swift 数据路径 ✓、 scale-out 客户端 ✓、对象级不可变 ✓。缺 chunk 数据模型、强一致控制面、 并行读吞吐。本计划以完成宗旨为唯一目标,不再新增 thin-adapter 功能。
目标架构
control plane (强一致)
┌───────────────────────────────────────┐
│ MetadataEngine (trait) │
│ ├─ P1: SQLite (单机验证数据模型) │
│ └─ P2: etcd + lease (多客户端强一致) │
│ inode: size/mode/times/version │
│ chunk map: chunks → slices → blocks │
│ 原子 commit: CAS on version │
└───────────────┬───────────────────────┘
│
┌───────────────▼───────────────────────┐
│ FUSE client (ChunkFs, 每节点一个) │
│ lookup/getattr/readdir ← engine │
│ read ← blocks 并行 GET + hole 补零 │
│ write → slice buffer → blocks PUT │
│ → metadata commit (先数据后元数据) │
│ compaction: 后台合并 slices │
│ GC: 孤儿 blocks 异步回收 │
└───────────────┬───────────────────────┘
│ data plane (完全 scale-out, 直连 Swift)
▼
Swift: 容器 <fsid>-data, 对象 = immutable blocks
文件 ≠ 对象; 对象永远只写一次
数据模型(JuiceFS 对齐,参数可配)
inode
├─ chunk 0 [0, 64MiB)
│ ├─ slice A start=0 blocks=[b0,b1] ← 一次 flush 的产物
│ └─ slice B start=1MiB blocks=[b2] ← 随机写 = 新 slice, 不读旧数据
├─ chunk 1 [64, 128MiB)
│ └─ slice C ...
CHUNK_SIZE = 64 MiB(逻辑),BLOCK_SIZE = 4 MiB(物理,可配)- block 对象名:
<inode-hex>/<chunk>/<slice-id>/<block-idx>,全局唯一、永不覆盖 - slice-id 由 metadata 引擎分配(单调),保证跨客户端不冲突
- chunk 未覆盖区间 = holes → 读补零
- 覆盖语义:先 PUT blocks(幂等,孤儿无害),再 metadata CAS commit (崩溃 = 孤儿 blocks,GC 回收;读者永远看不到半成品)
阶段与验收
| 阶段 | 内容 | 宗旨映射 | 验收 |
|---|---|---|---|
| P1 ✅ | SQLite 元数据引擎 + chunk/slice/block 读写 + 原子 commit + compaction + GC | JuiceFS 数据模型、metadata/data 分离、object immutable | 单机挂载:随机 4K 写 100MB 不重读旧数据;md5 全对;45/45 测试;双挂载并发 CAS 一致 |
| P2 ✅ | etcd 引擎(同 trait)+ open/close lease + txn CAS | ProxyFS lease、control plane 强一致 | 双节点并发写一致;EBUSY + TTL 崩溃回收实测通过 |
| P3 ✅ | 窗口放大读 + 块缓存 + inflight 去重 + path 缓存 + write-back | GeeseFS/Mountpoint 吞吐 | 热读 915MB/s、冷读 80MB/s(Swift 集群上限)、stat 3ms |
| P4 ✅ | 4 节点部署 + benchmark matrix + 一致性验证 | 整体 | docs/BENCHMARK.md 全矩阵;多节点并发写/读实测 |
里程碑(本仓库分支策略)
main= 宗旨实现主线;thin adapter 保留为--mode=raw- 每阶段合并前提:单元测试 + 集群集成验证全绿
P1 交付证据(2026-08-07, Contabo lab)
- 数据面块布局实测:100MB 文件 = chunk0(64MB,17块) + chunk1(36MB,9块);
50MB 处 8KB 随机写 = 1 个新 8KB 块,旧块零接触(
chunkfs-data容器审计) - 双挂载(同 meta.db)并发写同一文件:CAS Conflict 重试,两端读到一致结果
- compaction:30 次重叠写 → 合并为 1 块,读数据前后一致;与并发写竞争时 Conflict 恢复正确(新 slices 优先,JuiceFS 同款语义)
- hole 补零、跨 chunk 读、truncate(纯 metadata)、dir rename(纯 metadata)全过
已定决策(不再回问)
- 元数据引擎先 SQLite(数据模型验证),P2 换 etcd —— trait 隔离,写路径不变
- 文件 ≠ Swift 对象(宗旨必然推论,ProxyFS 2.x 同样放弃 bi-modal);
原始对象可见性由
--mode=raw保留 - 一致性 = lease-based close/open(ProxyFS 路线),非 close-to-open
- chunk 参数默认 64MiB/4MiB,Swift 实测后按 U 型曲线调