关于 PgVouch
PgVouch 是用于检查 PostgreSQL 模式漂移、迁移锁和数据一致性的只读 MCP 服务及命令行工具。它面向数据库团队和编码 Agent,提供确定性校验、安全改写建议与影子运行验证。
主要功能
- 01检测 PostgreSQL 模式对象漂移
- 02通过校验和验证数据并定位差异行
- 03分析迁移锁及当前锁冲突
- 04建议更安全的分步迁移改写
- 05通过影子运行校验计划并生成回滚 SQL
适用场景
- 01比较预发布与生产环境的模式
- 02部署前检查迁移锁风险
- 03验证复制或同步的数据库内容
- 04在 CI 中审查迁移拉取请求
MCP 相关
通过 stdio 向 AI 客户端提供 7 个只读工具,用于检查 PostgreSQL 数据库和迁移计划。LLM 负责提出计划,校验器与影子运行负责判定是否通过。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
npx -y pgvouchSOURCE_DATABASE_URL密钥Read-only postgres:// URL of the reference databaseTARGET_DATABASE_URL密钥Read-only postgres:// URL of the database to checkPGVOUCH_LLMOptional planner LLM: none (default), ollama or gemini本地服务需要在你的电脑上安装运行环境(如 Node.js 18+)。各客户端的配置文件位置不同,请参考客户端文档。
工具
本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。
详细介绍
PgVouch
一个 MCP 服务 + CLI,让 AI 助手能够安全地检查、规划并_证明_ PostgreSQL 迁移。 大模型提出方案;确定性代码负责裁定。

