Storforge
里程碑预计 50 分钟

里程碑④:在 forgefs 上编译内核模块

终极验收:挂载 forgefs,跑一次真实的编译任务,中途拔掉一个节点 —— 编译必须成功,产物 checksum 必须正确。

学完这节你能做到

  • 通过全部端到端验收项
  • 记录并解释性能数据:吞吐、元数据 ops、重建速度
  • 写出 forgefs 与 Weka 的差距清单(诚实版)

验收现场:弄不坏才算数

L0 立过的规矩今天全额兑现:能跑不算数,弄不坏才算数。这节课没有新概念, 只有三个验收项和一份诚实的差距清单。验收标准提前写死 —— 这是你运维时做重大变更的习惯(方案里必须有验证步骤和回退条件), 现在用同样的纪律验收自己造的文件系统。

环境:3 个 forge-node(每节点若干数据盘目录)+ 1 个 forge-fuse 挂载点。 EC 4+2 意味着 6 个分片要落在不同故障域 —— 3 节点场景按「每节点 2 个故障域」配置放置约束, 挂掉一个节点最多丢每条带 2 个分片,恰好压着 4+2 的容错上限。这不是巧合, 是我们故意把验收压在最坏许可条件上。

验收项 1:pjdfstest 子集

pjdfstest 是 POSIX 语义的考官,一共 8000 多个用例。forgefs 不做 link/chflags/ 特殊设备文件,所以跑声明过的子集,但子集内零容忍:

# 在挂载点上跑,只跑 forgefs 声明支持的语义组
cd /mnt/forgefs && mkdir pjd && cd pjd
prove -rv /opt/pjdfstest/tests/{open,mkdir,rmdir,unlink,rename,chmod,chown,truncate}

通过标准:上述 8 组全部 PASS,0 个 FAIL。跑不过的常见坑,按出现频率排:

  • errno 翻译错(ENOTEMPTY 写成 EEXIST,ENOTDIR 和 ENOENT 混用)—— fuse-client 课警告过
  • rename 的边界:rename 到自身要成功;rename 目录到自己的子目录要回 EINVAL
  • truncate 缩小文件后再读,尾部必须是 0 而不是旧数据 —— 这个 bug 藏在 extent map 的截断逻辑里,危害等级是「静默脏数据」,pjdfstest 抓不到的话编译验收也会抓到
i声明子集不丢人,静默不支持才丢人

NFS、CephFS、各家对象网关的 POSIX 层全都只支持子集,关键是写进文档并在 不支持的调用上返回明确的 ENOSYS,而不是装作成功。运维时你排查过 「应用在某 NAS 上行为诡异」的工单,根源常是文件系统对不支持的语义返回了假成功。

验收项 2:编译 + 拔节点,产物必须分毫不差

主菜。完整演练脚本,照做:

# 1. 基线:在本地 ext4 编译同版本 zlib,记录产物 checksum
cd /tmp && git clone https://github.com/madler/zlib && cd zlib
./configure && make -j8
sha256sum libz.a > /tmp/golden.sha256          # 黄金标准

# 2. 在 forgefs 上重来,编译进行到一半时杀节点
cd /mnt/forgefs && git clone /tmp/zlib zlib && cd zlib
./configure
make -j8 &                                      # 后台编译
sleep 5 && ssh node-2 'systemctl kill --signal=SIGKILL forge-node'   # 编译中途硬杀
wait $!                                         # 编译必须正常结束,退出码 0

# 3. 验收三连
sha256sum -c /tmp/golden.sha256 --ignore-missing <<< "$(cat /tmp/golden.sha256 | sed 's|libz.a|/mnt/forgefs/zlib/libz.a|')"
make test                                       # 功能测试全绿
forge-cli rebuild status                        # 危险条带应为 0,重建在进行

