GBrain 存储分层(Storage Tiering)实战:db_tracked 与 db_only 目录体系与数据恢复指南
【免费下载链接】gbrainGarry's Opinionated OpenClaw/Hermes Agent Brain项目地址: https://gitcode.com/gh_mirrors/gb/gbrain
导读:本文围绕 GBrain 的存储分层(Storage Tiering)功能展开,讲解如何通过gbrain.yml中的storage:配置,将脑仓库(brain repository)划分为版本控制目录(db_tracked)与数据库持久化目录(db_only)两层,从而在 git 仓库体量与数据完整性之间取得平衡。读完本文,你将掌握存储分层的配置语法、gbrain sync的自动.gitignore管理、gbrain export --restore-only的数据恢复、gbrain storage status健康巡检,以及 PGLite 引擎下的能力边界与迁移路径。
一、为什么要存储分层:git 仓库与机器生成内容的矛盾
GBrain 的脑仓库本质上是"以 git 为版本控制、以数据库为系统记录"(system-of-record)的知识管理系统:人类编辑的核心知识(人、公司、交易、概念)需要进入 git 历史,而抓取推文、归档文章、会议转录等批量机器生成内容体积巨大,如果全部纳入 git,仓库会迅速膨胀,clone、diff、CI 都会变得缓慢。
存储分层(Storage Tiering)正是为了解决这一矛盾:将版本控制内容与批量机器生成内容分离,让机器生成的数据仍保存在数据库中,但不再被 git 追踪。依据官方文档 docs/storage-tiering.md,这"防止 git 仓库被大量自动生成的内容撑爆,同时仍将内容保留在数据库中"。
分层后的收益体现在三个典型场景(详见原文档 Use cases 一节):
- 脑仓库扩展:适合文件数跨越 5 万到 20 万+ 的仓库——核心知识(people、companies、deals)继续被 git 追踪,批量数据(tweets、articles、transcripts)转入
db_only,开发期保持 git 仓库小巧,完整数据仍可从数据库获取; - 容器化部署:对临时容器环境至关重要——git 仓库只含必要文件,容器重启不丢失
db_only数据,本地磁盘退化为缓存层; - 多环境一致性:开发环境小体积 clone、按需恢复批量数据;生产环境通过数据库访问全量数据集、选择性本地缓存;CI/CD 只用 git 追踪数据做快速测试。
二、配置语法与路径规范
2.1 在 gbrain.yml 中添加 storage 段
在脑仓库根目录的gbrain.yml中新增storage:段(原文完整示例):
storage: # 版本控制目录(人工编辑、提交到 git)。 db_tracked: - people/ - companies/ - deals/ - concepts/ - yc/ - ideas/ - projects/ # 仅通过脑数据库持久化的目录(批量机器生成内容)。 # 写入磁盘时作为本地缓存,但不提交到 git; # `gbrain sync` 自动管理这些路径的 .gitignore; # `gbrain export --restore-only` 从数据库回填缺失文件。 db_only: - media/x/ - media/articles/ - meetings/transcripts/2.2 命名规范与弃用别名
- 规范键名是
db_tracked/db_only(引擎无关,PGLite 与 Postgres 均可用)。弃用别名git_tracked/supabase_only仍可加载,但每次进程只发出一次警告;建议手工重命名以消除告警。 - 路径必须以
/结尾(规范形式)。校验器会自动补全缺失的尾部斜杠,并一次性给出提示信息说明改动内容。 - 同一目录不能同时出现在两个层级——这是 tier-overlap 错误,
loadStorageConfig会抛出StorageConfigError,需编辑gbrain.yml消除重叠后重试。
2.3 源码级解析机制
配置解析器位于 src/core/storage-config.ts,loadStorageConfig是统一入口:
parseStorageYaml使用故意收窄的自研解析器(不依赖 gray-matter)。原文档在代码注释中记录了一个关键历史缺陷:pre-v0.22.3 的实现中 gray-matter 在无分隔符 YAML 时静默返回{data: {}},导致该特性在每次安装时都失效,重写后改为零依赖、行为可预测的解析。- 键名解析:
STORAGE_KEYS同时识别四个键(两个规范 + 两个弃用别名)。弃用键的解析顺序为:规范键存在则优先使用;仅弃用键存在则映射为规范键并提示重命名;两者同时存在时规范键胜出,且提示语更强烈(用户正处于迁移中间态)。 - 语义校验
normalizeAndValidateStorageConfig:尾部斜杠缺失属于"外观问题"(静默修复 + 一次性提示),层级重叠属于"语义错误"(直接抛出StorageConfigError,错误信息引用规范键名)。文件不存在、storage:段缺失或为空均返回null并给出一次性 sanity 警告,调用方可区分"未配置"与"空配置"。
路径匹配函数matchesTierDir(storage-config.ts 第 342-349 行)按完整路径段匹配:media/x/匹配media/x/foo但不匹配media/xerox/foo,杜绝前缀碰撞类 bug(eng review 的 Issue #5,D6 lock)。测试 test/storage-config.test.ts 中的 "regression — media/xerox does NOT match media/x" 用例专门锁定了这一行为。
三、gbrain sync:自动管理 .gitignore
当存在存储配置时,gbrain sync在每次成功同步后自动管理.gitignore条目:
- 将缺失的
db_only目录模式追加到.gitignore; - 幂等:重复运行不会产生重复条目(
manageGitignore用 Set 去重,同时识别dir与/dir两种写法,见 src/commands/sync.ts 第 5809-5816 行); - 稳定注释头便于 grep:追加的块以
# Auto-managed by gbrain (db_only directories)开头; --dry-run时跳过:预览模式不修改磁盘;blocked_by_failures状态时跳过:同步状态不一致时不写.gitignore;- git 子模块时跳过:子模块的
.git是文件而非目录,.gitignore改动无法在父仓库更新后幸存,会给出警告(源码第 5728-5746 行通过读取.git文件中的gitdir:路径判断——含/modules/的是子模块、含/worktrees/的是 worktree——worktree 是"一等公民"仓库,仍然正常管理); GBRAIN_NO_GITIGNORE=1时整体跳过:共享仓库场景的逃生舱,维护者不希望 gbrain 触碰.gitignore(源码第 5711-5713 行);- 写入失败(权限拒绝等)只记录不崩溃:
.gitignore管理是同步的副作用,绝不因副作用杀掉主任务(源码注释中的 D9 lock)。
追加后的.gitignore示例(原文档原样):
# Auto-managed by gbrain (db_only directories) media/x/ media/articles/ meetings/transcripts/易踩的坑——收集器输出碰撞:manageGitignore会检查已配置收集器(recipeoutput_pathsfrontmatter)声明的输出目录是否位于db_only路径内部。若碰撞,会给出警告:gitignored 的文件永远不会出现在 git 遍历的同步 diff 中,且gbrain import同样遵循.gitignore——收集器"跑绿了"但没有任何数据进入数据库。检测逻辑findDbOnlyCollisions位于 src/core/storage-config.ts 第 407-423 行,同步时的警告在 sync.ts 第 5771-5779 行;db_only_collector_collisiondoctor 检查会再次暴露同一陷阱。
相关的 doctor 检查:undeclared_db_only_pages会警告那些"没有对应磁盘文件、且位于所有已声明db_only路径之外"的数据库页面。引擎自身 derive 阶段的输出前缀(life/events/、atoms/、extracts/、dream-cycle-summaries/,以及写透importFromContent的concepts/)被隐式视为已声明(常量DERIVE_PHASE_DB_ONLY_DEFAULTS,storage-config.ts 第 368-378 行),因此健康的脑仓库无需把这些目录写进gbrain.yml也能保持安静。注意:这些前缀不会自动加入.gitignore,只有显式声明的db_only目录才会被追加。
还有一个大小写细节值得注意:effectiveDbOnlyDirs在求并集前会把已声明目录转为小写(storage-config.ts 第 396-398 行),因为该函数唯一消费者checkUndeclaredDbOnlyPages用.startsWith()匹配 slug,而 slug 在创建时一律小写(issue #3766)。但该小写化刻意不发生在normalizeAndValidateStorageConfig/loadStorageConfig中——manageGitignore需要保留真实大小写来匹配磁盘目录(Linux 大小写敏感),否则会破坏 gitignore 管理。
四、gbrain export --restore-only:从数据库恢复缺失文件
db_only内容在磁盘上只是"本地缓存",容器重启或全新 clone 后文件可能缺失。gbrain export --restore-only专门从数据库回填:
# 仅从数据库恢复缺失的 db_only 文件。 gbrain export --restore-only --repo /path/to/brain # 按页面类型过滤。 gbrain export --restore-only --type media --repo /path/to/brain # 按 slug 前缀过滤。 gbrain export --restore-only --slug-prefix media/x/ --repo /path/to/brain # 组合过滤。 gbrain export --restore-only --type media --slug-prefix media/x/ --repo /path/to/brain--restore-only的行为约束(依据 src/commands/export.ts 实现):
- repo 解析链:
--repo→ 类型化的sources.getDefault()→ 硬错误,绝不回退到当前目录(源码第 31-42 行,D5 约束;对应的gbrain storage status亦然,见 src/commands/storage.ts 第 77-83 行)。 - 无存储配置时直接拒绝:没有
storage:段就无从界定恢复范围,若回退到完整导出会静默倾倒整个数据库,因此在任何页面查询发出前即报错退出(源码第 52-60 行)。 - 只导出同时满足「匹配
db_only模式」且「磁盘缺失」的页面:实现上按每个db_only目录用 slugPrefix 引擎侧查询(issue #13 回归,测试 test/e2e/storage-tiering.test.ts 的 "slugPrefix filter on Postgres uses index-based range scan" 用例),并配合isDbOnly双保险过滤。在 200K 页大脑上若只有 5K 属于 db_only,这种按目录查询相比全表加载可带来约 40 倍查询量削减(源码注释原话)。 - 恢复文件写为
<slug>.md,slug 不是slugifyPath不动点(大小写、撇号、重音等历史手工键)时会在 frontmatter 中打上真实 slug 戳记,保证重新 import 不会悄悄改键(源码第 117-129 行)。 - 输出信息形如
Restoring N db_only pages to <dir>/与Restored N pages to <dir>/,进度条走 stderr,stdout 保持干净便于脚本解析。
E2E 测试中的"容器重启模拟"用例(storage-tiering.test.ts 第 147 行起)完整演示了该流程:先写入页面并同步(生成磁盘文件)→ 模拟容器重启删除磁盘文件 → 断言页面仍可从数据库列出 → 用--restore-only恢复并断言文件重新出现。
五、gbrain storage status:存储分层健康看板
# 人类可读状态。 gbrain storage status --repo /path/to/brain # 供脚本与编排器使用的 JSON 输出。 gbrain storage status --repo /path/to/brain --json输出包含:
- 各存储层级的页面总数;
- 各层级的磁盘占用分解;
- 需要恢复的缺失文件(默认显示前 10 个,完整列表在
--json中); - 配置校验警告;
- 当前层级目录清单。
原文档给出的示例输出(节选):
Storage Status ============== Repository: /data/brain Total pages: 15,243 Storage Tiers: ------------- DB tracked: 2,156 pages DB only: 12,887 pages Unspecified: 200 pages Disk Usage: ----------- DB tracked: 45.2 MB DB only: 2.1 GB Missing Files (need restore): ----------------------------- media/x/tweet-1234567890 media/x/tweet-0987654321 ... and 47 more Use: gbrain export --restore-only --repo "/data/brain" Configuration: -------------- DB tracked directories: - people/ - companies/ - deals/ DB-only directories: - media/x/ - media/articles/ - meetings/transcripts/源码层的几个实现要点(src/commands/storage.ts):
- 数据结构上用名义类型区分"页面计数"与"磁盘字节数"(
PageCountsByTiervsDiskUsageByTier,均带__brand品牌字段),让两种单位不同的映射在编译期就无法互换,杜绝显示 bug(eng review Issue #11)。 - 数据收集只做一次递归文件系统遍历(
walkBrainRepo),替代原先每页existsSync+statSync的方式——200K 页大脑原先约 40 万次系统调用,现在缩减为每目录一次 + 每个.md文件一次 stat(Issue #14)。 StorageStatusResult保持纯数据、无副作用,是稳定的 MCP/脚本契约(D14:storage_status 为只读的 MCP 暴露接口)。- 人类可读格式只用 ASCII 分隔符,保证终端通用可移植(D10 lock)。
- 未配置 gbrain.yml 时明确输出
No gbrain.yml configuration found.与All pages are stored in git by default.,不会假装统计有意义。 - 无显式
--repo时走getDefaultSourcePath,无本地路径时同样硬错误而不是静默回退。
六、验证规则:自动修复与硬错误
loadStorageConfig在解析后运行normalizeAndValidateStorageConfig:
- 自动修复(静默,附一次性信息提示):缺失尾部
/自动补全,'media/x'→'media/x/'; - 抛出
StorageConfigError(调用方得到干净的 exit-1 与可操作信息):同一目录同时出现在db_tracked与db_only(路由歧义)。
此外从源码可确认两个相关的失败语义:
- gbrain.yml 存在但不可读(权限拒绝等)会直接抛出——"大声失败"而不是静默禁用特性(storage-config.ts 第 185-193 行,D36 lock);
- gbrain.yml 存在但无
storage:段,或段为空,都会给出一性次控制台警告,提示"你的配置没有生效"而不是静默空转(Issue #1 lock)。
测试覆盖非常完备:test/storage-config.test.ts 包含合法配置零警告、层级重叠警告、缺尾斜杠警告、前缀边界、media/xerox不匹配回归、规范键 vs 弃用键优先级、弃用键一次性告警、无 storage 段警告、权限拒绝抛出等 13 组用例。
七、PGLite 引擎的能力边界与迁移路径
PGLite 引擎(gbrain 本地嵌入式 Postgres)下,db_only页面所在的"数据库"就是 gbrain 用于一切事务的本地文件。此时"卸载到数据库"的承诺在技术上空洞——但.gitignore整理仍然有用(把批量内容挡在 git 历史之外)。引擎检测到 PGLite 时会发出一次性软警告(storage.ts 第 104-114 行,sync.ts 第 5786-5792 行同样处理)。
要获得完整分层能力,需执行gbrain migrate --to supabase迁移到 Postgres。E2E 测试也专门断言了 Postgres 引擎下不会出现 PGLite 警告(storage-tiering.test.ts 第 256 行起)。
八、迁移策略与最佳实践
原文档给出的迁移路径(六步):
- 评估当前仓库:用
gbrain storage status了解当前分布; - 规划目录结构:确定哪些目录应 db_tracked、哪些 db_only;
- 创建
gbrain.yml:在仓库根目录添加存储配置; - dry-run 验证:
gbrain sync --dry-run验证行为——dry-run不会触碰.gitignore; - 执行真实同步:
gbrain sync成功后自动更新.gitignore; - 验证恢复:针对一个小型 db_only 目录测试
gbrain export --restore-only --repo .。
最佳实践清单:
- 目录命名:存储路径以
/结尾(规范形式),忘记时校验器会补全; - 从小处着手:先从明显是机器生成的目录开始放入
db_only; - 认真对待校验错误:层级重叠是错误而非警告,同步前必须修复;
- 定期测试恢复:在 staging 环境定期执行
--restore-only; - 记录决策:在
gbrain.yml中注释说明层级选择理由。
九、兼容性说明
- 向后兼容:没有
gbrain.yml的系统行为不变(所有页面默认全部 git 追踪); - 渐进增强:按需添加配置即可;
- 数据库不变:无论层级如何,所有数据始终保存在 Postgres 中;
- 既有工作流不变:所有现有
sync与export行为保留; - 弃用键:
git_tracked/supabase_only仍可加载并伴随一次性进程警告,未来版本将拒绝(storage-config.ts 顶部注释明确记录 sunset 计划)。
十、参考实现与测试入口
- 文档:docs/storage-tiering.md
- 配置解析与校验:src/core/storage-config.ts
.gitignore自动管理:src/commands/sync.ts(manageGitignore于第 5707 行)- 恢复导出:src/commands/export.ts
- 状态看板:src/commands/storage.ts
- 配置单测:test/storage-config.test.ts
- 端到端测试:test/e2e/storage-tiering.test.ts
【免费下载链接】gbrainGarry's Opinionated OpenClaw/Hermes Agent Brain项目地址: https://gitcode.com/gh_mirrors/gb/gbrain
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考