ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Beads 的 Dolt 并发模型:基于事务的单分支(All-On-Main)共享数据平面设计

Beads 的 Dolt 并发模型:基于事务的单分支(All-On-Main)共享数据平面设计 Beads 的 Dolt 并发模型基于事务的单分支All-On-Main共享数据平面设计【免费下载链接】beadsBeads - A memory upgrade for your coding agent项目地址: https://gitcode.com/GitHub_Trending/beads1/beads本文基于仓库设计文档 engdocs/design/dolt-concurrency.md 展开结合 internal/storage/dolt/transaction.go、internal/storage/dolt/store.go、cmd/bd/dolt_autocommit.go 等源码与测试完整还原 Beads 从分支隔离到事务约束下单主分支的并发模型演进以及它在多 Agent 系统、联邦同步Federation/Wasteland和独立 Beads 场景中的落地方式。Beads 是面向多 Agent 系统的通用数据平面universal data planeworker、coordinator、observer、processor、patrol 等所有角色都通过读写 beads 来完成相互协调。因此底层的 Dolt 存储并发模型必须同时服务于全部角色而不只是单个 worker。本文讲解 Beads 采用的All-On-Main全部写入 main 分支 显式 SQL 事务 事务内 DOLT_COMMIT并发方案它为何取代旧的 branch-per-worker 策略、两层并发架构的原理、三条事务规则、连接池配置、冲突处理、分阶段迁移策略以及它对联邦同步与独立 Beads 部署的影响。读完本文你将掌握如何在一个共享 Dolt 分支上让数十个并发 Agent 安全读写同一份数据并获得可复制的 SQL 事务模板与 Go 实现对照。1. 背景为什么 branch-per-worker 必须退休文档指出Beads 此前使用branch-per-worker策略每个 worker 拥有独立的 Dolt 分支在隔离状态下写入稍后再合并回 main。该策略本意是消除并发写者之间的乐观锁竞争且实测有效——测试中 50 个并发写者、250 个 Dolt 提交、成功率 100%。但文档明确判定这些并发收益是虚幻的illusory理由有四Agent 之间互相看不见 beads。Agent A 创建的 bead在 A 的分支合并回 main 之前对 Agent B 完全不可见。这破坏了调度dispatch、依赖跟踪和状态查询所需的跨 Agent 可见性。共享状态必须放在 main 上。作为整个系统的协调层所有角色都需要对 bead 状态持有同一份视图分支隔离与共享数据平面的需求背道而驰。完成时合并带来陈旧性staleness。长期运行的 Agent 不断累积分叉完成时的合并只是一次批量对账点而非持续共享视图。分支爆炸branch proliferation。每次 sling 都会创建分支清理依赖bd done或专门的分支清理流程孤儿分支不断累积为保障安全引入的BD_BRANCH分析issue #1796又给代码库增添了大量复杂度。外部输入是决定性的Dolt 联合创始人 Tim Sehn 在 2026-02-21 给出建议DoltHub 2026-02-18 并发博客——It is far simpler to use one branch, so start there. You can get hundreds of transactions per second on a single branch. We fixed the bug you ran into.用单分支要简单得多从那里起步单分支上每秒可获得数百个事务。你遇到的问题我们已修复。Tim 的关键洞察事务边界缺失Tim 进一步点出了问题的核心I think you dolt commit every sql statement. If you dont you want to wrap writes in a BEGIN and finish with a CALL DOLT_COMMIT(), ie in a transaction otherwise connections will commit each others writes.即在 Dolt 的 auto-commit 模式下如果没有显式 SQL 事务边界任何连接都可能不经意地把另一条连接的未提交工作集变更一并提交掉。解决方案是建立正确的事务边界让DOLT_COMMIT与它对应的写入处于同一个事务内。这一洞察在源码中得到了印证当前实现中RunInTransaction的commitMsg参数正是用于让常规写入在 Dolt 历史中可见的 DOLT_COMMIT见 internal/storage/dolt/transaction.go 的注释而 internal/storage/dolt/transaction.go 也明确要求SQL 事务、config 保护、DOLT_COMMIT 必须运行在同一条 Dolt session 上GH#2455——这正是对 Tim 洞察的工程化落地。2. Dolt 两层并发架构背景知识Dolt 的并发能力来自两个叠加的层次源自 DoltHub 并发博客。2.1 Layer 1SQL 事务MVCC标准 SQL 事务语义但有一个关键差异冲突检测采用针对分支 HEAD 的三路合并three-way merge而非行级锁不同单元格被并发修改无冲突自动合并同一单元格被更新为相同值无冲突同一单元格被更新为不同值冲突必须解决这比传统数据库更宽容两个 Agent 并发更新同一个 bead 的不同字段会成功不会互相阻塞。隔离级别为repeatable read可重复读代价是对读后写同一单元格的场景存在丢失更新lost update风险。2.2 Layer 2提交图串行化版本控制操作DOLT_COMMIT、DOLT_MERGE、DOLT_BRANCH等会获取全局锁原子执行后释放。合并工作merge work在锁外进行只有最终的图写入被串行化。性能上正常运行时可达到每秒数百次提交图操作全局锁计划在未来改为每分支粒度但实践中从未成为瓶颈。2.3 两种模式对比模式 1事务包裹的 Dolt 提交推荐 Beads 采用BEGIN; INSERT INTO issues (id, title, status) VALUES (gt-abc, Fix bug, open); INSERT INTO dependencies (issue_id, depends_on_id, type) VALUES (gt-abc, gt-def, blocks); CALL DOLT_COMMIT(-Am, bd: create gt-abc); -- 事务结束变更原子可见模式 2每客户端分支Beads 正在退休的旧方案CALL DOLT_BRANCH(worker-ace-1708642800); CALL DOLT_CHECKOUT(worker-ace-1708642800); -- 隔离写入对其他 Agent 不可见 -- 稍后合并3. 目标设计All-On-Main 事务纪律3.1 设计原则所有 beads 都位于main分支上。并发访问通过带显式DOLT_COMMIT的 SQL 事务来管理不再为 worker 创建任何分支。3.2 规则 1每个写入组都必须包在事务里每个逻辑写操作创建 bead、更新状态、关闭 bead、添加依赖等都必须包裹在BEGIN…CALL DOLT_COMMIT()中。文档给出了重构前后的 Go 对照// BEFORE旧每条语句自动提交无事务边界 func (s *DoltStore) CreateIssue(ctx, issue, actor) { s.db.Exec(INSERT INTO issues ...) // auto-committed // DOLT_COMMIT 稍后由 maybeAutoCommit() 触发 } // AFTER显式事务DOLT_COMMIT 在事务内 func (s *DoltStore) CreateIssue(ctx, issue, actor) { tx, _ : s.db.BeginTx(ctx, nil) tx.Exec(INSERT INTO issues ...) tx.Exec(INSERT INTO labels ...) // 如适用 tx.Exec(INSERT INTO dependencies ...) // 如适用 tx.Exec(CALL DOLT_COMMIT(-Am, ?), msg) tx.Commit() }这样做保证了一次逻辑操作内的所有写入原子可见其他连接无法提交我们的未提交写入DOLT_COMMIT属于事务的一部分因此只包含我们的变更若事务回滚不会产生 Dolt 提交。这一模式在当前源码中已是主线RunInTransaction(ctx, commitMsg, fn)接收提交消息并调用runDoltTransaction事务成功后由finishDoltTransaction依次完成常规 SQL 事务提交 →versioncontrolops.StageAndCommit生成 Dolt 修订 → ignored 表事务提交见 internal/storage/dolt/transaction.go。其中StageAndCommit只提交脏表集合tx.dirty.DirtyTables()并带上commitAuthorString()作为作者正是文档事务内 DOLT_COMMIT 只包含我们的变更的精确实现。3.3 规则 2读操作不需要事务简单读取对issues、dependencies等的SELECT可以直接使用裸连接读取 main 上最新已提交状态事务内的读取在 repeatable-read 隔离下看到一致快照。3.4 规则 3批处理模式变为事务作用域原有的批处理模式累积变更、在逻辑边界处提交可以自然映射为长事务// 批处理开启事务多次写入结束时一次性 DOLT_COMMIT tx.Begin() for _, issue : range issues { tx.Exec(INSERT INTO issues ...) } tx.Exec(CALL DOLT_COMMIT(-Am, ?), batchMsg) tx.Commit()4. Beads 侧的代码改动对照当前仓库实现4.1store.go移除分支初始化固定 main文档要求删除BD_BRANCH初始化块原 store.go 第 336-358 行、删除SetMaxOpenConns(1)/SetMaxIdleConns(1)限制以启用连接池。当前仓库中这一迁移已经完成在 internal/storage/dolt/store.go 可以看到明确的落点——All writers operate on main — transaction isolation via RunInTransaction replaces the former branch-per-worker approach (BD_BRANCH)随后store.branch main。此外 internal/storage/dolt/store.go 还注册了 OTel 连接池指标registerPoolGauges对应 GH#3140用于诊断共享服务器性能退化——这是文档设计之上新增的运维增强。4.2transaction.go把 DOLT_COMMIT 纳入事务文档指出旧runDoltTransaction只做sql.BeginTx/Commit从不调用DOLT_COMMIT导致 SQL 变更只进工作集、不进 Dolt 版本历史其他连接可能把这些变更卷进自己的提交。重构后的目标形态func (s *DoltStore) runDoltTransaction(ctx, fn, commitMsg) error { sqlTx, _ : s.db.BeginTx(ctx, nil) tx : doltTransaction{tx: sqlTx, store: s} if err : fn(tx); err ! nil { sqlTx.Rollback() return err } // Dolt 提交位于 SQL 事务内部 —— 与写入原子 _, err : sqlTx.Exec(CALL DOLT_COMMIT(-Am, ?, --author, ?), commitMsg, s.commitAuthorString()) if err ! nil !isNothingToCommit(err) { sqlTx.Rollback() return err } return sqlTx.Commit() }当前实现的runDoltTransaction更进一步internal/storage/dolt/transaction.go使用s.db.Conn(ctx)钉住单条连接执行整个操作保证 SQL 事务、config 保护与 DOLT_COMMIT 处于同一条 Dolt sessionGH#2455读取SELECT active_branch()确认当前分支在 journal 模式eventsJournalEnabled下把 ignored 表事务与常规事务合并到同一条 SQL 事务保证事件日志与数据变更同事务提交对事务期间 panic、回调失败分别回滚回调至多调用一次at-most-once提交结果不确定时以ErrCommitIndeterminate上报并停止重放internal/storage/dolt/transaction.go。这与文档的AFTER形态语义一致并额外补齐了连接亲和性、事件日志原子性和不确定提交的处理——是生产化后的演化版本。4.3dolt_autocommit.go外部自动提交退居安全网文档建议DOLT_COMMIT移入事务后外部自动提交包装器maybeAutoCommit对大多数操作不再必要但可保留为裸写入逃离事务模式的安全网例如迁移脚本、一次性修复。当前 cmd/bd/dolt_autocommit.go 保留了maybeAutoCommit第 103-116 行、maybeAutoCommitStore第 129 行起、isDoltNothingToCommit第 161 行等函数且文件开头注释明确说明事务路径已让maybeAutoCommit冗余DOLT_COMMIT occurred, preventing the redundant maybeAutoCommit同时writesCommitNow()/embeddedWritesCommitNow()控制提交时机——正是安全网 双路径的形态。4.4versioned.go合并/分支操作降级为迁移期专用Merge()与DeleteBranch()仅在迁移期清理既有 worker 分支需要迁移完成后它们对正常写入路径成为死代码但保留给联邦DoltHub 远程合并与独立 Beads 场景。5. 编排器Orchestrator侧的改动文档在此处引用的编排器内部命令gt sling、gt done属于历史上下文——该设计最初是为编排器迁移撰写的Beads 仓库中对应逻辑已随迁移演进或移除。Worker 派发停止创建分支。旧的派发逻辑会创建 Dolt 分支并把BD_BRANCH注入 worker 环境。迁移后派发时不再创建分支、不注入BD_BRANCH、所有 Agent 共用同一 main 分支连接池。任务完成停止合并分支。旧的任务完成逻辑会 checkout main、合并 worker 分支并删除它。迁移后无合并、无分支删除任务完成只是关闭 bead它已在 main 上、已经可见。BD_BRANCH安全基础设施#1796可整体退役包括bdbranch/analyzer.go、OnMain()、StripBdBranch()与 arch 测试注册表代码库显著简化。当前仓库仅在 internal/storage/dolt/store.go 与 CHANGELOG.md 中残留历史说明印证该基础设施已移除。session_manager.go移除 DoltBranch 选项。从会话选项中删除DoltBranch字段及向 tmux 会话注入BD_BRANCH的逻辑。6. 连接池配置所有连接都落在 main 上之后需要合理的连接池配置db.SetMaxOpenConns(10) // 允许并发读者 写者 db.SetMaxIdleConns(5) // 保持热连接 db.SetConnMaxLifetime(5 * time.Minute)具体数值取决于 rig一套运行编排器的机器的并发级别。典型编排器 rig 为 6 个 worker coordinator observer processor patrol ≈10 个并发 Agent每个 Agent 可能持有一条连接。文档建议以 10 为最大连接数的保守起点压测后调优。补充两个当前实现中的池化细节见 internal/storage/dolt/transaction.goignored 事务的第二条连接通过beginIgnoredTxOnBranch从主池借用一条热连接ignoredTxBorrowTimeout 250ms为上限借用不可得时回退到专用单连接池db.SetMaxOpenConns(1)——这解释了为何源码中仍会出现SetMaxOpenConns(1)它服务于 ignored 表事务的专用回退路径而非旧的整库单连接限制连接获取耗时connAcquireMs、池等待次数poolWaitCount被记录为 OTel 指标供容量评估与退化诊断。7. Wisps无需改动Wisps 已经位于dolt_ignore的表中不受版本跟踪、不建分支、不参与联邦digest 发布除外。其并发模型纯粹是 SQL——标准的 MySQL 兼容并发写入无 Dolt 特有顾虑。Wisps 按设计是worker 本地的一个 Agent 的 wisps 只对该 Agent 的会话有意义。跨 Agent 的 wisp 可见性如 molecule 步骤协调走 events/comments 表这些表同样被 dolt_ignore。本次迁移对 wisps 零改动。8. 冲突解决多个连接并发写入 main 时冲突可能但罕见原因在于 Dolt 的单元格级合并语义场景冲突解决方式两个 Agent 创建不同的 bead否不同行自动合并两个 Agent 更新不同的 bead否不同行自动合并两个 Agent 更新同一 bead 的不同字段否不同单元格自动合并两个 Agent 更新同一 bead 的同一字段是Last writer winsupdated_at一个 Agent 写、另一个 Agent 读否读端看到已提交状态同一 bead 的同一字段在实践中的确罕见——bead 通常在同一时刻只归属一个 Agent通过 sling 指派。主要风险是并发状态更新如一个 Agent 关闭 bead 的同时另一个 Agent 也在更新它。缓解手段在必要时使用乐观并发检查更新前校验期望状态。仓库中的 internal/storage/dolt/concurrent_test.go 专门覆盖了这类并发场景——它验证多个 writer 并发创建 issue、更新状态、添加依赖、关闭 issue 时系统正确收敛并专门测试了 autocommit 模式下同单元格分支冲突被拒后工作集保持干净、可安全重放TestDoltAutocommitRollbackContentionConverges与文档可能但罕见 乐观并发兜底的判断互为印证。9. 分阶段迁移策略Phase 1加事务纪律不破坏现有行为修改RunInTransaction把DOLT_COMMIT放进 SQL 事务内。该改动无论是否启用 branch-per-worker 都成立属于纯增量安全增强。改transaction.go增加提交消息参数在tx.Commit()前调用DOLT_COMMIT为所有RunInTransaction调用方补提交消息保留maybeAutoCommit作为裸写入的兜底测试既有 internal/storage/dolt/concurrent_test.go 应继续通过。Phase 2移除 Branch-Per-Worker编排器侧以 Phase 1 在生产环境稳定为前提。从 worker 派发与会话管理移除BD_BRANCH注入派发时不再创建分支、完成时不再合并分支从store.go移除BD_BRANCH环境变量处理清理OnMain()、StripBdBranch()、analyzer 基础设施测试用 2-3 个 Agent 部署验证跨 Agent 的 bead 可见性。Phase 3退役分支基础设施清理移除bdbranch/analyzer.go与 arch 测试注册表从文档中清除BD_BRANCH引用更新dolt-storage.md设计文档清理既有安装中的孤儿分支测试全规模 swarm6 Agent并发写入压力测试。10. 对联邦同步Federation / Wasteland的影响全量写入 main简化了联邦Push/pull 分支干净。单一 main 分支使dolt push/dolt pull作用于单一线性历史除了 Dolt 内容寻址的合并提交之外无分支命名空间污染。提交图更简单。branch-per-worker 产生的复杂 DAG 在向 DoltHub 远程同步时难以推理all-on-main 带来更干净的提交历史。跨 rig 的 bead 可见性即时生效。rig A push 到 DoltHub 后rig B pull 即可看到全部 beads无需分支对账。联邦事务。同样的BEGIN…DOLT_COMMIT模式适用于联邦同步拉取远程变更、解决冲突、提交——与 Dolt 原生复制流程一致。Wisps 与联邦Wisps 被dolt_ignore且从不 push这是正确的Wisps 是易失的、worker 本地的运行状态Wisp digest摘要可选地结晶为 beads 并发布到 main 以参与联邦digest 发布流程本身已是独立操作创建的是正规 bead而非 wisp。11. 对独立 Beads 的影响bdCLI 同时支持嵌入式 Dolt 与 server 模式。本设计仅适用于 server 模式编排器的部署形态嵌入式模式是单进程不存在本文所述的多连接并发问题。对使用嵌入式 Dolt 的独立bd单连接auto-commit 即可无 branch-per-worker单用户事务包裹仍属良好实践但非关键。12. 性能预期Tim 的指导是单分支每秒数百个事务。Beads 的典型负载文档估算典型编排器 rig6-12 个并发 Agent全角色合计写模式创建/更新/关闭 bead每 Agent 每分钟约 1-10 次写读模式状态查询、bead 查找每 Agent 每分钟约 10-100 次读峰值总量约60-120 次写/分钟、600-1200 次读/分钟。这远在 Dolt 单分支能力之内。即使放大到 10 倍规模大型 rig 上 60 Agent也只是约 1200 次写/分钟 ≈20 次写/秒远低于每秒数百次的上限。13. 遗留的开放问题提交粒度是否每个bd create都产生一个 Dolt 提交还是在更高层级批量如按 molecule、按 formula 步骤按操作提交审计性更好批处理可减少提交图体积。Tim 的模型表明在当前规模下按操作提交没有问题。连接池大小每个 rig 的正确池大小需在负载下测试。建议从保守的 10 个最大连接起步再调优。丢失更新防护Dolt 的 repeatable-read 隔离不防丢失更新。对status、assignee等高竞争字段是否需要应用层乐观锁如WHERE updated_at ? AND status ?既有分支清理生产 rig 已累积 worker 分支切换 all-on-main 前需要迁移脚本执行 merge-or-delete。嵌入式模式兜底若独立bd用户对同一嵌入式 Dolt 运行多个进程罕见但可能会遭遇相同问题。应文档化为不支持还是也给嵌入式模式加事务纪律14. 参考与延伸阅读设计文档engdocs/design/dolt-concurrency.mdDoltHub 并发架构博客2026-02-18Tim Sehn 2026-02-21 给 Steve Yegge 的邮件文中已引述核心观点核心实现internal/storage/dolt/transaction.goRunInTransaction / runDoltTransaction / finishDoltTransaction、internal/storage/dolt/store.gostore.branch main落点自动提交安全网cmd/bd/dolt_autocommit.go并发与冲突验证internal/storage/dolt/concurrent_test.go历史上下文已移除的编排器组件done.go合并流程、session_manager.go的 BD_BRANCH 注入、bdbranch/安全分析器【免费下载链接】beadsBeads - A memory upgrade for your coding agent项目地址: https://gitcode.com/GitHub_Trending/beads1/beads创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表