判分标准,三条全过才算过:

  1. make 退出码 0:节点死亡期间,读走降级重构(缺 2 分片可拼回), 写照常提交到幸存节点 —— 应用无感知,这是 L3+L4 全部工作的总和
  2. libz.a 的 sha256 与 ext4 基线一致:编译产物是确定性的,checksum 不同 意味着某个环节吐了脏数据 —— 端到端 crc32c 应该在更早的位置抓住它, 两道防线都漏了才会走到这里
  3. make test 全绿:防止「checksum 恰好对了但中间文件坏了」的侥幸

然后把节点拉回来,验证第二个半场:systemctl start forge-node, 观察 rebuild status 里积压清零、孤儿回收,全程挂载点不许有任何 EIO

Checkpoint单选

验收为什么坚持对比编译产物的 sha256,而不是只看编译成功?

验收项 3:重建期间 P99 受控

第三项验证 rebuild 课的限速引擎。用 forge-bench(L2 的老朋友)当业务负载:

# 1. 铺底数据 + 稳态基线
forge-bench --target /mnt/forgefs --rw randrw --size 8GiB --qd 16 --runtime 120 \
            --hist /tmp/baseline.hgrm          # 记录稳态 P99,设为 X

# 2. 负载不停,杀一个节点触发重建,再压 10 分钟
ssh node-3 'systemctl kill --signal=SIGKILL forge-node'
forge-bench --target /mnt/forgefs --rw randrw --size 8GiB --qd 16 --runtime 600 \
            --hist /tmp/degraded.hgrm

通过标准写成数字:重建期间业务 P99 不超过稳态基线的 3 倍,且无单次超 1 秒的请求; 同时 forge-cli rebuild status 的速率轨迹要能看到退让生效(业务压力大时速率下调)。 3 倍不是拍脑袋:降级读本身贵 4 倍读放大,叠加重建争抢,2~3 倍是 FUSE + tokio 架构下的现实目标;如果测出 10 倍,先查限速器有没有真的在 IO 路径上生效 (常见翻车:重建的限了速,重构后的忘了 acquire)。

AI 结对:把三个验收项自动化成一条命令pair with ai

验收脚本本身也要 review —— 它是里程碑的裁判,裁判不能有 bug。 重点检查两处:02 脚本里 sha256 校验失败时会不会被 set -e 之外的管道吞掉 (管道中段失败要靠 pipefail,确认它真的在);03 脚本解析 forge-bench 输出的方式 是否脆弱(建议让 forge-bench 直接输出 json,而不是 grep 人类可读文本)。 裁判可靠了,后面 L5 的性能回归 CI 直接复用这套脚本。

为 forge 项目编写端到端验收套件 scripts/milestone-fs/:

1. run-all.sh:按顺序执行三个验收项,任何一项失败立即退出并打印失败摘要;成功则输出一张验收报告(markdown)到 docs/milestones/l4-report.md;
2. 01-pjdfstest.sh:挂载 forgefs,跑 open/mkdir/rmdir/unlink/rename/chmod/chown/truncate 八组,解析 prove 输出,FAIL 数非 0 即失败;
3. 02-compile-kill.sh:先在 ext4 编译 zlib 记录 sha256 基线;再在 forgefs 上编译,make 启动 5 秒后 SIGKILL 掉配置文件指定的一个 forge-node,等待编译结束,校验退出码、sha256、make test 三连;之后重启该节点,轮询 forge-cli rebuild status 直到积压清零,全程 dmesg 和挂载进程日志里 grep 不到 EIO;
4. 03-p99-under-rebuild.sh:forge-bench 先跑 120 秒稳态取 P99 基线,再杀节点后跑 600 秒,断言期间 P99 不超基线 3 倍、max 不超 1 秒,并把两条延迟直方图数据留档;
5. 所有脚本 set -euo pipefail,节点地址、挂载点、阈值全部读 scripts/milestone-fs/env.conf,不许硬编码;
6. 报告模板包含:每项的通过状态、关键数字(pjdfstest 用例数、编译耗时对比、P99 基线与重建期数值、重建总时长)、失败时的诊断线索章节。

