Skip to content

필사 모드: bcachefs 摘掉纠删码 experimental 标签的故事 — 以及这到底意味着什么

中文
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.

引言 — 戏剧已经落幕,现在的问题是"所以现在到底是什么状态"

过去两年里,bcachefs 的故事大多是关于人的故事 — 谁说了什么,哪个拉取请求在哪个 -rc 上被拒绝了,邮件列表上又发生了什么。这个故事已经被充分消费过了,这里没打算再重复一遍。

不如换一个对工程师真正有用的问题。截至 2026 年 7 月,bcachefs 在技术上处于什么状态? 而要弄清这一点,该看的又不是推文或者新闻标题,而是什么?

先说结论:2026 年最具体的一处变化,是纠删码摘掉了 experimental 标签。而网上流传着一句概括这件事的话,日期、版本、对象全都错了。追查这句话为什么错,构成了本文的一半内容 — 因为这个追查过程本身,恰好演示了"摘掉标签"这类宣告该怎么读。

先从事实说起 — 它是怎么从内核里消失的

先只梳理可核实的事实,都是直接从内核 git 历史里确认的。

2025 年 8 月 28 日。 Linus Torvalds 改了 MAINTAINERS 文件里的一行。提交 ebf2bfec412a,新增 1 行,删除 1 行。提交信息只有两句话 — "MAINTAINERS: mark bcachefs externally maintained / As per many long discussion threads, public and private."。LWN 第二天以Bcachefs goes to "externally maintained"报道了此事,当时的解读是代码仍留在内核里,只是不再接受上游补丁。

2025 年 9 月 29 日。 Linus 把代码本身删掉了。提交 f2c61db29f277b9c80de92102fc532cc247495cd,284 个文件,新增 0 行,删除 117,483 行。提交信息全文如下。

Remove bcachefs core code

bcachefs was marked 'externally maintained' in 6.17 but the code
remained to make the transition smoother.

It's now a DKMS module, making the in-kernel code stale, so remove
it to avoid any version confusion.

也就是说,删除的理由不是情绪,而是版本混淆 — "现在已经有 DKMS 模块了,内核里的那份副本只会变得陈旧。为了避免版本混淆,把它删掉。" LWN 的Bcachefs removed from the mainline kernel(Jonathan Corbet,2025 年 9 月 30 日)报道了这次提交。

核实起来很简单。内核标签 v6.17 下的 fs/bcachefs/ 有 260 个条目,而 v6.18 里根本没有这个目录(404)。写作本文时主线版本是 v7.2-rc3,在此期间它没有在任何发布版本里回归过。

这里的先后顺序很关键。bcachefs-tools 仓库里加入 DKMS 支持的提交(00ad9b1ed126,"add dkms support")是在2025 年 9 月 10 日,比删除提交早 19 天。Linus 写下"It's now a DKMS module"时,那不是预言,而是陈述事实 — 正因为替代的分发路径已经存在,才有可能把它删掉。

DKMS 转型,以及反转的上下游关系

项目自己的说法最简洁。bcachefs.org 首页写着:

As of 6.18, bcachefs is no longer being distributed with the kernel, for reasons too complicated to go into here. (But it might have something to do with QA). So we're shipping as a DKMS module now. (Like ZFS!). The DKMS package supports Linux 6.16 and later.

那句括号里的"(Like ZFS!)"精准概括了这一处境。和因为许可证问题永远被挡在树外的 ZFS 不同,bcachefs 是 GPL 协议的,但单看分发形式,如今两者站到了同一个位置上。

6.16 这个下限不是厂商网页的一面之词,而是能在代码里核实的。dkms/dkms.conf.in 里写死了这一点:

# kernels older than 6.16 are currently not supported
BUILD_EXCLUSIVE_KERNEL_MIN="6.16"

