Beads 数据库历史瘦身实战:通过 Dolt 历史压缩(History Squash)回收不可达存储 Beads 数据库历史瘦身实战通过 Dolt 历史压缩History Squash回收不可达存储【免费下载链接】beadsBeads - A memory upgrade for your coding agent项目地址: https://gitcode.com/GitHub_Trending/beads1/beadsBeads 以 Dolt 数据库为底层存储每一次 bead 写入都会铸成一个 Dolt commit而 Dolt 会保留所有可达 commit 引用的每个字节——dolt gc只能回收没有任何引用指向的数据。高频写入累积数月后一个工作区可能膨胀到远超其活跃数据量几千条 bead 占用数 GB 存储而dolt gc却回收不了任何东西因为整条提交链仍然从你的分支可达。本文基于 docs/recovery/history-squash.md 这份 runbook完整讲解如何将这条提交链压扁为一个基线 commit让旧历史变成可回收垃圾同时不触碰你的活跃数据——包括症状识别、诊断命令、五步压缩流程、远端与备份重定向以及事后预防措施。为什么 Dolt 历史会失控可达性回收的底层原理要理解这份 runbook 的价值先要明白 Beads 的存储模型每一次 bead 写入都会铸造一个新的 Dolt commit。Dolt 的存储设计是保留每一个可达 commit 引用的字节——dolt gc只回收没有任何东西指向的数据。因此只要分支仍然指向这条提交链整条链都是可达的dolt gc什么也回收不了一个累积了数月高频写入的工作区存储体积会远远超过其活跃数据live data本身数据目录或它的远端/备份持续变大而bd stats显示 bead 数量并不多。从源码看Beads 的bd gc命令cmd/bd/gc.go是一个三阶段生命周期清理Decay删除超过 N 天关闭的 issue默认 90 天→Compact查看 Dolt commit 历史→GC运行 DOLT_GC() 回收磁盘。关键在于 GC 阶段cmd/bd/gc.go的注释明确写道bd gc运行时前面没有 squash所以 remote-tracking refs 会被保留它们缓存了远端 tip 用于迁移门禁远程跟踪引用和 tag 会锚定历史——在历史压缩之后必须先用bd flatten/bd compact之类的操作剪除它们GC 才能真正回收空间。这正是 history-squash runbook 中先删 refs 再 gc步骤的源码级印证。而bd flattencmd/bd/flatten.go是这条思路的自动化封装——它描述的就是 Tim Sehn 配方从当前状态创建新分支、soft-reset 到初始 commit、把所有数据作为单个快照提交、把 main 分支切换到扁平化后的分支、剪除 remote-tracking refs否则它们会让旧历史保持存活下一次 push/fetch 会按新 tip 重建它们、再运行 Dolt GC。本文的 history-squash runbook 是同一个原理的手动、可控、可审计版本——区别在于它保留根 commit 作为唯一祖先而不是创建孤儿分支。症状Symptoms出现以下现象时说明你的 Dolt 历史很可能已经成为存储瓶颈Dolt 数据目录或它的远端/备份体积大且持续增长而bd stats显示 bead 数量并不多——几千条 bead 却占用数 GB 存储dolt gc和dolt gc --full回收量极少或根本没有回收——因为整条链仍然可达克隆或拉取数据库的速度慢得与其内容不成比例——每次同步都要搬运整条历史链。诊断Diagnosis在 Dolt 数据目录内运行以下命令。数据目录的位置取决于运行模式embedded嵌入式模式.beads/embeddeddolt/database/server服务模式.beads/dolt/database/从源码确认bd dolt命令cmd/bd/dolt.go默认是 embedded 模式进程内运行没有 sql-server也不会自动启动只有配置了共享服务器模式、显式 server 模式或非 localhost 的dolt_server_host时才启用 Dolt sql-server。默认数据目录是.beads/dolt可通过bd dolt set># 存储有多大 du -sh . # 历史有多深数千个 commit 配一个小型活跃数据集 # 意味着膨胀的是历史而非数据。 dolt log --oneline | wc -l # 确认 gc 是否真的没有不可达数据可收集 dolt gc --full关键判断如果dolt gc --full释放了空间说明问题已经解决无需 squash——直接结束即可。解决方案Solution五步历史压缩流程Step 1围栏隔离Fence与备份停止每一个写入者Agent、后台服务以及最容易漏掉的——每一台同步该数据库的机器上的定时同步任务cron、launchd、systemd。必须在所有同步该数据库的机器上停止而不只是你要执行 squash 的那一台。为什么必须全体停机因为一台存活的 peer 同步会在下一个 tick 破坏整个 squash它会拉取新的基线把它与自己的旧链交叉合并cross-merge然后把整条旧历史推回全新的远端——你的瘦身成果瞬间泡汤。在 server 模式下备份之后还要停止 Dolt 服务器。备份是 Dolt 原生的保留完整历史因此它就是你的回滚手段bd backup sync bd dolt stop源码印证bd backupcmd/bd/backup.go是Dolt 原生数据库备份保留数据库状态包括表、分支、commit 历史和工作集数据——这与bd export把 issue 记录写成 JSONL 用于迁移和互操作完全不同。备份命令族包括bd backup init path、bd backup sync、bd backup restore [path]、bd backup remove、bd backup status。bd dolt stop则是 server 模式下的服务器生命周期命令。Step 2压扁为单一基线Squash to a Single Baseline从 Dolt 数据目录出发把当前树直接重新提交到根 commit 之上。保留根 commit 作为唯一祖先可以维持一条有效链——不要试图通过创建孤儿分支orphan branch来进一步简化那样会破坏链的有效性root$(dolt log --oneline | tail -1 | cut -d -f1) dolt reset --soft $root dolt add -A dolt commit -m history squash: baseline $(date %F)解释一下这段命令dolt log --oneline | tail -1取出链上最老的 commit即根 commitcut -d -f1提取其 hashdolt reset --soft把 HEAD 软重置回根 commit 而保留全部工作区内容活跃数据完好无损dolt add -A暂存所有内容然后一次dolt commit把当前完整状态铸成新的基线 commit。这样旧链从新基线之外脱落变为可回收。Step 3删除其他 ref 并收集Drop the Other Refs and Collect任何仍然指向旧链的东西都会让它保持存活——陈旧的本地分支和 tag还有每次 push 和 fetch 遗留下来的 remote-tracking refs任何长期同步的工作区都有大量这种引用。必须在收集之前全部删除Step 4 的 force-push 会在新链上重建 remote-tracking refsdolt branch # 删除陈旧分支 dolt branch -D name dolt branch -r # 删除远程跟踪引用 dolt branch -rd remote/branch dolt tag # 删除陈旧 tag dolt tag -d name dolt gc --full du -sh . # 验证存储现在应该只有原来的零头这正是bd flatten与bd gc源码中反复强调的要点remote-tracking refs 和 tag 会锚定旧历史GC 无法回收它们之后的数据cmd/bd/flatten.go 在 GC 前剪除 remote-tracking refs并打印剪除报告cmd/bd/gc.go 会在 GC 后提示有多少 remote-tracking ref 和 tag 锚定了历史。如果体积几乎没变说明仍有 ref 锚定旧链——重新检查dolt branch、dolt branch -r和dolt tag是否有幸存者然后再次收集。收集失败不会危及 Step 4——push 只发送新基线引用的内容——但在 gc 成功之前这台机器会一直保留膨胀。Step 4重定向远端与备份Re-point Remotes and Backups新历史与旧历史互不相关因此第一次发布必须替换它bd dolt push --force bd backup remove bd backup init path # 全新目的地然后 bd backup sync警告Dolt 远端单调累积 chunks。force-push 会把远端的 refs 重新指向压扁后的链但不会删除任何东西所以远端自身的存储不会缩小。要连已发布一侧的空间也回收必须替换远端——在 push 之前清空它的存储或挑选一条全新的路径/前缀bd dolt remote remove name bd dolt remote add name fresh-url bd dolt push --force反正 squash 之后其他每个克隆都必须重新克隆所以替换远端不会带来额外成本。Git 后端远端git-backed remote的特殊处理如果 issue 数据骑在你的代码远端上位于refs/dolt/data就没有新路径可选——存储本身就是 git 仓库它的 manifest 把每个历史表文件都列为当前文件因此即使 force-push 之后整个旧存储仍然可达。此时要就地替换数据平面先删除 git 远端上的 Dolt 数据 refs再 force-push 重建一个只持有活跃 chunk 的全新存储。代码分支不受影响git push origin :refs/dolt/data :refs/heads/__dolt_remote_info__ bd dolt push --forceStep 5验证然后其余机器全部重新克隆Verify, Then Re-clone Everywhere Else在本机验证bd doctor bd list -n 5这台数据库的每一个其他克隆都必须从压扁后的远端重新创建。旧克隆绝不能 pull两条链仍然共享根 commit所以一次 pull 可能成功为一次交叉合并从而重新锚定整条旧历史——之后的一次 push 就会在压扁后的远端上复活膨胀。把同步任务和写入者当作两个独立的开关来对待每台机器的同步任务只能在该机器完成重新克隆之后已验证而不是假设再重新启用最后解除写入者的围栏解除写入者围栏绝不能隐式重启对端机器的同步任务——一台 peer 从旧克隆同步就会把整个压缩前的存储重新上传到全新远端。另外注意从重新克隆的机器上发出的第一次 push 可能比常规同步大得多。如果该机器的定时同步任务有超时限制超时的 push 可能每 tick 都在上传中途死亡在远端留下残缺的上传碎片。正确做法是重新克隆后立即手动执行一次bd dolt push无超时完成后再把控制权交还给调度。预防Prevention从源头避免历史膨胀高频协调状态刻意放在非版本化表unversioned tables中目的就是让常规 Agent 流量不铸造历史——比如 claim 租约和 wisps蒸发态分子。wisps 是以Ephemeraltrue标记的真实 bead默认被排除在 federation 推送之外federation.exclude_types默认值为[wisp]不属于共享审计轨迹关闭后由bd purge或bd mol wisp gc批量删除。因此这个量级的膨胀通常意味着有某个写入者正在紧循环地写版本化表——找到并修复那个写入者随时间观察数据目录的增长趋势周期性运行dolt gc让不可达垃圾永远不要累积在可达历史之上。完整流程速查表步骤目标关键命令诊断确认膨胀来自历史而非活跃数据du -sh .、dolt log --oneline \| wc -l、dolt gc --full1. 围栏备份停止全部写入者含所有机器的同步任务保住回滚手段bd backup sync、bd dolt stop2. 压扁把当前树软重置到根 commit 上重新提交dolt reset --soft $root、dolt add -A、dolt commit3. 收集删除一切锚定旧链的 ref触发 GCdolt branch -D、dolt branch -rd、dolt tag -d、dolt gc --full4. 重定向替换远端与备份发布新基线bd dolt push --force、bd backup remove、bd backup init、bd backup sync5. 验证重克隆本机验证其余克隆全部重建bd doctor、bd list -n 5结语History squash 是 Beads 运维工具箱中少数几个需要全局协调的操作它不只是本机动作而是整个同步集群的集体动作。成败关键完全在于纪律——围栏窗口内所有机器全体停机、备份先行、refs 清剿彻底、重克隆逐台验证、同步任务最后才恢复。只要按 docs/recovery/history-squash.md 的五步走完一个数千 commit 的巨型数据库可以在一轮操作内缩回活跃数据应有的体积而真正的长期解法是让高频协调流量停留在 wisps 与非版本化状态里让版本化表只为有审计价值的工作铸造历史。【免费下载链接】beadsBeads - A memory upgrade for your coding agent项目地址: https://gitcode.com/GitHub_Trending/beads1/beads创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考