编者注(2026 年 9 月更新): 本文描述的是 v0.25 之前的实时管线。Beadbox 已不再运行 WebSocket 服务器,应用完全不开启网络监听端口。变更事件现在通过 sidecar 的 stderr 上的 kkrpc 传递。对用户而言行为没有变化:CLI 写入依然无需刷新就会出现在界面中。改变的只是实现方式。本文中的更新速度数字同样予以撤回:变更多快出现取决于平台,我们不再给出具体数值。 编者注(2026 年 9 月更新): 本文描述的是 v0.25 之前的实时管线。Beadbox 已不再运行 WebSocket 服务器,应用完全不开启网络监听端口。变更事件现在通过 sidecar 的 stderr 上的 kkrpc 传递。对用户而言行为没有变化:CLI 写入依然无需刷新就会出现在界面中。改变的只是实现方式。 4 月 2 日,beads 发布了 v1.0.0。核心特性是 embedded Dolt:一个零配置后端,在进程内运行数据库,无需单独管理服务器。对于独立开发者来说,这意味着
bd init就搞定了。没有端口,没有守护进程,没有配置。
我们立即开始在 Beadbox 中添加支持。六个热修复版本、一次公开回滚、深入研究 bd 源代码之后,我们带着一个可能几个月前就该构建的弹性层走了出来。
一切崩溃前的早晨
那天开始得很顺利。我们在整个代码库中进行了死代码清理,发布了 v0.20.0,删除了 5,350 行代码,冷启动改善了 2 秒。关闭了四十二个 bead。一个好早晨。
然后我们将 bd 升级到了 0.63.3,这是基于 beads v1.0.0 的 embedded Dolt 后端构建的第一个版本。
Beadbox 找不到数据库了。Embedded 模式将数据存储在 .beads/embeddeddolt/ 而不是 .beads/dolt/。数据库名也变了,从硬编码的 beads 变成了从 metadata.json 读取的项目前缀。而我们的 WebSocket 服务器用于通过 DOLT_HASHOF_TABLE 进行 O(1) 变更检测的 bd sql,在 embedded 模式下完全不支持。
前十分钟,三个假设全部打破。
一天六个版本
发现、修复、发布、再发现。
v0.20.1 添加了使用 OS 钥匙串的凭据持久化(六个 bead 的工作已在进行中),修复了自定义状态过滤器 bug,并修补了 Windows 特定问题。
v0.20.2 教会了 Beadbox 从 metadata.json 读取 dolt_database,以便找到重命名后的数据库。
v0.20.3 添加了 embedded 模式守卫。每个 bd sql 调用都包裹了检查:如果处于 embedded 模式,回退到基于 CLI 的轮询而非直接 SQL 查询。getDoltDir 函数学会了先检查 embeddeddolt/。
v0.20.4 修复了 embedded 布局的 --db 路径规范化。在旧目录结构下能用的路径在新结构下坏了。
每次修复都暴露出下一个问题。
Flock
v0.20.4 之后,我们以为稳定了。然后跑了一个简单的并发测试:五个 bd list 同时调用。
四个失败了。
Embedded Dolt 在每个命令的整个生命周期内获取数据库的排他文件锁(flock)。从 PersistentPreRun 到 PersistentPostRun,其他任何东西都无法访问。这是设计如此。没有它,并发引擎初始化会导致 nil-pointer panic(beads#2571)。Flock 防止了崩溃。但这也意味着在 embedded 模式下,bd 实际上是单进程的。
Beadbox 不是单进程的。我们的 WebSocket 服务器每秒轮询变更。UI 在页面加载时触发多个 server action。用户在后台轮询器运行时点击应用会产生并发的 bd 调用。Flock 会阻塞除第一个之外的所有调用。
DoltHub 关于 embedded 实现的博客文章描述了预期行为:并发调用者应该"通过指数退避自然排队"。但 arch 审查了发布的源代码,发现 bd 使用 TryLock 配合 LOCK_NB(非阻塞)。它不等待。它立即失败。有两层锁:上层是 bd 的 flock,下层是 Dolt 的驱动级退避。第一层短路了第二层。重试逻辑存在于代码库中,但从未执行,因为 flock 在 Dolt 的退避有机会运行之前就拒绝了连接。
修复方案(通过 FlockSharedNonBlock 实现读操作的共享锁)存在于 bd 的源代码中。只是还没有接入。