没有上限。取而代之的是,发布说明会紧跟最新内核 — v1.37.0 写的是"Linux 7.0 support",v1.38.7 写的是"Builds against Linux 7.2",尽管内核 7.2 那时候还处于 rc 阶段。这正是树外模块必须承担的代价:内核内部 API 一有变动就得跟着改,而这件事如今已经成了发布说明里的固定条目。

而且上下游关系反转了。bcachefs-tools 仓库里 doc/vendored-kernel-files.md 的第一段这样写道:

bcachefs source itself lives in fs/ and is developed in this repository directly — kernel-side fs/bcachefs/ is downstream of us now.

文件系统的源码如今住在 tools 仓库里的 fs/ 目录下,内核那一侧反而成了下游。根据 README,scripts/install-to-kernel.sh 会把 fs/ 复制进内核树的 fs/bcachefs/,这样就能通过 CONFIG_BCACHEFS_FS 编译了。方向彻底反过来了。

2026 年真正的新闻 — 纠删码摘掉了标签

正题从这里开始。

bcachefs 的纠删码(EC)自 2023 年 11 月起,就一直被藏在一个独立的 Kconfig 选项 CONFIG_BCACHEFS_ERASURE_CODING 背后。提交标题直白地写着这一点 — "bcachefs: Put erasure coding behind an EXPERIMENTAL kconfig option"(6a673880f92b)。哪怕在整个文件系统都还是实验性的年代,EC 也比它还要更实验一个等级。

这一点在 2026 年变了。顺序是这样的。

2026 年 2 月 2 日。 提交 f42ccc690dd7 — "bcachefs: kill CONFIG_BCACHEFS_ERASURE_CODING"。Kconfig 的门槛消失了。如果去拉今天 master 上的 fs/Kconfig 然后 grep 一下,"ERASURE" 会是 0 条命中。

2026 年 3 月 15 日,v1.37.0。 发布说明亮点的第一行就宣告了这件事。

- Erasure coding is no longer experimental; all the core functionality is
  complete.

同一个发布版本里加入了新的磁盘元数据版本号 bcachefs_metadata_version_erasure_coding,EC 那一节这样解释 — "Erasure coding is now hooked up to reconcile: degraded stripes are now automatically repaired, like other degraded data, and can be reshaped as needed."。也就是说,损坏的条带如今接上了一条路径,会像其他损坏数据一样在后台自动修复。文中还写道,分层配置和大小混杂的设备组合也应该能正常工作。

这才是新闻。 而这条新闻的日期是 2026 年 3 月 15 日。请记住这个日期。

一句广为流传的话错在三处

搜索这个话题,你会碰到一句大意如此的话 — "2026 年 6 月 17 日,bcachefs-tools v1.38.6 发布,摘掉了 experimental 标签。" 听起来煞有介事,日期和版本都写得很具体,而且完全是错的。错了三层。

第一,版本错了。 涉事的提交是 8fc60dd30a8c,标题是"ec is not experimental"。它的作者时间戳是 2026-06-18T01:58:15Z。可 v1.38.6 这个标签打于 2026-06-17T19:03:36Z — 提交比标签晚了大约 7 个小时。用 git 一查就很清楚 — v1.38.6 并不包含这次提交,第一个包含它的发布版本是 v1.38.7(2026 年 7 月 3 日)。v1.38.6 的发布说明开头是"Feature and performance release on top of v1.38.5. No on-disk format changes.",通篇一次也没出现"experimental"这个词。

第二,规模错了。 这次提交只改了一个文件(fs/opts.h)里的一行字符串,仅此而已。

-	  NULL,		"Enable erasure coding (DO NOT USE YET)")	\
+	  NULL,		"Enable erasure coding (RAID5/6, but no write hole)")\