写完先空跑(dry-run 模式,打印将执行的命令)给我检查,确认后再真跑。

差距清单(诚实版):forgefs 离 Weka 有多远

验收通过不等于追平 Weka。这份清单是里程碑的必交作业 —— 每一项写清 「差什么、差多少、为什么不做」,它同时是你面试或复盘时最有说服力的材料:

差距项Wekaforgefs差多少(量级)
客户端内核模块 + 共享内存FUSE 两次往返两次拷贝小 IO 的 IOPS 差 5~10 倍
网络栈DPDK 用户态,零拷贝tonic over 内核 TCP单连接延迟差 3~5 倍
NVMe 驱动SPDK 用户态轮询io_uring(仍过内核块层)单盘延迟差 1.5~2 倍
CPU 模型thread-per-core 绑核 + busy poll,NUMA 感知tokio work stealing,不绑核P99/P50 比值差 2~4 倍
元数据粒度64k bucket,细粒度接管256 分片均衡与接管粒度粗 250 倍
小写聚合精细的日志分级与整理调度单级日志 + 定期整理整理风暴下延迟抖动更大
快照/克隆有,基于元数据分层功能缺失
分层(S3 下沉)有,冷数据下沉对象存储功能缺失
大目录分裂留了 DirLayout 演进位,未实现单目录百万级会撞墙
客户端缓存一致性分布式租约固定 TTL 1 秒多客户端语义弱一档

写这份清单的规矩:数字要有出处(自己测的写测法,引用的注来源), 「为什么不做」只允许两种诚实答案 —— 教学价值低,或工程量超出课程范围。 禁止写「未来会支持」这种既不承诺也不放弃的话,那是产品 PPT 的语言,不是工程师的。

AI 结对:复盘 L4 —— 让 AI 当质询方pair with ai

里程碑的最后一步不是庆祝,是复盘 —— 让 AI 反过来质询你,是检验 「这个系统是你造的,还是 AI 替你造的」的试金石。四步循环走到这里闭环: 你出的 spec、你 review 过的代码,现在要能在追问下为每个决策辩护。 答不上来的地方就是三条红线失守的地方,回去重读对应的代码,别跳过。

我刚完成 forgefs 的 L4 里程碑:pjdfstest 子集通过、挂载后编译 zlib 中途杀节点产物 checksum 正确、重建期 P99 控制在稳态 3 倍内。请你扮演一位苛刻的存储系统面试官,基于 forge 仓库的代码向我提问:

1. 先读 forge-fs 和 forge-fuse 的代码,挑 3 处你认为最可能在生产环境出问题的设计,逐一质询我(每次一个问题,等我回答后追问,不许一次全抛出来);
2. 质询范围至少覆盖:跨分片 rename 的崩溃恢复、extent 指针切换的提交语义、重建限速对突发业务的响应速度;
3. 我答错或答含糊时,给出正确分析并标注对应代码位置;
4. 结束后给我的回答打分,并列出 3 个我应该重点补课的主题。

整个过程用中文,问题要具体到代码行为,不要问概念背诵题。

小结

  • 验收项 1:pjdfstest 声明子集零 FAIL;不支持的语义返回 ENOSYS,静默假成功是运维工单之源
  • 验收项 2:编译中途 SIGKILL 节点,make 退出码 0 + 产物 sha256 与 ext4 基线一致 + make test 全绿;节点回归后重建清零且全程无 EIO
  • 验收项 3:重建期业务 P99 不超稳态 3 倍、无超 1 秒请求;超标先查限速器是否漏了重构写路径
  • 差距清单要有数字、有出处、有诚实的「为什么不做」;它是你能力的证明,不是系统的耻辱柱
  • 里程碑④达成:forge 从对象存储长成了能扛节点故障的文件系统 —— L5 解决最后一个问题:demo 和产品之间的距离