RustFS 元数据 — 全新构建最终设计(中文版)
RustFS 元数据 — 全新构建最终设计(中文版)
文档编号: RUSTFS-MD-DESIGN-005(权威 / 单一事实来源)
状态: 草案
版本: 1.0.0
前提: 没有历史数据。全新构建。永不迁移。
归并: RUSTFS-MD-DESIGN-002(内核)+ 004(无共存),并**彻底废止**
CHG-001 与 DESIGN-003 的迁移/共存内容。
语言: 中文 (English mirror: rustfs-final-greenfield-EN.md)
1. 新前提及其删除项
没有历史数据。系统全新构建。因此:
彻底删除(不再属于任何方案):
✗ 一切迁移(无 rustfs-migrate 工具、无重条带化、无选项 A/B 抉择)
✗ xl.meta 的一切形态——运行时没有、迁移输入也没有、根本没有
✗ 一切遗留解码待办(O1 旧 bitrot、O5 复现旧 EcDist……)
✗ 任何向后兼容脚手架
结果:设计纯粹向前。系统见到的唯一数据,是它以自己的格式亲手写下的数据。
2. 原则重述:"最小改动" → "最大复用"
早先"对 RustFS chunk 改动最小"的目标,是为了不破坏运行中的数据。既无历史数据, 重述为:
复用经过验证、难做对的算法(Reed-Solomon、bitrot、磁盘 I/O、heal 重建)。 在其上构建一个干净的、公式原生的 chunk 服务。不带入任何对象存储 / xl.meta 语义。
作为库复用(不重写): ecstore-core(RS 编解码)、bitrot 读写器、磁盘 I/O 层、
heal 重建数学、quorum 逻辑。
全新构建(干净、无遗留):chunk 服务外壳、on-disk chunk 格式、公式放置、KV 元数据。
永久丢弃: SetDisks 对象语义、xl.meta、对象→集合的名称哈希、DDir、
EcDist 存储、按版本的对象目录布局。
由于 on-disk chunk 格式从零定义,我们将其设计为自校验(内联流式 bitrot)—— 因此元数据从不携带逐块哈希,解码路径只需布局参数。
3. 权威架构(全貌,一页)
客户端 无状态 MDS 事务型 KV
┌──────────────────┐ ┌────────────────────┐ ┌──────────────────┐
│ 元数据 RPC stub │──控制────▶ 文件系统语义 │──事务─▶ INOD / DENT / │
│ 放置引擎 │ │ inode 区段分配 │ │ VIDX / VOLU / │
│ 集群映射表缓存 │◀─布局────│ 放置引擎 │ │ EXTM / PEND/DELQ │
│ chunk I/O ────────┼─数据─┐ │ (无数据路径) │ │ (TiKV / 内嵌) │
└──────────────────┘ │ └────────────────────┘ └──────────────────┘
▼
┌──────────────────────────────────────────┐
│ CHUNK 服务(全新外壳) │
│ · 公式原生、按 chunk-key 寻址 │
│ · 自校验的 on-disk chunk 格式 │
│ 复用:RS · bitrot · 磁盘 I/O · heal │
└──────────────────────────────────────────┘
3.1 元数据 = KV 行
INOD | inode_id → InodeAttr { size, mode, uid, gid, times, kind, nlink,
body, sys_meta }
body = Inline(bytes) # 仅 ≤ ~1 KB(罕见)
| Slice{container_id,off,len} # 小文件 → 打包
| OwnLayout(Layout) # 大文件 → 自有 chunk
DENT | parent_ino | name → { child_ino, kind }
VIDX | bucket | key → [ { version_id, inode_id, mtime, delete_marker } ]
EXTM | volume_id | extent → ExtentEntry (块卷——唯一的索引)
VOLU | volume_id → Volume
CMTA | container_id → ContainerMeta { layout, capacity, used, dead_bytes,
sealed } (小文件打包)
IALOC → 批量 inode 计数器
PEND | file_id → 写意图(崩溃 GC)
DELQ | file_id → 删除墓碑(chunk GC)
MPUP/MPRT | upload_id... → 分段暂存
KV 只存元数据——绝不存文件数据本体。 Inline 仅保留给退化的 ≤ ~1 KB 情形,
不是小文件方案(见 §3.5)。AI 数据集与代码/依赖树(node_modules、pip/conda)
天然有亿万级小文件——它们的元数据(inode + dentry 行)在 KV 中可扩展,正如 3FS
所证;但它们的数据绝不能 inline。
3.2 布局 = 纯公式(每文件一份描述符,内联于 inode)
struct Layout {
file_id: u64, ec: EcParams, stripe_unit: u32, chunk_size: u64,
placement_seed: u64, cluster_map_epoch: u64,
}
// 所有位置均计算得出,逐 chunk 零存储:
// set = HRW(file_id, chunk_index, seed, cluster_map[epoch])
// path = deterministic(file_id, version, chunk_index)
// dist = deterministic(file_id, chunk_index)
// hashes = 内联于分片(自校验)
3.3 数据面 = 客户端 ↔ chunk 直连(MDS 永不在路径上)
open(): 客户端一次性取得 Layout + 集群映射 epoch → 缓存(对一次写入文件布局不可变
→ 在文件生命周期内缓存)
read(): 客户端本地计算位置 → ReadChunk(chunk_key, off, len) 直连 chunk 服务器。
零 MDS 流量。
write(): 客户端 → WriteChunk 直连 chunk 服务器(RS 编码在服务内部进行);
随后 MDS CommitWrite(size, mtime)——仅元数据。
3.4 保持简单的干净原语
极小文件(≤~1KB) → inode.body = Inline(罕见;仅退化优化)。
小文件 → 打包进容器(§3.5)——亿万级小文件的解法。
inode.body = Slice{container_id, off, len}。
大文件 → inode.body = OwnLayout(§3.2 的公式路径)。
版本 → VIDX;每个版本是自己的 inode + 确定性 chunk 路径。
分段 → 暂存 parts;Complete 时写入 formula chunk(仅当 part 边界不对齐
时重条带化——少见)。
块卷 → EXTM 区段映射表(精简/COW),唯一的枚举索引。
崩溃一致性 → 先数据,后 KV 提交;PEND 写意图 + GC 巡查器。
3.5 小文件打包(一等公民——亿万级小文件)
AI 数据集(数百万微小样本/token)与代码/依赖树(node_modules、pip/conda、 构建产物)动辄数亿到数十亿小文件。把它们的数据塞进 KV 是致命的;为每个微小 文件单独做 EC/副本分片又会吃掉每分片的固定开销底。解法是打包,且与公式布局 天然契合:
容器(Container) = 一个大的内部对象(如 256 MiB),位于保留的内部命名空间。它在
内部就是一个普通大文件 → 自己的 OwnLayout、公式放置、EC。
完全复用大文件 chunk 路径。
写 = 把小文件字节追加进该写入者当前打开的容器;预留
(container_id, offset, length);提交 inode,body =
Slice{container_id, offset, length}。容器达到上限即封存
→ 封存后不可变 → 适合 EC。
读 = inode → Slice{container_id, offset, length} → 容器的 Layout
(可缓存;容器极少且热)→ 计算 chunk 位置 → 直接向 chunk
服务器做字节范围读。MDS 不在数据路径上。小切片落在一个 EC
数据分片 fragment 内 → EC 部分读 ~1x 放大(复用 LLD-002 随机读)。
删除/GC = 给切片打墓碑;按容器记 dead_bytes(CMTA)。后台压实器把稀疏
容器里的存活切片重打包进新容器、再释放旧容器(log-structured GC)。
规模核算:10 亿 × 32 KiB 文件 → 数据打包进约 13 万个容器(各 256 MiB),高效 EC (~1.5x,而非小文件分片底);元数据约 10 亿 inode + dentry 行在 KV(~几百 GB, 分布式——KV 的本职)。这就是 Haystack / SeaweedFS 模型,适配到我们的公式布局。
代价(已接受,见 PLAN-001 Track B):压实 GC;批量快速建小文件的客户端批处理; 每容器 append 偏移预留(用每写入者/会话一个打开容器来限制争用)。
4. Chunk 服务:复用算法,构建干净外壳
需记录的一个设计决策:
决策:构建一个**全新的**薄 ChunkStore,直接调用被复用的库,而非改造既有的、
面向对象的 SetDisks。
原因:SetDisks 把 EC + 分布 + xl.meta + 对象级 quorum/锁 耦合在一起。既无遗留数据,
改造它会把对象存储假设带入 chunk 层。一个组合 ecstore-core(RS)、磁盘层、
bitrot/heal 模块的全新 ChunkStore 更干净、且公式原生。
代价:比改造多一些新代码——但这是彻底迭代的选择,避免继承我们永不使用的对象语义。
ChunkStore 接口(按 chunk-key 寻址、字节范围、自校验):
write_chunk(chunk_key, offset, data, sync) -> ()
read_chunk(chunk_key, offset, length) -> bytes (校验内联 bitrot)
delete_chunk(chunk_key) -> ()
// RS 编解码、分片放置(按公式)、bitrot、heal:复用库
5. 构建计划(无迁移——只是构建)
B1 rustfs-kv:TransactionalKv trait + 内嵌后端(redb/RocksDB)+ TiKV。
B2 rustfs-inode:InodeAttr、Layout、DentValue、Volume、ExtentEntry 类型。
B3 rustfs-placement:共享 HRW 放置 + 集群映射表(MDS 与客户端共用)。
B4 rustfs-mds:无状态服务、inode 区段分配器、文件系统语义事务、
gRPC(命名空间 + OpenLayout + GetClusterMap + CommitWrite + 卷)。
B5 chunk-service:复用 RS/bitrot/磁盘/heal 的全新 ChunkStore;自校验 on-disk
chunk 格式;chunk-key 寻址。
B5b packing:容器管理(append/seal/Slice)、小文件打/解包、压实 GC。
复用 B5(容器即大的内部文件)。
B6 rustfs-client / FUSE:放置引擎、集群映射缓存、直连 chunk I/O、
预读(顺序)+ 随机读路径(LLD-002);
客户端把小文件写批量化为容器 append。
B7 S3 + 块(iSCSI/NVMe-oF)前端,构建于同一 MDS + ChunkStore 之上。
B8 HA:将 MDS 指向 TiKV → 免费获得分布 + HA。无自研 Raft。
没有接缝/双写/切换/迁移的阶段——这些概念已不存在。
6. 剩下的少数真问题
Q1 KV 后端:TiKV(Rust 原生、集群、推荐)对比 内嵌 redb/RocksDB(单机)。
先交付内嵌,再为 HA 加 TiKV。
Q2 inline 上限(仅退化的 ≤~1 KB——海量小文件走打包 §3.5,**不** inline)。
inline 保持极小,使 KV 永不持有数据本体。
Q3 定义 on-disk chunk 格式:stripe_unit、内联 bitrot 块大小、chunk-key 路径方案。
(自由选择——无遗留约束。)
Q4 按 大小 × 访问模式 定默认存储策略:
极小 ≤~1KB → Inline;小 → 打包容器(§3.5);大 ≥~1MiB → EC OwnLayout;
随机/部分写(块卷、可变文件)→ 副本(副本数 = m+1,各档容错一致)。
Q5 ChunkStore:确认"全新外壳"决策 vs 改造 SetDisks(§4)。
Q6 容器大小 + 打包局部性(按写入者/时间,或可选按目录子树)+ 压实触发阈值
(dead_bytes 比例)。
这些都是向前的设计选择,而非兼容性约束。
7. 小结
前提 : 无历史数据 → 全新构建 → 无迁移、无 xl.meta、处处皆无。
原则 : 复用经验证的 RS/bitrot/磁盘/heal;构建干净的公式原生外壳。
元数据 : 事务型 KV 之上的无状态 MDS;INOD/DENT/VIDX 行。KV 只存元数据,绝不存
文件数据本体。
布局 : 纯公式;逐 chunk 零元数据;分片自校验。
小文件 : 打包进大容器(inode 存 Slice 指针);亿万级小文件可扩展;KV 永不 inline
数据本体。
数据面 : 客户端 ↔ chunk 直连;MDS 永不在数据路径上。
Chunk : 全新薄 ChunkStore,复用硬算法;无对象/xl.meta。
HA : TiKV 提供分布 + 一致性;无自研共识。
共存/迁移: 无。系统只见自己的格式。
这就是简化的终态:单一原生模型,经验证的数学作为库复用,毫无历史包袱。
英文镜像:rustfs-final-greenfield-EN.md。权威设计;废止 RUSTFS-CHG-001 与 RUSTFS-MD-DESIGN-003 的迁移/共存内容。