提交信息正文只有四个词 — "hasn't been for awhile."。这是 Kent Overstreet 本人在说"这玩意儿已经不是 experimental 有一阵子了"。真正的宣告三个月前就在 v1.37.0 里发生过了,6 月的这次提交,只是迟来地把此后一直放着没管的选项说明字符串补上而已。man 手册页(bcachefs.8)被放置的时间还要更久 — 那里的"DO NOT USE YET"要到 6 月 30 日的提交 c2b1c10fdd35("docs: clarify erasure coding replica limit")才被删掉。同一个提交也再次改了 fs/opts.h 的措辞,把只存在了 12 天的"RAID5/6, but no write hole",改成了现在的"Enable erasure coding (RAID5/6; data replicas are capped at 3)"。

第三,对象错了。 这一点最关键。摘掉标签的是纠删码,不是整个文件系统。今天 master 上 fs/Kconfig 的开头是这样:

config BCACHEFS_FS
	tristate "bcachefs filesystem support (EXPERIMENTAL)"
	depends on BLOCK

(EXPERIMENTAL)。截至 2026 年 7 月 16 日,bcachefs 在自己的 Kconfig 里仍然管自己叫实验性的。而且这一行最近根本没被动过 — fs/Kconfig 上一次改动是在 2026 年 5 月 21 日,一次和这无关、改配额默认值的提交。

归纳起来就是这样。"某个子系统摘掉了实验标签"被错移成了"文件系统摘掉了实验标签","3 月的发布"被错移成了"6 月的发布","不包含它的版本"被错移成了"包含它的版本",各错了一格。单看每一处偏差都不大,可叠加起来,意思就跑到了反方向。

bcachefs 的纠删码到底是怎么运作的

标签的事说到这里,来看看标签底下的东西究竟是什么。一手信源是项目的 Principles of Operation(doc/bcachefs-principles-of-operation.tex),以及 EC 实现源码(fs/data/ec/create.c)里的文档注释。

基本骨架。 bcachefs 用的是 Reed-Solomon 纠删码 — 和大多数 RAID5/6 实现用的是同一套算法。erasure_code 是一个inode 级选项,可以用 set-file-option 只对特定目录开启,也就是说不必把整个文件系统都改造一遍。冗余度由 data_replicas 选项决定。

  • data_replicas=1 — 关闭 EC
  • data_replicas=2 — 1 个校验块(RAID-5 风格)
  • data_replicas=3 — 2 个校验块(RAID-6 风格)

校验块最多限制为 2 个。这就是选项说明里"data replicas are capped at 3"的真实含义 — 不是一个随意的数字,而是从 Reed-Solomon 校验块数量上限推出的值。而且不支持对元数据做纠删码。

它怎么规避 write hole。 这是设计的核心。转述 PoO 的说法:在传统 RAID 里,write hole 是个严重问题 — 条带内的一次小写入需要更新 P/Q 校验块,而这些写入没法做成原子操作,一旦崩溃让校验数据陷入不一致状态,就会连同一条带里毫不相干的数据的重建读取也一并弄坏。ZFS 靠把每次写入都变成新条带来规避这个问题,但 PoO 指出了代价:碎片化会损害性能 — 碎片化数据的读取会被最慢那个碎片的延迟拖住,于是中位数延迟就被拉向了尾部延迟。

bcachefs 的答案是用 COW 把两个问题一起绕开。照搬 PoO 的表述 — 原地更新是根本原因,那就干脆不做;而 extent 级的条带会招来和 ZFS 一样的碎片化,所以改为对整个桶编码。

实现注释里描述的写入路径是这样的。

1. 前台写入照常做复制 (data_replicas=2 时复制 2 份)
2. reconcile 线程追踪 EC 候选桶(装着复制数据的桶)
3. 候选积累到足够多时,后台 EC 任务会:
   - 取一批数据桶 (例如 5 个装着无关数据的桶)
   - 分配校验桶 (例如 RAID-6 风格就分配 2 个)
   - 在这些数据桶之间计算 Reed-Solomon 校验并写入
   - 更新所有指向原始桶的 extent:
       丢弃多余的复制指针,
       加上带有"需要重建读取"标记的校验指针

