最近我的 GBrain 知识库挂了。
不算意外——用 PGLite(WASM 嵌入式 PostgreSQL)跑了几个月,终于撞上了那个传说中的 Aborted()。
但有趣的是这次解决问题的过程:我没有花时间修数据库,而是直接删了重建。事后复盘发现,这个”不修”的决策在大多数 PGLite 损坏场景下反而是对的。
这篇文章不教你”怎么修 PGLite 数据库”——因为很多时候根本修不了。我整理了 6 种真实损坏场景的判别逻辑和恢复策略,以及当 rm -rf 反而是最优解时的判断依据。
PGLite 是什么(30 秒速览)
PGLite(@electric-sql/pglite)是一个 WASM 版的 PostgreSQL 17.5,不需要安装 Postgres 服务,直接在进程里跑。典型用法:
const db = new PGLite('./path/to/pgdata') // 持久化到文件系统
- 纯 WASM 运行,3MB gzipped
- 单连接设计——一次只能有一个进程打开数据库
- 支持 pgvector、pg_trgm 扩展
- 数据存在文件目录(
~/.gbrain/brain.pglite/)
我用它做 GBrain 知识库的默认存储引擎。GBrain 之前还有另一篇文章写过迁移到 PostgreSQL 的过程,其中 PGLite 的单连接限制是主要迁移原因——但那是另一个故事了。
真实案例:2026-04-30 数据库丢了
问题现场:
- gbrain serve、gbrain export、gbrain query、gbrain health——全部报 Aborted()
- MCP 服务也无法连接
- 报错信息没有具体指示,就是一行 RuntimeError: Aborted()
排查过程:
1. 怀疑 PGLite 进程还在跑 → ps aux | grep gbrain → 无异常进程
2. 检查数据库目录 → ls -la ~/.gbrain/brain.pglite/ → 目录不存在
3. 确认文件已丢失(可能是之前某次操作异常中断导致)
决策结果:不用修,直接重建。
rm -rf ~/.gbrain/brain.pglite
gbrain init --pglite
gbrain import ~/wiki # 从 wiki 重新导入 36 个页面
gbrain embed --all # 重新生成 embedding(512维,HNSW索引)
重建耗时约 10 分钟。加上恢复时间不到 15 分钟。
为什么选择删除重建而不是尝试修复:
1. 数据目录已丢失 → 没有可修复的目标
2. 原始数据在 wiki 目录中完好 → 重建成本几乎为零
3. PGLite 的数据库是 WASM 内部状态和目录文件的复合体,没有 SQLite 的 .recover 命令
4. 重建顺便升级了 schema(vector(512) + HNSW)
核心原则:先确认
ls -la文件是否存在,再决定是修复还是重建。
六种 PGLite 损坏场景
“PGLite 挂了” 这件事在不同场景下意味着完全不同的东西。下面是最常见的六种:
| # | 场景 | 症状 | 根因 | 恢复策略 |
|---|---|---|---|---|
| 1 | 数据目录丢失 | Aborted(),目录不存在 |
kill -9、系统异常重启 | 删除重建 |
| 2 | WASM 运行时崩溃 | Aborted(),但目录还在 |
WASM 兼容性问题/版本升级 | 升级/降级 PGLite 版本 |
| 3 | 单连接锁冲突 | MCP 跑着时 CLI 报错 | PGLite 架构限制,非损坏 | 等锁释放或关 MCP |
| 4 | Embedding 数据损坏 | 搜索异常,非数字向量 | 写入中断 | gbrain embed --stale |
| 5 | Schema 版本不匹配 | 升级后打不开数据库 | GBrain 升级后 schema 变化 | gbrain doctor + apply-migrations |
| 6 | 分支 migration 回不来 | 跑了分支版本 migration 后 master 不认 | 开发分支 schema 版本冲突 | 删除重建或回退版本 |
关键认知:场景 1~3 在用户看来都是 Aborted(),但处理方式完全不同。目录丢失(场景 1)直接重建比尝试修复快 10 倍,WASM 崩溃(场景 2)升级版本可能数据完好无损,锁冲突(场景 3)根本就是假损坏,等几秒就好。
Embedding 损坏的特殊性(场景 4)
这是唯一”修比删划算”的场景。GBrain 的 embedding 列是 JSONB 类型,部分损坏不会阻塞整个数据库——tryParseEmbedding() 函数会逐行尝试解析,遇到损坏行 WARN 跳过而不是崩溃。
// src/core/utils.ts — 简化
function tryParseEmbedding(row: any): number[] | null {
try {
return JSON.parse(row.embedding)
} catch {
console.warn(`损坏的 embedding,跳过: ${row.id}`)
return null
}
}
这种设计让修复成本变得极低:gbrain embed --stale 只重新生成损坏的 embedding。
决策树:删还是修?
┌─────────────────────┐
│ PGLite 报错/异常 │
└──────────┬──────────┘
│
┌────────────┴────────────┐
│ 检查文件是否存在 │
│ ls -la ~/.gbrain/brain │
│ .pglite/ │
└────┬──────────────┬──────┘
│ │
目录丢失 目录存在
│ │
┌─────┴─────┐ ┌─────┴─────┐
│ 删除重建 │ │ 检查错误 │
│ 成本低 │ │ 类型 │
│ 原始数据 │ └─────┬─────┘
│ 在别处 │ │
└───────────┘ ┌──────┴─────────┐
│ │
WASM 崩溃 锁冲突/数据损坏
│ │
┌────────┴───┐ ┌──────┴──────┐
│ 升级版本 │ │ gbrain doctor│
│ 或降级重试 │ │ 诊断再修 │
└────────────┘ └──────┬──────┘
│
┌───────────┴───────────┐
│ │
可修复 不可修复
│ │
┌──────┴───────┐ ┌────────┴───────┐
│ gbrain │ │ 数据在别处 │
│ repair-jsonb │ │ → 删除重建 │
│ embed --stale│ │ 数据不在别处 │
│ 等工具 │ │ → 尝试 pg_dump│
└──────────────┘ │ 然后重建 │
└────────────────┘
决策维度对比
| 维度 | 删除重建 | 修复 |
|---|---|---|
| 适用场景 | 目录丢失、数据可重导、schema 大版本变化 | WASM 崩溃、embedding 损坏、小版本迁移 |
| 恢复时间 | 取决于数据量(需重新 embedding) | 分钟级(仅修损坏部分) |
| 风险 | 无(原始数据在别处) | 低(只读诊断) |
| 操作复杂度 | 低(rm → init → import → embed) | 中等(需定位具体问题) |
| 是否需要备份 | 不需要(依赖原始数据源) | 建议事前备份 |
四条关键原则
-
数据目录缺失 → 不要尝试修复,直接重建
PGLite 的数据库文件是 WASM 运行时状态和文件目录的复合体。目录不存在意味着一切都没了,修复工具没有操作目标。 -
WASM 运行时崩溃 → 先升级/降级版本,数据可能还在
PostgreSQL 的数据文件格式很稳定。换一个版本 PGLite,同版本的数据库文件可能直接可用。 -
部分数据损坏 → 先诊断再修,不要全局重建
只有 embedding 损坏的场景下,gbrain embed --stale比重建快得多。但需要先确认这不是全局损坏。 -
锁冲突误判为损坏 → 等几秒重试或停 MCP
这是 PGLite 单连接设计的天生限制,不是数据问题。GBrain 有pglite-lock.ts文件锁机制防止误判。
社区最新动态:PGLite 正在变好
写这篇文章时发现的几个有趣的社区动向,说明 PGLite 的损坏恢复正在从”全手工”走向”半自动化”:
PR #994 — 自动 WAL 恢复(未合并,2026-05)
yestheboxer 提交了一个 PR,在 PGLite 启动时检测是否因为 WAL/checkpoint 损坏导致崩溃。如果是,自动重置 WAL 并重试一次。关键设计:
- 只针对 PostgreSQL 17 的文件布局
- dataDirRepair: 'none' 可完全退出
- 恢复成功后实例暴露 repairedDataDir 字段
- 有趣的是维护者的回复:”我们觉得这个应该做成独立工具”
PR #892 — NodeFS 文件锁(未合并,2026-02)
reisepass 的 PR 解决了一个真实痛:同时在两个终端跑 PGLite 导致文件损坏。方案很简单——加一个锁文件,第二个进程直接拒绝打开。同步提交者评论称这”应该在 PGLite 的 dev 模式默认开启”。
Issue #1012 — Fatal error, can’t start instance anymore
pglite@0.4.6 + Debian 13 + node v24 的崩溃报告,目前还没有修复方案。如果你在用这个组合,建议锁定版本。
Issue #223 — macOS 26.3 兼容性
经典的 WASM 版本兼容问题。修复方法:降级到 @electric-sql/pglite@0.4.3。
备份策略
PGLite 没有 SQLite 的 .backup 命令,也不支持标准 PostgreSQL 的 pg_dump。但有几种务实的方案:
| 方案 | 操作 | 适合场景 |
|---|---|---|
| 数据源备份 | 保持 wiki/markdown 目录作为唯一可信源 | GBrain 模式(数据在 markdown 文件中) |
| 文件目录备份 | cp -a ~/.gbrain/brain.pglite/ backup/ |
快速快照,定期执行 |
| 迁移到 PostgreSQL | gbrain migrate --to supabase |
数据重要时迁移到标准 PG |
| 版本锁定 | 锁定 @electric-sql/pglite 版本 |
防止 WASM 兼容性崩溃 |
而我学到的(来自 2026-04-30 教训):
重要数据不要只存在 PGLite 里。 它能跑但不适合做唯一存储。如果原始数据在别处(markdown 文件、git 仓库),重建就是几分钟的事。如果数据只存在于 PGLite 的二进制目录里,丢了才是真·灾难。
GBrain 命令速查
# 第一步:诊断(别急着动手)
gbrain doctor # 全面健康检查
gbrain doctor --json # JSON 输出(便于 agent 处理)
gbrain integrity check # 只读报告(放心跑)
gbrain integrity auto # 自动修复(三档置信度)
# 修复(确认是局部问题再调用)
gbrain repair-jsonb [--dry-run] # 修复 JSONB 双编码
gbrain apply-migrations --yes # 强制应用迁移
gbrain embed --stale # 重新生成损坏的 embedding
gbrain embed --all # 全量重新生成
# 重建(原始数据在别处时最快)
rm -rf ~/.gbrain/brain.pglite # 删除数据库
gbrain init --pglite # 重新初始化
gbrain import ~/wiki # 重新导入数据
gbrain embed --all # 重新生成 embedding
# 迁移(终极方案——换掉 PGLite)
gbrain migrate --to supabase # 迁移到 PostgreSQL
不止适用于 PGLite
这篇文章虽然以 PGLite + GBrain 为案例,但核心决策逻辑适用于所有嵌入式数据库(SQLite、LevelDB、RocksDB):
如果数据源在别处 → 删了重建最快。 这也是为什么很多系统设计成”数据源 + 可重建索引”的模式——不是为了让数据库随时可以丢,而是让”丢了也不怕”。
如果数据库是唯一存储 → 备份是第一优先级,修复是最后手段。 花时间研究修复工具,不如花时间做一个可靠的备份策略。
回到最初的问题:删库重建还是修?
如果你的原始数据还在,且重建成本可以接受——删了重建永远比修快。这个结论听起来反直觉,但在用了半年的嵌入式数据库上,它是对的。