「SSoT 漂移」误报:先确认谁是运行时真正的数据源
某天,一个后台管理系统的业务角色权限出了问题——某个受限角色仍然能看到本不该看到的全量数据。排查过程中,有人发现 scripts/data/roles.json 里的 data_scope 字段和数据库 roles 表里的值不一致。结论来得很快:「SSoT 漂移,JSON 和 DB 不同步,同步一下就好了。」
但这个结论完全是错的。折腾了一圈才发现,运行时代码根本不读那个 JSON。
现象
scripts/data/roles.json中某角色的data_scope值为旧的配置- MySQL
roles表中同一角色的值已经是修正后的新值 - 视觉上看,两份数据「漂移了」
直觉反应:同步这两份数据,问题应该就解决了。
根因
「数据重复出现在多个地方」≠「多处都是运行时数据源」。
这个项目里,roles.json 的真实生命周期是:
- 初始化脚本
scripts/init-db.ts在空库首次部署时读一次,把数据写入数据库 - 之后该文件再无人问津,数据库
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 = 0DB 是唯一的运行时读取点。roles.json 的「漂移」只是种子文件的文档腐化,跟线上 bug 毫无关系。
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/):下次构建自动覆盖,不用管 - 历史文档:文档腐化,单独整理
在喊「SSoT 漂移」之前,先跑一遍 grep -rn "<文件名/表名>" src/。如果 src/ 下没有命中,那份「漂移的副本」根本不参与运行时,不是本次 bug 的根因,不用同步,另立 issue 处理即可。
一句话外卖
数据重复出现在多处是正常的——种子、快照、构建产物都会留下副本;但运行时只读一处,找到那一处之前,不要贸然断言「SSoT 漂移」。