注释的结论句写得很明确 — "This approach avoids the write hole entirely: parity is computed once for immutable data, and the extent updates are atomic btree operations."。校验数据只针对已经变为不可变的数据计算一次,extent 的更新是原子的 btree 操作。既然没有校验数据需要更新,也就没有洞可以被撕开。

随之而来的代价。 这不是免费的。注释这样描述条带的生命周期 — 一旦几个桶被归入同一条带,在条带里所有数据都消亡或迁移之前,其中任何一个桶都不能被重用。copygc 知道这个约束,所以一旦条带碎片化,它会把整个条带清空 — 把还活着的数据重写进新的桶,再回收旧条带。也就是说,规避 write hole 换来的代价,是用后台写放大来支付的。

读取路径很平常。读取带有 EC 指针的 extent 时,先直接读数据桶,失败了(设备离线、校验和错误)才切换到重建读取,把幸存的数据桶和校验桶收集起来,用 Reed-Solomon 恢复出来。

"摘掉标签"背后附带的注脚

到了该诚实的部分。v1.37.0 的 EC 那一节,并没有只写"no longer experimental"就结束。紧接着的下一条是这样的:

Erasure coding is no longer hidden behind CONFIG_BCACHEFS_ERASURE_CODING, but one significant item is still remaining - stripe allocation needs to allocate blocks on different devices at similar LBAs, to avoid seeking when resilvering an array. This should land in 1.38.

翻译过来就是 — 门槛去掉了,但还剩一项重要工作没做完。 条带分配需要在不同设备上把块分配到相近的 LBA 位置,这样重建(resilver)阵列时才不会产生寻道(seek)。这项工作原计划会进入 1.38。

那它进入 1.38 了吗?

没有。 从 v1.38.0(4 月 19 日)到 v1.38.8(7 月 3 日),发布说明里哪儿都没提到这一项。看起来对应这项工作的提交是 6cf75ca7c70f — "ec: allocate stripe blocks near the stripe's centroid",作者时间戳 2026-07-04T17:45:18Z,在 fs/data/ec/create.c 里新增 48 行、删除 6 行。这比 v1.38.8 的标签时间(2026-07-03T19:15:42Z)晚了一天。用 git 核实可以确认 v1.38.8 不包含这次提交。截至 2026 年 7 月 16 日,这项工作只存在于 master 上,尚未进入任何一个发布版本。

而且这次提交自己也承认没做完。它的最后一段写道 — "Outlier reallocation - fixing up early blocks when the centroid settles elsewhere - follows in the next patch."。等重心后来落定在别处时,回头修正早期那些块的工作,被推迟到了下一个补丁里。

至于这个问题为什么会从 3 月的"应该会进 1.38"一路拖到 7 月,提交信息里也留了线索。那个朴素的做法 — 把目标定在各块原始偏移量的平均值上 — 据说反而把情况弄得更糟。按提交信息的说法,这种做法会把所有块都收敛到同一个绝对桶编号上,完全给不出寻道局部性,还会和分配器的比例填充(proportional fill)机制打架。作者举的例子是混用 1G/2G/4G/8G 设备的测试:1G 设备填到 26% 的同时,8G 的搭档才 3%,这就在小设备的剩余空间上凿出了一个洞,把系统推向了提前 ENOSPC。(这是作者自测的数字,来自某个特定测试配置下的观察,而且描述的是一个被否决的替代方案的失败,不是最终采纳的设计 — 不是可以拿来当基准测试引用的数值。)最终采纳的不是原始偏移量,而是以各设备位置比例的平均值作为重心 — 因为一条条带会跨越大小不同的设备,同样的偏移量在不同设备上落在不同的位置。

归纳一下:3 月记下的"还剩一项",后来才发现是个带有设计陷阱的问题;到 7 月中旬,一个部分解法刚刚进入 master;而这个解法本身也已经预告了后续补丁。

那现在该怎么看待这件事

把以上事实转成实务判断,大致如此。

