Seed Data Fidelity Strategy — 基于 DB 依赖关系图分析的讨论结论 — 2026-02-08
在构建 DB 数据流依赖关系图 的过程中,我们发现当前的 perf-seed 种子数据生成脚本存在一个根本性问题:种子数据的生成路径和生产环境的数据流路径不一致。
当前 seed 使用 BatchInserter 通过 bulk INSERT + ON CONFLICT DO NOTHING 直接向数据库灌数据,完全绕过了:
employee_work_schedules_daily → 自动生成 employee_available_times)ON CONFLICT DO NOTHING 悄悄吞掉违规行)从 DB 依赖关系图 中,我们将表间关系分为四种类型。这四种类型有明确的健康度排序:
| 边类型 | 执行层级 | 强制性 | 健康度 | 说明 |
|---|---|---|---|---|
| FK Constraint | 数据库层 | 无法绕过 | 最健康 | 任何方式写 SQL 都会被强制检查,是最可靠的数据完整性保证 |
| DB Trigger | 数据库层 | 自动触发 | 健康 | 数据库层自动执行,应用代码无法绕过,但有隐蔽性(调试困难) |
| Business Logic | 应用层 | 仅限 service 调用 | 脆弱 | 只在 JavaScript service 代码里保证多表一致性。直接 SQL、其他脚本、seed 都可以绕过 |
| Seed Derivation | 脚本层 | 仅限 seed | 不适用 | 仅反映测试数据如何生成,与生产运行无关 |
FK 和 Trigger 是数据库层强制执行的,属于"健康"关系。Business Logic 是应用层约定的,属于"脆弱"关系 — 有 Green 边但没有 Blue 边兜底的表关系是最大风险点。Seed Derivation 的理想状态是消失 — 即 seed 不应该有自己独立的数据流路径,而应该复用生产的 Business Logic 路径。
种子数据的生成路径应该和生产环境的数据流保持一致。seed 脚本应复用 service 层的核心逻辑函数,而不是用 bulk INSERT 绕过所有校验。
即使只测读性能,绕过业务逻辑生成的数据也会导致测试结论不可靠:
employee_available_times 表为空或不完整 → 依赖它的可用时段查询行为完全不同ON CONFLICT DO NOTHING 悄悄丢弃了违反 FK 的行 → 实际插入行数可能远少于预期 → 表大小和基数都偏了不是所有表都需要同等保真度。按表的角色分三层:
| 层级 | 适用表 | 生成方式 | 原因 |
|---|---|---|---|
| 高保真 | appointments, sub_appointments, transactions, invoices, receipts, refunds, checkins | 复用 service 层函数 | 核心业务表,数据量最大,且是性能瓶颈集中区。写入顺序、约束校验、触发器触发都必须走完整路径 |
| 中保真 | employees, employee_work_schedules_daily, guests, customer_memberships, day_end_closeouts | 模拟 service 层写入顺序,确保 FK 和触发器正常工作 | 这些表被核心表依赖。必须确保数据完整性(如 daily schedules 必须触发 available_times 生成) |
| 低保真 | guest_tags, promotion_send_logs, application_logs, auth_audit_logs, points_transactions | bulk INSERT 可接受 | 末端数据,不触发下游逻辑。只需要行数和分布合理即可 |
关键:不需要调 HTTP API。直接在 seed 脚本中 require service 模块,跳过认证/权限中间件,但保留:
复用 service 层会比 bulk INSERT 慢。但可以通过以下方式缓解:
| 问题 | 影响 | 涉及的层 |
|---|---|---|
L4 直接 INSERT appointments,跳过时间冲突检测 |
同一技师同一时段可能有多个预约,数据分布不真实 | L4 |
L2 写入 employee_work_schedules 而非 employee_work_schedules_daily |
触发器 fn_generate_available_times 未触发,employee_available_times 可能为空 |
L2 |
ON CONFLICT DO NOTHING 吞掉 FK 违规 |
实际插入行数可能少于 expected,表基数偏小(如 .checkpoint.json 中 appointments inserted 11,110,000 > expected 10,950,000) | 全部 |
| L5 transactions 没走支付确认流程 | 缺少 transaction_history 的完整状态变更链(pending→confirmed→completed) |
L5 |
| L8 guest_tags 直接 INSERT,没走标签评估引擎 | 标签分布可能与真实评估结果不一致 | L8 |
预约数据永久保留,不做时间窗口删除。通过 PostgreSQL 表分区(按月)实现查询性能隔离 — 查询只扫描需要的分区,历史数据不影响热数据性能。
性能测试 seed 保持 2 年(730 天)数据量。目的不是减少数据让查询变快,而是用真实数据量暴露需要加索引、需要分区、需要归档的地方。
employee_available_times 只需生成未来 4 周(28 天),因为预约最多提前 4 周。历史预约不需要 available_times 支撑。
employee_work_schedules_daily 而非 employee_work_schedules,确保 employee_available_times 被正确生成