SBD:我们家里就有文件系统
摘要
Fly.io 介绍了 Sprites 块设备(SBD),用一个更简单的块设备方案取代了之前魔改的 JuiceFS 存储栈,以提升 Sprites 的可靠性并消除元数据不一致问题。
暂无内容
查看缓存全文
缓存时间: 2026/10/22 15:57
# SBD:家里就有文件系统
来源:https://fly.io/blog/sprites-block-device/
作者Daniel Botha姓名Daniel Botha一位疲惫的鸟厨师,身旁是一个酷酷的意大利面机器人插画作者Annie Ruygt (https://annieruygtillustration.com/)对于那些自己做意大利面的人,我有些不太客气的话想说。不过在这里,我只会拿他们举个例子,说明构建基础设施时不该抱有什么想法。人们太容易误以为,在一件事上花的功夫越多,成果就越好。但基础设施要做的是那袋 1 美元的意大利面:简单、好用,而且不需要你花一周时间把厨房角落里的面粉刮干净。
Sprites 刚开始开发时,它是为一种新产品打造的一种新基础设施。所以,当我们在意大利面货架上找不到合适的文件系统时,就自己做了一个。它很巧妙,而这恰恰是我们不喜欢它的原因。这篇文章介绍它的替代品:Sprites 块设备(SBD)。它就是你从货架上随手拿起的那袋意大利面,而你唯一会察觉到的区别,就是可靠性更好了。
## https://fly.io/blog/sprites-block-device/#it-was-always-object-storage一直都是对象存储
对于 Sprites 存储的一个核心方面,我们的立场从 v1 起就没变过:Sprite 磁盘的根基是对象存储,而不是 Sprite 恰好运行所在的那台服务器上的 NVMe。正因如此,Sprite 才能休眠、在另一台服务器上唤醒,并且仍然在原处找到自己的文件。一个 Sprite 的持久化状态,差不多就是存储桶里的一个位置。我们曾满怀感情地长篇解释过原因 (https://fly.io/blog/design-and-implementation/)。
对象存储的问题在于,它无法修改对象的一部分。哪怕只改一个字节,也得替换整个对象。这与 Linux 程序的习惯很不合拍,因为它们总是在文件中间重写几个字节。必须有什么东西来处理这种转换。
产品发布时,负责这件事的是一个经过大幅改造的 JuiceFS 版本。原版 JuiceFS 将存储分成两部分:数据以分块形式放在存储桶里,元数据则放在数据库里,记录哪些分块组成哪些文件。不过,我们并不是这样用它的。我们的魔改版运行着重写后的 SQLite 元数据后端,通过 Litestream 实现持久化。它只存储一个文件:一个 100 GB 的磁盘镜像,通过 loop 设备挂载,里面是 ext4。Linux 负责所有常规的 ext4 操作。JuiceFS 只是托管这个镜像。
它运行得还不错。Sprites 靠它上线,用户创建了大量 Sprite,而且检查点创建得很快,因为创建检查点基本上只是挪动元数据。但随着使用量增长,问题开始显现。
## https://fly.io/blog/sprites-block-device/#manifests-that-lie会撒谎的清单
带有元数据数据库的文件系统有两个密不可分的组成部分:存储桶里的分块,以及说明这些分块如何拼在一起的数据库。两者一致时,你就有文件。两者不一致时,你就完蛋了。
我们在上面构建的每一层,都必须继承这种结构,并继续往上加东西。在 JuiceFS 下,检查点是磁盘镜像的写时复制克隆,因此元数据数据库除了跟踪当前磁盘的分块,还要跟踪每个检查点的分块。另一个 SQLite 数据库 `checkpoints\.db` 则列出检查点本身,以及每个检查点应该将哪个镜像文件挂载到 loop 设备上。
所以,一个 Sprite 的磁盘由分块、说明哪些分块组成各个镜像的元数据数据库,以及说明各个镜像对应哪个检查点的检查点数据库构成。每一部分都单独持久化,所以其中任何一部分都可能是对的,而其他部分却是错的。
## https://fly.io/blog/sprites-block-device/#what-could-go-wrong能出什么问题呢?
这个故事可能源自亲身经历,也可能不是:某一天,一个 Sprite 无法启动了。JuiceFS 向对象存储请求一个分块,得到的却是 404。我们想要速度快,所以让 JuiceFS 运行在回写模式下。它会先在元数据中记录分块,再上传实际字节。如果机器在这两个步骤之间的任何时刻被终止,元数据就会指向一个不存在的分块。
于是,运行在其上的 ext4 文件系统发现某个目录块未通过校验和检查,便切换为只读。把 Sprite 移到新机器上无济于事,恢复检查点也没用,因为 JuiceFS 的检查点是克隆。它们共享相同的分块,所以每个检查点都会有同一个缺口。
## https://fly.io/blog/sprites-block-device/#my-agent-ate-my-dogfood我的智能体吃了我的狗粮
促成 Fly.io 史上最密集的一轮“吃自家狗粮”测试的,与其说是这类问题的技术细节,不如说是它们悄无声息的特性。而如果你要为智能体提供计算机,并亲自试用,那最好让智能体来吃这份狗粮。
于是,我们搭建了一个测试框架,让成群的 Sprite 一遍又一遍地执行写入、创建检查点、休眠、唤醒和恢复,并验证读回的每一个字节。我们让它对 400 个 JuiceFS Sprite 跑了大约 5,000 个循环。其中有不止一个永久失去了创建检查点的能力。好吧……操。
有一段时间,我们继续审视自己的配方。增加了更多一致性核对、更多修复工具,以及越来越巧妙的办法,来让三本账保持一致。但一份自带修理包的意大利面,无论怎么做,吃起来都会怪怪的。
## https://fly.io/blog/sprites-block-device/#just-a-disk就是一块磁盘
现在登场的是:Sprites 块设备(SBD)。它拿一个存储桶,通过 NBD 将其作为块设备呈现给 Linux 内核。Sprite 将这个设备格式化为 ext4 并挂载,就像你在任何 Linux 机器上挂载磁盘一样。
这不是明摆着嘛。
在 SBD 下,元数据与它所描述的块一起存放在存储桶中,因此不再需要同步一个独立的数据库。Sprite 内部的守护进程 `sbd serve` 从自己的本地缓存响应内核的块读写请求,在后台将脏块上传至对象存储,并在需要保证字节已经送出本机时,按需执行刷写。
繁重的工作依然由 ext4 完成。我们只是把它下面的一切大幅简化了:现在只有一个守护进程和一个存储桶,而以前则有一个文件系统、一个数据库和一个磁盘镜像,需要让它们保持一致。
## https://fly.io/blog/sprites-block-device/#dumber-checkpoints-are-better-checkpoints越笨的检查点,越是好检查点
对于任何旨在为智能体提供安全活动空间的基础设施,检查点都至关重要。在 JuiceFS 下,检查点是磁盘镜像的写时复制克隆,记录在 `checkpoints\.db` 中。在 SBD 下,检查点是整个卷的块快照,带有版本号,记录在 SBD 自己的检查点目录中。啊……单一事实来源带来的安心,真是无与伦比。
恢复也同样朴素。每个检查点都知道自己属于卷的哪个版本,因此恢复时只需卸载文件系统,将卷原地回退,再重新挂载。开销大约相当于重启一次。
当然,事情仍然可能出错。区别在于,现在 Sprite 会停下来并报告问题,而不是继续使用一块它无法保证可靠的磁盘。SBD 会对从对象存储读回的每个块进行检查,确认它与写入时一致。如果 SBD 自身崩溃,整个 Sprite 都会重启,因为只有彻底重启,才能重新获得一块值得信赖的磁盘。
## https://fly.io/blog/sprites-block-device/#moms-spaghetti妈妈做的意大利面
持续高强度的自用测试、近乎虐待的测试框架,以及几位先行设计合作伙伴的反馈,都表明 SBD 的可靠性比我们自制的意大利面文件系统高出几个数量级。
因此,从现在起,SBD 将成为 Sprites 的文件系统。ext4 运行在块设备上,块设备构建在存储桶上。负责跟踪你文件的,只有每台 Linux 机器都已经在运行的文件系统代码。
*如果你有在 10/6 之前创建的 Sprite,可以按照这些说明 (https://docs.fly.io/sprites/sprite-maintenance#storage-upgrades) 进行迁移。*
上一篇 ↓你的智能体会说 MCP。给它一台计算机。 (https://fly.io/blog/sprites-mcp/)
相似文章
@yifanxu_ephai: 这是我正在做的 DSH-LayerFS。 https://github.com/Ephemeral-AI-Lab/layerfs/tree/main/dsh… 基于 LayerFS 的存算隔离架构和存储压缩能力,做了一个极简的 multi…
DSH-LayerFS 是基于 LayerFS 存算隔离与存储压缩能力构建的极简 multi-agent harness,支持执行记录可追溯、独立 Workspace 运行 Bash 命令、冲突检测与递归嵌套 Fork。
@ewind_dev: 非常激动地和大家分享,在 Fable 加持下,我终于做出了多年来设想中最极致的跨平台 UI 框架,这就是 PocketJS! 将 QuickJS 移植至内存仅 32MB 的索尼 PSP,结合标准版 SolidJS 和 Vue Vapor,…
在Fable加持下,作者将QuickJS移植到索尼PSP,结合SolidJS和Vue Vapor以及编译期Tailwind样式引擎,实现了8MB内存下60fps的流畅动画,发布了名为PocketJS的极简跨平台UI框架。
@yifanxu_ephai: 在LayerFS跑一个RUST编写的DB Engine任务: 将 2.50 TB 的快照数据、30 个分支,压缩到仅 4.66 GB, 光是 main 分支就有 8.23 GB。 先吹个牛逼,我怕明天发布翻车,就吹不了了。 半年前我和VC…
Ephemeral AI Lab 宣布 LayerFS 1.0.0 正式发布,这是一款为 AI 智能体设计的文件系统基座,其 Rust 编写的 DB Engine 将 2.50 TB 快照数据和 30 个分支压缩到仅 4.66 GB,主打短暂工作区与共享持久历史。
@haydenbleasel:推出 Files SDK——一个统一的对象与 Blob 后端存储 SDK。小巧、坦诚的 API。Web 标准 I/O。当需要原生客户端时的逃生口。…
Files SDK 被介绍为一个统一的存储接口,支持18个提供商如 S3 和 R2,可跨 Node、Bun 和边缘运行时使用。它旨在简化 Web 应用的文件操作,并与 AI 代理框架集成。
@modal:推出 VM Sandboxes 时,我们希望在不影响性能的前提下保留 Modal 的亚秒级冷启动……
Modal 分享了为 VM Sandboxes 构建的新文件系统栈的工程挑战,其自定义惰性加载容器文件系统 ImageFS 基于分层分布式文件系统(本地 NVMe、区域与 P2P 缓存、全局 blob 存储),在保持亚秒级冷启动的同时大幅提升性能。