"no longer experimental"实际保证了什么。 核心功能已经完成,不再藏在一个独立的编译门槛后面,损坏的条带接入了自动修复路径,还分配了一个磁盘格式版本号。这是实打实的进展,发布说明里的说法在代码里也确实能核实到。

它没有保证什么。 不代表项目自己指出的未完成项就此消失 — 重建时的寻道局部性工作仍未发布。这就更谈不上是对整个文件系统成熟度的断言了。BCACHEFS_FS 今天依然是 (EXPERIMENTAL)

不该用的时候。

  • 要存放找不回来的数据时。 只要这个文件系统还在自称实验性的,正确的做法就是只往上面放有备份、而且实际验证过能从备份恢复的数据。这不是在贬低 bcachefs,只是照实读了一遍项目自己的 Kconfig。
  • 重建时间是 SLO 的阵列。 偏偏那项还没发布的工作,瞄准的正是这个点。如果你在大型旋转磁盘阵列上用 EC,而设备更换耗时又很关键,现在用的就是没有这项改进的版本。
  • 无法自由选择内核的环境。 DKMS 要求 6.16 及以上。如果用的是企业发行版里锁定的老内核,这就不是一个选项。而且因为是树外模块,每次升级内核都要重新编译,如果启用了 Secure Boot,模块签名就会成为另一个单独要操心的事(v1.38.7 加入了签名支持和预编译模块的分发)。
  • 打算连元数据也靠 EC 省空间时。 不支持。要是容量计算是按这个思路做的,得重新算一遍。

值得一试的时候。 把上面的条件都读完还觉得可以接受 — 数据有备份、内核可以灵活选、重建时间有余裕 — 那 EC 本身的设计确实认真地有吸引力。用桶级别的后台编码,在没有 write hole 的前提下拿到 RAID5/6 级别的空间效率,同时避开 ZFS 式的条带碎片化,这个组合如果站得住脚,确实是个新的落点。而且因为它是 inode 级选项,可以只在冷数据目录上先试试这种渐进式采用。和现有文件系统的一般性对比,整理在ext4·XFS·ZFS·btrfs 深度对比那篇里。

结语

希望能带走两点。

第一,bcachefs 的 2026 年是工程,不是戏剧。 内核里少掉的那 117,483 行发生在 2025 年 9 月,此后项目一直靠 DKMS 分发并持续发布 — Changelog.mdwn 里带 2026 年日期的发布条目,从 v1.35.0(1 月 12 日)到 v1.38.8(7 月 3 日)一共 20 个。内核源码搬去了 tools 仓库,内核那一侧变成了下游,发布说明甚至一路跟到 rc 阶段的内核。这是稳定的运营,不是漂流。

第二,任何一句"摘掉了 experimental"都该反问三件事 — 摘掉的是什么(文件系统,还是某个子系统),什么时候(哪份发布说明做出的宣告),以及还剩什么注脚没写。在这个案例里,这三个问题的答案分别是"只是纠删码"、"2026 年 3 月 15 日的 v1.37.0"、"重建时的寻道局部性 — 还没发布"。网上流传的那句概括,三个都没答对。

而查清这一切并不需要任何特殊的访问权限。公开的 git 标签日期、一次提交的 diff、Changelog.mdwn、一个 fs/Kconfig 文件就够了。引用一句概括之前先核对一下标签日期的习惯 — 如果本文能留下一件事,就是这个。这份坦诚,其实项目自己早就先做出了示范。把"no longer experimental"和"one significant item is still remaining"并排写在同一份发布说明里连续两段的,正是 Kent Overstreet 本人。把注脚弄丢的不是项目,而是转述它的人。

参考资料

현재 단락 (1/96)

过去两年里,bcachefs 的故事大多是关于人的故事 — 谁说了什么,哪个拉取请求在哪个 -rc 上被拒绝了,邮件列表上又发生了什么。这个故事已经被充分消费过了,这里没打算再重复一遍。

작성 글자: 0원문 글자: 10,587작성 단락: 0/96