使用预置的演示数据库(100 万笔事务、200 万条账本记录)运行的实际输出;目标数据库按下方第 2 步所述设置为故障状态。输入为实时录制;命令运行时间经过缩短(verify 运行耗时 14.0 s)。
问题
编写 ALTER TABLE 是数据库迁移中最简单的部分。难点在于:
- 有什么差异? 不同环境中的模式会发生漂移(热修复、迁移失败、手动更改)。
- 会造成什么锁定? 在繁忙的表上执行
CREATE INDEX会阻塞所有写入,直到操作完成。即使ALTER TABLE只需“1 ms”,也可能因在锁队列中等待而导致服务中断。 - 数据真的相同吗? “行数一致”并不能证明这一点。
- 哪些行有问题? 验证失败时,你需要确切的行,而不是“这张包含 200 万行的表里有些问题”。
AI 助手现在会编写迁移,但它们会臆造对象、忽略锁,也无法证明任何事情。 PgVouch 为它们(以及你)提供只读工具,回答上述四个问题,并通过确定性检查验证每一条 AI 建议。
结果(实测,非估算)
所有数据均来自针对预置 Docker 数据库运行的 npm run eval。完整表格见 evals/。
| 项目 | 结果 |
|---|---|
| 检测 32 个预置场景中的漂移(表、视图、函数、触发器、枚举、扩展、RLS) | 精确率 100%,召回率 100%(34 个真正例,0 个假正例,0 个漏检) |
| 通过校验和二分查找发现的确切差异行 | 预置行变更的 100%,0 个错误行 |
| 为找到这些差异而读取的行数 | 311 行,而非 6,079,997 行(不匹配表中的行数,两端合计) |
预测的锁模式与实际获取的锁模式(从 pg_locks 读取) | 在 PostgreSQL 13、14、15、16 和 17 上,36 条语句全部匹配 |
预测的表重写与实际重写(pg_class.relfilenode 已更改) | 35 条语句全部匹配 |
| 复制后序列落后于数据(下次 INSERT 会遇到重复键) | 发现 1/1,10 次检查中 0 次误报 |
| 通过真实架构副本影子运行的纯规则迁移计划 | 27/27 |
| LLM 计划(本地 llama3.2,3B),首个包含 21 个场景的套件中 17 个漂移场景,仅验证器接受率(v0.1.0) | 首次尝试有 5.9% 有效,重试一次后为 17.6%,82.4% 回退到规则计划;0/3 个被接受的 LLM 计划通过影子运行 |
迁移期间的应用停顿,在 100 万行表上测量,每隔约 10 ms 执行一次探测查询(evals/results-rewrite.md):
| 迁移 | 原始方式:最长停顿 | PgVouch 重写:最长停顿 | 重写总耗时 |
|---|---|---|---|
CREATE INDEX | 1.58 秒 | 47 毫秒 | 2.1 秒 |
ADD FOREIGN KEY(200 万行) | 1.01 秒 | 49 毫秒 | 1.2 秒 |
ADD CHECK | 440 毫秒 | 63 毫秒 | 0.85 秒 |
SET NOT NULL | 625 毫秒 | 31 毫秒 | 0.68 秒 |
ADD COLUMN ... NOT NULL DEFAULT gen_random_uuid() | 10.94 秒 | 964 毫秒 | 108.7 秒(分批回填) |
ALTER COLUMN merchant_id TYPE bigint | 6.51 秒 | 93 毫秒 | 28.0 秒(包括重新创建其外键;+3 个手动步骤) |
**LLM 数据说明了什么:**小型本地模型无法可靠地规划迁移。这就是 PgVouch 从不信任它的原因。验证器拒绝了 17 个计划中的 14 个(SQL 无法解析、虚构或重复的对象、会阻塞的 DDL)。它接受的 3 个计划安全但不完整,只有影子运行发现了这些问题(例如缺少索引,以及重建已删除表时遗漏的序列)。确定性规则计划通过了全部 17 个场景(加入后续场景后为 27/27)。详情见:evals/results-llm.md。
因此发生的变化:这些数据是在仅由验证器做出决定时测得的。现在,LLM 计划只有在通过验证器并且通过影子运行后才会被接受,且影子运行的结果必须与源端完全一致;影子运行失败时,会将结果反馈给 LLM 重试一次,之后 PgVouch 会回退到规则计划。因此,上述 3 个不完整计划现在都会被拒绝。新流程下的 LLM 评估尚未重新运行,因此这里没有新的数据。
权衡显而易见:需要回填的重写总耗时要长得多,但应用可以持续运行。 这些是在笔记本电脑(Apple M1、8 核、Docker Desktop)上进行的单次运行。绝对数值可能会有变化;重要的是差距。
工作原理
flowchart LR
A["AI assistant<br/>(Claude Code, Cursor)"] -- "MCP (JSON-RPC, stdio)" --> S
U["You (terminal)"] -- CLI --> S
subgraph S["PgVouch core"]
I["F1 introspect<br/>pg_catalog"] --> D["F2 diff<br/>(pure function)"]
D --> P["F7 planner<br/>LLM + guardrail validator"]
L["F5 lock analyzer<br/>(Postgres parser)"] --> R["F6 safe rewrites"]
R --> P
P --> SH["F10 shadow run<br/>(throwaway container)"]
V["F3 chunked checksums"] --> B["F4 bisection"]
P --> RC["F12 receipt<br/>(SHA-256)"]
end
S -- "read-only SQL" --> SRC[("source DB")]
S -- "read-only SQL" --> TGT[("target DB")]
P -. "structured facts only, no rows" .-> LLM["Ollama (local)<br/>or Gemini"]
黄金法则: 大模型绝不会接触数据库,也不会判定通过或失败。它负责提出建议;解析器、规则和影子运行负责做出判断。
| # | Feature | How |
|---|---|---|
| F1 | Schema 内省 | pg_catalog(而不是 information_schema,后者会隐藏索引方法、部分索引谓词和 NOT VALID) |
| F2 | 漂移检测 | 对表、列、索引、约束、序列、视图、函数、触发器(包括已禁用的触发器)、枚举类型、扩展和行级安全执行纯函数 diffSchemas(source, target):结果确定且排序,并遵循严重性规则,绝不猜测重命名 |
| F3 | 数据验证 | 在设置已规范化的会话中,于 REPEATABLE READ 快照内按主键分块计算 md5(string_agg(md5(row::text) ORDER BY pk))。文本键按字节顺序排列(COLLATE "C"),因此使用不同排序规则的服务器也能得出一致结果。还会检查目标端的每个 identity/serial 序列是否都领先于其数据。对于仍在接收复制的目标,--recheck 会在延迟后再次检查差异,并只报告始终未能追上的差异 |
| F4 | 校验和二分 | 类似 Merkle 树:在中位键处拆分不匹配的分块,并仅递归检查哈希不同的部分 |
| F5 | 锁分析器 | libpg-query(Postgres 自带的解析器,基于 WASM)→ 规则表 → 根据 reltuples 评估风险 |
| F6 | 安全重写 | CONCURRENTLY、NOT VALID + VALIDATE、UNIQUE ... USING INDEX、扩展/收缩、每批执行 COMMIT 的分批 DO 循环 |
| F7 | 受保护的大模型规划器 | 经 zod 检查的 JSON → 验证器(解析、允许列表、对象存在性检查、禁止不安全的 DDL)→ 影子运行(结果必须与源端一致)→ 携带错误重试一次 → 仅使用规则的回退方案 |
| F8 | MCP 服务 | 通过 stdio 提供 7 个只读工具 |
| F9 | 评测套件 | 32 个漂移/数据场景、36 条具有 Postgres 实际结果作为基准的锁语句,以及一项负载下的停滞基准测试 |
| F10 | 影子运行 | 将目标端的架构复制到一次性容器中,应用计划,并将结果与源端进行差异比较 |
| F11 | 可逆性 | 为每个步骤标记 reversible / reversible-with-backfill / data-lossy,并生成回滚 SQL |
| F12 | 收据 | 包含漂移 + 验证 + 计划 + 影子运行结果的 JSON,并附带对规范化(按键排序)JSON 计算的 SHA-256 |
| F13 | 锁队列预检 | “现在可以安全运行吗?”:将每条语句所需的锁与实时 pg_locks + pg_stat_activity 对照,使用 Postgres 完整的 8×8 锁冲突表 |
| F14 | PR 审查操作 | pgvouch review --format markdown + GitHub Action:在作业摘要和一条 PR 评论中提供锁分析以及对变更迁移的安全重写 |
快速开始
使用 PgVouch 有两种方式:
| 选项 A:npm 包 | 选项 B:克隆仓库 | |
|---|---|---|
| 适用场景 | 自己的数据库、CI 或 AI 助手 | 在预置演示数据库上试用所有功能,或参与贡献 |
| 需要 | Node 22(或 20.12+) | Node 22(或 20.12+)和 Docker |
| 运行命令的方式 | pgvouch <command>(或 npx pgvouch <command>) | npm run cli -- <command> |
选项 A:使用 npm 包
1. 立即试用,无需数据库。 指定任意迁移文件:
npx pgvouch locks my_migration.sql --offline # the locks each statement takes, and the risk
npx pgvouch rewrite my_migration.sql --offline # a safe, non-blocking version of the same migration
手头没有迁移文件?创建一个:echo "CREATE INDEX idx_amount ON transactions (amount);" > my_migration.sql
2. 安装(可选:无需安装即可运行 npx pgvouch <command>):
npm install -g pgvouch
pgvouch --help
3. 指定数据库。 在每个数据库上创建一个只读角色(SQL 语句见此处),然后在运行 PgVouch 的目录中放置一个 .env 文件:
SOURCE_DATABASE_URL=postgres://pgvouch_ro:PASSWORD@staging-host:5432/your_db
TARGET_DATABASE_URL=postgres://pgvouch_ro:PASSWORD@prod-host:5432/your_db
PGVOUCH_LLM=none # rules-only plans; set it to ollama to let a local LLM propose plans
环境中已设置的变量优先于 .env。然后检查两个连接是否均为只读:
pgvouch doctor # must say read_only=true and write privileges: none
4. 运行:
pgvouch diff # schema drift (exit code 1 if any)
pgvouch verify --rows # checksums + exact differing rows
pgvouch verify --rows --recheck 3 # target still being replicated to: ignore rows in flight
pgvouch locks my_migration.sql # lock impact per statement, with real table sizes
pgvouch preflight my_migration.sql # would it have to wait for locks RIGHT NOW?
pgvouch rewrite my_migration.sql # safe multi-step script
pgvouch review --format markdown migrations/*.sql # PR-comment report (offline, used by the GitHub Action)
pgvouch plan --no-llm # rules-only plan that makes target match source
pgvouch plan # LLM plan (Ollama) behind guardrails
pgvouch shadow # plan, then prove it on a throwaway container (needs Docker)
pgvouch receipt --shadow # hashed audit record
pgvouch receipt-verify pgvouch-receipt.json
locks --fail-on high 会以代码 1 退出,因此可用于在 CI 流水线中拦截风险较高的迁移。
preflight 回答的是另一个问题:不是“这会锁住什么?”,而是“现在是否有人持有或正在等待冲突锁?”。它会列出它需要排在其后等待的会话(pid、用户、应用程序、状态、事务时长;绝不显示查询文本),并提示 CREATE INDEX CONCURRENTLY 会等待数据库中的所有较早事务。若迁移需要等待,它会以代码 1 退出。PgVouch 不会终止会话。
5. 另外: 可通过AI 助手使用它(npx -y pgvouch mcp),也可以使用 GitHub Action(其中的第 4 步)检查每个迁移拉取请求。
选项 B:克隆仓库(包含演示数据库)
仓库包含两个已填充数据的 Postgres 数据库,因此你无需接触真实数据库即可试用所有功能。要求:Node 22(或 20.12+)和 Docker。
git clone https://github.com/Shubh-Sgr/pgvouch.git && cd pgvouch
npm install
cp .env.example .env
npm run db:up # two Postgres 16 containers, seeded: ~3 min on first start
npm run cli -- doctor # confirms both connections are read-only
| 容器 | 端口 | 内容 |
|---|---|---|
source-db | 5433 | 金融科技架构,10 张表:1M 条 transactions、2M 条 ledger_entries、复合主键表和无主键表 |
target-db | 5434 | 完全相同的副本。评估场景会在它的临时副本中制造偏差。 |
然后按照下方的分步指南:测试所有功能操作:有意破坏目标数据库,观察各项功能如何发现并修复问题。
从克隆仓库运行命令
选项 A 中的所有命令都可在此处通过 npm run cli -- <command> 运行。这些命令使用仓库中的示例迁移:
npm run cli -- diff # schema drift (exit code 1 if any)
npm run cli -- verify --rows # checksums + exact differing rows
npm run cli -- verify --rows --recheck 3 # target still being replicated to: ignore rows in flight
npm run cli -- locks examples/risky-migration.sql # lock impact per statement
npm run cli -- preflight examples/risky-migration.sql # would it have to wait for locks RIGHT NOW?
npm run cli -- rewrite examples/risky-migration.sql # safe multi-step script
npm run cli -- review --format markdown examples/*.sql # PR-comment report (offline, used by the GitHub Action)
npm run cli -- plan --no-llm # rules-only plan
npm run cli -- plan # LLM plan (Ollama) behind guardrails
npm run cli -- shadow # plan, then prove it on a throwaway container
npm run cli -- receipt --shadow # hashed audit record
npm run cli -- receipt-verify pgvouch-receipt.json
在 8 GB 笔记本电脑上运行
PgVouch 是在 8 GB MacBook Air(M1)上构建并测量的。遵循以下建议即可保持运行流畅:
- 为 Docker Desktop 分配 3 GB 内存(Settings → Resources)。两个数据库在
docker-compose.yml中各限制为 768 MB,影子容器则限制为 256 MB。 - 一次只运行一个高负载任务:
db:up、集成测试和评估都会创建或扫描数百万行。 - 默认设置为
PGVOUCH_LLM=none,因此除非你启用模型,否则绝不会向模型发送任何内容。使用 LLM 规划器时,Ollama 会在最后一次请求后 30 s 卸载模型。 - 除非你需要这些数据,否则请跳过
npm run eval -- --llm ...(使用 3B 模型时约需 1 小时);默认评估不会调用 LLM。 - 想要更轻量的配置?减少填充的数据行数:
npm run db:down && SEED_TRANSACTIONS=100000 npm run db:up(只需几秒,而非约 3 分钟)。CI 使用 20,000 行。已发布的评估数据和npm run eval需要默认的 1,000,000 行。
分步操作:测试每项功能
本演练使用 选项 B 中的两个演示数据库。你会故意破坏目标数据库,然后观察每项功能如何发现并修复问题。
请在 pgvouch 文件夹中逐条运行命令。想自动执行吗? npm run demo 会运行步骤 1–9,中途暂停,并在最后修复目标数据库。
macOS:如果命令因
Operation not permitted/EPERM uv_cwd而失败,请授予终端访问该文件夹的权限:系统设置 → 隐私与安全性 → 文件与文件夹 →(你的终端)→ 文稿。
1. 基准状态:一切匹配
npm run cli -- diff # "No schema drift: source and target match."
npm run cli -- verify # "IDENTICAL": 10 tables, ~3.1M rows compared by hash
2. 破坏目标数据库(这模拟“有人犯了错”;PgVouch 本身无法写入)
docker exec pgvouch-target-db-1 psql -U postgres -d fintech -c "DROP INDEX transactions_account_created_idx"
docker exec pgvouch-target-db-1 psql -U postgres -d fintech -c "ALTER TABLE ledger_entries DROP CONSTRAINT ledger_entries_transaction_id_fkey"
docker exec pgvouch-target-db-1 psql -U postgres -d fintech -c "ALTER TABLE customers ADD COLUMN legacy_code int"
docker exec pgvouch-target-db-1 psql -U postgres -d fintech -c "UPDATE transactions SET amount = amount + 1 WHERE id = 424242"
docker exec pgvouch-target-db-1 psql -U postgres -d fintech -c "DELETE FROM ledger_entries WHERE id IN (777001, 777002)"
3. 架构漂移(F1 + F2)
npm run cli -- diff
预期有 3 项:缺失的索引(低)、缺失的外键(高)、额外的 legacy_code 列(中)。退出代码为 1。
4. 精确差异行(F3 + F4)
npm run cli -- verify --table transactions ledger_entries --rows
预期出现 changed id=424242 columns: amount、missing_in_target id=777001 和 id=777002,以及显示从 3M 行中获取约 150 行的 bisection: 行。
5. 迁移的锁影响(F5)
npm run cli -- locks examples/risky-migration.sql
npm run cli -- locks examples/risky-migration.sql --fail-on high; echo "exit code: $?" # 1 = a CI pipeline would stop here
每条语句都会显示其锁(例如,SHARE ... blocks writes)、是否会扫描或重写表,以及风险级别;此外还会警告缺少 lock_timeout。
5b. 现在运行安全吗?(F13)
打开第二个终端,并保持一个事务处于打开状态,模拟被遗忘的会话:
docker exec -it pgvouch-target-db-1 psql -U postgres -d fintech
# then, inside psql: BEGIN; SELECT count(*) FROM accounts; (don't COMMIT yet)
回到第一个终端:
npm run cli -- preflight examples/risky-migration.sql
预期出现 WOULD WAIT behind 1 session(s):ALTER TABLE accounts ADD COLUMN 需要 ACCESS EXCLUSIVE 锁,而打开的会话在 accounts 上持有 ACCESS SHARE 锁(显示 state=idle in transaction 及其持续时间)。其他语句都没问题:外键需要 SHARE ROW EXCLUSIVE 锁,该锁不会与读操作冲突。在 psql 中输入 COMMIT; 并再次运行:SAFE NOW。
6. 安全地重写相同的迁移(F6)
npm run cli -- rewrite examples/risky-migration.sql
预期首先出现 SET lock_timeout。CREATE INDEX 会改为 CONCURRENTLY,外键会先设置为 NOT VALID,再进行 VALIDATE,新建的 NOT NULL 列则会使用分批回填循环。
7. 规划修复(F7 + F11)
npm run cli -- plan --no-llm # rules-only: each step has risk, reversibility and rollback SQL
npm run cli -- plan # optional, ~2 min: the LLM plans (needs Ollama + Docker); watch the validator and the shadow run reject bad plans
除非传入 --allow-data-loss,否则会注释掉可能导致数据丢失的 DROP COLUMN legacy_code 步骤。
8. 在一次性副本上验证计划(F10)
npm run cli -- shadow --no-llm
预期出现 Shadow run: PASS,每一步的状态都是 applied,并显示 the shadow matches the source schema。
9. 防篡改回执(F12)
npm run cli -- receipt --no-llm --out receipt.json
npm run cli -- receipt-verify receipt.json # "OK: receipt intact"
编辑 receipt.json 中任意值,然后再次运行 receipt-verify:会报告 MODIFIED。
回执仅按主键列出差异行和变更列,因此可以附加到工单,而无需复制个人数据。--include-values 会添加行值。
10. 从 AI 助手使用(F8): 请先按照下文从 AI 助手(MCP)使用操作,然后询问 “使用 pgvouch 查找架构漂移,以及 ledger_entries 中的差异行。”
11. 重置
npm run db:down && npm run db:up # fresh, identical databases again (~3 min)
自动化测试: npm test(单元测试,无需数据库)和 npm run test:integration(需要数据库)。
从 AI 助手(MCP)使用
MCP(Model Context Protocol)是 AI 助手调用外部工具的标准方式。PgVouch 以本地 MCP 服务的形式运行:助手(Claude Code、Cursor、……)将其作为子进程启动,并通过 stdin/stdout 与其通信。然后,你可以用日常语言提问,由助手决定调用哪些 PgVouch 工具。
PgVouch 已列入官方 MCP Registry,名称为 io.github.Shubh-Sgr/pgvouch,因此读取该注册表的客户端和目录都能找到它。无论如何,它都在你的机器上运行:注册表只说明如何启动它。
步骤 1:确保数据库已启动
npm run db:up # in the repo; or use your own databases (see "Use it on your own databases")
npm run cli -- doctor # both connections must say read_only=true
步骤 2:注册服务
选择一个选项。npx -y pgvouch mcp 会下载并启动已发布的软件包,因此此步骤无需克隆仓库。
Claude Code(终端或桌面应用)。 在安装了 claude CLI 的任意终端中运行一条命令:
claude mcp add pgvouch \
-e SOURCE_DATABASE_URL=postgres://pgvouch_ro:pgvouch_ro_local@localhost:5433/fintech \
-e TARGET_DATABASE_URL=postgres://pgvouch_ro:pgvouch_ro_local@localhost:5434/fintech \
-e PGVOUCH_LLM=none \
-- npx -y pgvouch mcp
添加 --scope project,即可将其存储在项目的 .mcp.json 中(通过 git 与团队共享),而不是仅为你个人配置。若要改为运行本地克隆版本(在执行 npm run build 后),请将 npx -y pgvouch mcp 替换为 node /absolute/path/to/pgvouch/dist/cli/index.js mcp。
或者使用配置文件。 适用于 Claude Code(项目根目录中的 .mcp.json)和 Cursor(项目中的 .cursor/mcp.json,或供所有项目使用的 ~/.cursor/mcp.json)。将 examples/mcp.json 复制到相应位置。
或者使用 Docker,无需 Node。 使用 examples/mcp-docker.json。在容器中,localhost 指向容器自身,因此这些 URL 使用 host.docker.internal 来访问你机器上的数据库。在仓库目录中构建一次镜像:docker build -t pgvouch .
PGVOUCH_LLM=none 表示 plan_migration 会返回仅基于确定性规则的计划。设置为 ollama 可让本地模型提出计划。计划仍须通过验证器和影子运行,而影子运行需要在运行服务的机器上安装 Docker。若没有 Docker,计划会回退到规则计划,并在输出中说明原因。
步骤 3:检查连接状态
- Claude Code: 运行
claude mcp list(应显示pgvouch ... ✓ Connected),或在会话中输入/mcp。添加服务后,请启动一个新会话。 - Cursor: 前往“设置”→“MCP”。
pgvouch应显示绿点和 7 个工具。编辑文件后请重启 Cursor。
步骤 4:提问
| 向助手提问 | 调用的工具 |
|---|---|
| “目标数据库是否与源数据库存在偏差?请按严重程度对差异分组。” | detect_drift |
| “源数据库和目标数据库中的数据是否完全一致?” | verify_data |
“ledger_entries 中具体哪些行不同?” | find_differing_rows(仅返回键和已更改的列) |
| “显示这些行的实际值。” | find_differing_rows,并设置 includeValues: true |
“ALTER TABLE transactions ALTER COLUMN merchant_id TYPE bigint 会锁定什么,锁定多久?” | analyze_locks |
“现在运行 ALTER TABLE accounts ADD COLUMN note text 是否安全?” | check_lock_queue |
| “重写此迁移,使其不会阻塞生产环境:……” | suggest_safe_rewrite |
| “规划一个安全迁移,使目标数据库与源数据库一致。” | plan_migration |
典型的 Agent 工作流:“检查目标数据库是否存在偏差,解释有风险的项目,并提供安全的迁移计划”。助手会调用 detect_drift,然后调用 plan_migration,也可能对结果运行 analyze_locks。
安全性: 所有 7 个工具均为只读(标注为 readOnlyHint: true)。它们都无法写入你的数据库;返回的计划是 SQL,供你自行审查和执行。唯一的副作用是:配置了 LLM 时,plan_migration 会启动一个短暂运行的本地 Postgres 容器来执行影子运行,并在之后将其移除。连接字符串来自上述配置,绝不会来自对话。只有在明确请求时才会返回行值。
故障排除
| 症状 | 解决方法 |
|---|---|
| 服务显示“failed”/未连接 | 使用相同设置手动启动:SOURCE_DATABASE_URL=... TARGET_DATABASE_URL=... npx -y pgvouch mcp。它应打印 pgvouch MCP server ... ready on stdio(然后按 Ctrl-C)。配置错误会指出缺少哪个变量。(服务不会读取 .env;这些变量必须来自 MCP 配置。) |
Cannot find module .../dist/cli/index.js(本地克隆) | 运行 npm run build,并确认路径是绝对路径。 |
工具报错 ECONNREFUSED/Connection terminated | 数据库未运行或仍在填充数据。运行 npm run db:up 并等待 Healthy。 |
| Docker 版本无法连接数据库 | 在 URL 中使用 host.docker.internal,而不是 localhost。 |
macOS 上出现 EPERM/“Operation not permitted” | 授予启动服务的应用(你的终端/Cursor)访问该文件夹的权限:系统设置 → 隐私与安全性 → 文件与文件夹。 |
再次移除它:运行 claude mcp remove pgvouch,或从 JSON 文件中删除该条目。
在你自己的数据库上使用
演示数据仅供试用。要在实际场景中使用 PgVouch:
1. 选择一对数据库。 适用哪些功能取决于数据库组合:
| 场景 | 源数据库 → 目标数据库 | 用途 |
|---|---|---|
| 预发布环境与生产环境(数据本应不同) | staging → prod | diff、plan、shadow(仅限架构) |
| 迁移数据库(迁移到云端、升级版本、蓝绿部署) | old → new | diff 和 verify --rows(数据应完全一致);复制仍在运行时添加 --recheck 3 |
| 副本/CDC 管道检查 | primary → replica | verify --rows --recheck 3 |
| 审查即将执行的迁移 | (将要执行迁移的数据库) | locks、rewrite |
2. 在每个数据库上创建一个只读角色(以管理员身份操作):
CREATE ROLE pgvouch_ro LOGIN PASSWORD 'choose-a-strong-password';
ALTER ROLE pgvouch_ro SET default_transaction_read_only = on;
GRANT CONNECT ON DATABASE your_db TO pgvouch_ro;
GRANT USAGE ON SCHEMA public TO pgvouch_ro;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO pgvouch_ro;
-- Optional, for `preflight`: see other sessions' states and transaction ages.
-- It also lets the role read their query text; PgVouch never returns or stores it.
GRANT pg_read_all_stats TO pgvouch_ro;
3. 在 .env 中将 PgVouch 指向这些数据库(切勿提交此文件):
SOURCE_DATABASE_URL=postgres://pgvouch_ro:PASSWORD@staging-host:5432/your_db
TARGET_DATABASE_URL=postgres://pgvouch_ro:PASSWORD@prod-host:5432/your_db
PGVOUCH_LLM=none
然后,在该文件夹中运行 pgvouch doctor(克隆仓库时也可运行 npm run cli -- doctor),必须显示 read_only=true 和 write privileges: none,之后才能运行其他命令。对于超大型表,请在只读副本上运行 verify。
4. 在 CI 中设置迁移门禁。 使用 --offline 时无需数据库:
npx pgvouch locks migrations/0042_add_index.sql --offline --fail-on high
或者使用 GitHub Action 审查每个迁移拉取请求。 它会分析 PR 新增或修改的迁移文件(离线运行:无需数据库、无需密钥),将锁分析和建议的安全改写写入作业摘要,并发布一条 PR 评论,后续推送时会更新该评论:
# .github/workflows/migration-review.yml
name: Migration review
on:
pull_request: # never pull_request_target: PR code must not get a write token
paths: ["migrations/**/*.sql"]
permissions:
contents: read
pull-requests: write # only for the comment; fork PRs get read-only and just the summary
jobs:
review:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: Shubh-Sgr/[email protected] # or pin the release's commit SHA
with:
paths: migrations/**/*.sql # one glob per line
fail-on: high # optional: fail the check at this risk
或者在本地运行相同的报告:pgvouch review --format markdown migrations/*.sql。PR 中的不可信文本(文件名、标识符)会经过转义,SQL 会放入比其中最长反引号序列更长的代码围栏中,评论长度也会限制在 GitHub 的大小限制以内。此仓库会针对涉及 examples/**/*.sql 的 PR 运行该 Action(migration-review.yml)。
5. 或者使用 Docker 运行(无需安装 Node):使用 docker build -t pgvouch . 构建一次,然后运行 docker run -i --rm --env-file .env pgvouch diff。可以用任何 CLI 命令替换 diff;不指定命令时会启动 MCP 服务。shadow 需要 Docker 守护进程,因此请改为从 CLI 运行。
安全模型
PgVouch 可以访问的内容。 这对安全审查以及阅读供应链扫描器报告(例如 Socket 的“网络访问”或“Shell 访问”)很有用:
| 内容 | 时机 | |
|---|---|---|
| 网络 | 你配置的源数据库和目标数据库 | 每条需要数据库的命令 |
Ollama(OLLAMA_URL,默认在本地运行)或 Gemini API | 仅在设置 PGVOUCH_LLM=ollama 或 gemini 时(默认关闭) | |
127.0.0.1 上的临时 Postgres 容器 | shadow 和 LLM 计划(必须通过 shadow 运行) | |
| 进程 | 使用 execFile 启动的 docker(直接传递参数,不使用 shell) | 同上。不会执行其他任何内容 |
| 文件 | 读取 ./.env 和你指定的迁移文件或回执文件;只写入你指定的 --out / receipt 文件 | 仅 CLI。MCP 服务不会读写任何文件 |
PgVouch 及其依赖项均没有遥测,也没有安装脚本(preinstall / postinstall)。从 0.4.1 版起,发布版本由 GitHub Actions 构建,并带有 npm provenance,因此 npm 上的每个版本都关联到构建它的提交和工作流运行记录。CI 只能暂存版本:须由维护者通过 2FA 批准后才会正式发布。扫描器还会报告官方 MCP SDK 依赖项中的 eval 和网络使用情况(ajv 会将 JSON schema 编译为函数;express 和 hono 会提供其 HTTP 传输服务,而 PgVouch 不使用该传输:它通过 stdio 通信)。
- 三层只读保护:
pgvouch_ro角色只有SELECT权限,该角色默认使用default_transaction_read_only,并且每个连接都会在其启动数据包中设置default_transaction_read_only=on、statement_timeout和lock_timeout。即使你提供的是超级用户 URL,这些保护仍然有效;有一项集成测试专门验证了这一点。 - 绝不将表加载到内存中: 哈希在 Postgres 内部计算,网络中只传输 32 个字符的摘要。二分查找每个叶节点每侧最多获取 50 行。
- 所有值都使用参数化查询。 标识符只从目录中获取,并会加引号。
- 连接字符串仅来自环境变量,不会来自工具输入。
- 不会向 LLM 发送行数据。 提示词只包含漂移报告和表结构。这可以防止通过数据库内容进行提示词注入;使用 Ollama 时,你的数据也不会离开本机。
- Shadow 运行只会写入 PgVouch 在
127.0.0.1上创建的容器,该容器使用随机密码,并在之后删除(包括按下 Ctrl-C 时)。计划 SQL 被视为不可信内容:它以拥有已复制 schema 的非超级用户角色运行,因此pg_read_file()或COPY ... PROGRAM会因“permission denied”而失败。每条语句都有计划无法取消的客户端超时;容器资源上限为 256 MB、1 CPU 和 256 个进程。 - LLM 计划会经过验证,而非盲目信任: 只有验证器和 shadow 运行都通过后,才会接受计划(
PGVOUCH_SHADOW_VERIFY=off会跳过 shadow 运行,此时输出会警告该计划尚未证明完整)。如果无法运行 shadow(例如没有 Docker),则不会接受 LLM 计划。发回给 LLM 的错误消息来自仅含 schema 的数据库,因此不会包含行数据。
设计决策
- 安全关键型转换由规则负责,而非大模型。 对生产数据而言,“大多数时候”正确的重写远远不够。大模型负责排序和解释,也可以不使用。
- 以 Postgres 作为真实数据来源。 锁评测从
pg_locks读取真实锁,并从relfilenode读取真实重写,而不是与我自己编写的表进行比较。 - 哈希的哈希。 先对每行使用
md5,再对它们的拼接结果使用md5,这样无论表有多宽,聚合结果每行都限制在 32 字节。 - 根据真实键确定分块边界(
row_number() % chunk_size),因此稀疏键、UUID 和复合键都能均匀分块。首尾分块不设边界,因此仅存在于目标端的行也能被捕获。 - 纯差异函数。 它非常容易进行单元测试,并被 CLI、MCP 服务、规划器、影子运行和评测复用。
- 一个服务层(src/service.ts)同时供 CLI 和 MCP 使用,确保两者不会产生分歧。
- 影子运行发现了实际缺陷: 类型变更重写最初会删除列的
DEFAULT/NOT NULL(已在提交cc4f55b中修复),之后又会在交换后将列的外键和索引留在旧列上。这两个问题都已修复,并补充了回归测试和评测场景。
限制(如实列出)
- 仅支持 PostgreSQL。CI 会在 13、14、15、16 和 17 上运行集成测试和锁准确性评估;已发布的评估数据是在 16 上测得的。
- 漂移检测涵盖表、列、索引、约束、序列、视图、函数/存储过程、触发器、枚举类型、扩展和行级安全策略。暂不支持:授权和权限、列排序规则、注释、发布/订阅、外部服务器以及表存储选项。
- 某些对象漂移需要人工处理:安装或升级扩展(需要特权角色)、已更改的物化视图(需要重新创建并刷新)、含有额外或重新排序标签的枚举(Postgres 无法原地删除或重新排序标签),以及列已更改的视图(
CREATE OR REPLACE VIEW无法删除或更改列类型;影子运行会报告此情况)。 - 文本主键按字节进行比较(
COLLATE "C"),以便排序规则不同的服务器也能得出一致结果。此类键上的范围扫描无法使用主键索引,因此验证带文本键的超大表会比验证带整数或 uuid 键的表慢。 - 启用行级安全策略时,没有
BYPASSRLS权限的角色只能看到策略允许其查看的行;verify会在该表的备注中说明这一点。 plan和shadow适用于public架构。diff --schema和verify --schema可检查其他架构,但目前尚未规划其中的漂移。- 重命名会报告为删除+新增,并附带
possible_rename提示。PgVouch 绝不会自动重命名。 - 对没有主键的表,可以检测到不匹配,但无法定位具体行。
- 验证会比较两个快照,因此在活动目标上,仍在变动的行会有所不同。
verify --recheck N会重新检查这些差异(每隔--recheck-delay秒一次,默认为 5 秒),只保留始终无法匹配的差异。它只会重新检查首次检查发现的内容,因此新增写入不会导致表持续验证失败。每次检查时源端都发生变化的行仍可能被报告,并标记为“源仍在变化”。若要最终确认切换结果,短暂冻结写入仍是最可靠的证明。 - 逻辑复制不会复制序列值,因此在逻辑副本上,
verify会报告序列值落后,直到你在切换时设置这些值。这是正确的:人们经常会忘记这一步。 - 锁分析器了解 30 种语句形态。其他任何语句都会被标记为“规则表中不存在”,绝不会被默默判定为安全。
- 自动分批回填需要单个整数主键;否则回填步骤会变为手动模板。
- 影子运行只会复制架构,因此它能证明最终结构正确,但不能证明生产负载下的耗时(这由锁分析器覆盖)。
- 演示数据库仅监听
127.0.0.1。在 Linux 上,影子容器通过 Docker 网桥而非回环接口访问主机,因此要对演示数据库运行影子运行,还需在网桥上发布这些数据库:npm run db:down && DB_BIND=172.17.0.1 npm run db:up(CI 在其临时运行器上使用0.0.0.0)。macOS 和 Windows(Docker Desktop)则无需额外设置。 - 类型更改的扩展/收缩仍需人工操作:部署双写并切换读取。列上的索引、外键、CHECK 和 UNIQUE 约束会自动复制到新列。主键、排除约束以及引用该列的其他表中的外键仍需手动处理。
- 评估场景由我编写,涵盖已知情况,包括一些棘手情形(易变默认值、复合键、部分索引、会话设置陷阱);所有场景都公开在 evals/scenarios 中。
- Gemini 适配器已实现,但未在评估中测试(未使用 API 密钥)。
路线图
- 支持 MySQL。
- 感知复制状态的验证:在已知 LSN 上比较两端。
- 除 SHA-256 完整性哈希外,添加签名回执(Ed25519)。
- 在免费套餐上托管演示(影子运行使用 Neon 分支)。
开发
npm test # 198 unit tests, no database needed
npm run test:integration # needs `npm run db:up` and Docker (shadow runs)
npm run eval # all evals → evals/results*.md
npm run eval -- --only scenarios --llm llama3.2 # include LLM plans (needs Ollama)
src/
introspect/ F1 diff/ F2 verify/ F3 + F4 locks/ F5 rewrite/ F6
plan/ F7 llm/ Ollama + Gemini mcp/ F8 shadow/ F10
reversibility/ F11 receipt/ F12 cli/ service.ts (shared by CLI + MCP)
evals/ F9 scenarios/, locks/, lib/, results*.md
docker/ docker-compose.yml + deterministic seed + read-only role
贡献
PgVouch 是开源项目,欢迎贡献:报告 Bug、新增锁定规则、新增评测场景和完善文档。请参阅 CONTRIBUTING.md 了解环境配置和基本准则,并参阅 SECURITY.md 私下报告安全漏洞。本项目遵循行为准则。
许可证
Apache License 2.0 © 2026 Shubham Sagar。可免费使用、修改和分发,包括用于商业用途;分发任何副本时,请保留 LICENSE 和 NOTICE 文件。该许可证还授予每位贡献者的专利许可。
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。