返回博客

「SSoT 漂移」误报:先确认谁是运行时真正的数据源

某天,一个后台管理系统的业务角色权限出了问题——某个受限角色仍然能看到本不该看到的全量数据。排查过程中,有人发现 scripts/data/roles.json 里的 data_scope 字段和数据库 roles 表里的值不一致。结论来得很快:「SSoT 漂移,JSON 和 DB 不同步,同步一下就好了。」

但这个结论完全是错的。折腾了一圈才发现,运行时代码根本不读那个 JSON。

现象

  • scripts/data/roles.json 中某角色的 data_scope 值为旧的配置
  • MySQL roles 表中同一角色的值已经是修正后的新值
  • 视觉上看,两份数据「漂移了」

直觉反应:同步这两份数据,问题应该就解决了。

根因

「数据重复出现在多个地方」≠「多处都是运行时数据源」。

这个项目里,roles.json 的真实生命周期是:

  1. 初始化脚本 scripts/init-db.ts 在空库首次部署时读一次,把数据写入数据库
  2. 之后该文件再无人问津,数据库 roles 表才是运行时 SSoT

用 grep 验证一遍:

grep -rn "roles\.json" ./src --include="*.ts" --include="*.tsx"
# 无命中
grep -rn "roles\.json" . --include="*.ts" --include="*.tsx"
# scripts/init-db.ts:214      — 一次性初始化
# scripts/migrate-phase1.ts:203 — 历次迁移脚本
# docs/archive/design.md     — 历史文档
# dist-electron/.../roles.json  — 构建产物

src/ 下零命中。API 路由里读的是:

SELECT data_scope FROM roles WHERE id IN (?) AND is_deleted = 0

DB 是唯一的运行时读取点。roles.json 的「漂移」只是种子文件的文档腐化,跟线上 bug 毫无关系。

坑:文件名叫 data/,不代表运行时读它

scripts/data/roles.json 听起来很权威——放在 data/ 目录,不是 tmp/ 也不是 archive/。但目录名只是命名约定,不是运行时保证。代码可能早在几个月前就切到 DB 读取,这个 JSON 只是当年的种子文件残留。

不要用文件名/路径做 SSoT 判定依据,要用 grep。

正解

发现「多份数据不一致」时,先做 4 步核查,再决定要不要同步:

第 1 步:grep 每一份副本的所有引用方

grep -rn "roles\.json" . --include="*.ts" --include="*.tsx"
grep -rn "roles\.json" . --include="*.js"

把每个命中分类:scripts/ → 一次性脚本;src/app/api/ → 运行时(真正危险);dist/ → 构建产物。

第 2 步:只有命中 src/ 运行时路径,才值得继续分析

init 脚本、迁移脚本、历史文档里的引用不参与请求生命周期,漂移了也不会产生线上 bug。

第 3 步:用 DB 查询确认运行时 SSoT 的实际数据

SHOW COLUMNS FROM roles;
SELECT code, data_scope FROM roles WHERE is_deleted = 0;

对比 API 路由里的查询逻辑,两边对得上,DB 就是 SSoT,结案。

第 4 步:把非 SSoT 副本按类型归档

  • 种子文件(scripts/seed/):漂移是下一次 init-db 的 housekeeping 问题,单独开 issue
  • 构建产物(dist/app-dist/):下次构建自动覆盖,不用管
  • 历史文档:文档腐化,单独整理
正解:grep 先行,确认运行时读取路径再下结论

在喊「SSoT 漂移」之前,先跑一遍 grep -rn "<文件名/表名>" src/。如果 src/ 下没有命中,那份「漂移的副本」根本不参与运行时,不是本次 bug 的根因,不用同步,另立 issue 处理即可。

一句话外卖

数据重复出现在多处是正常的——种子、快照、构建产物都会留下副本;但运行时只读一处,找到那一处之前,不要贸然断言「SSoT 漂移」。