弱工单
给 accounts 加 account_status。 需要 active、paused 和 closed 状态。 更新 API,确保报表还能用。
这个案例把“加 account_status 字段”这种模糊需求,改写成包含回填证据、双读行为、回滚边界和 AI 编码范围的分阶段迁移规格。
给 accounts 加 account_status。 需要 active、paused 和 closed 状态。 更新 API,确保报表还能用。
Feature: Account status schema migration Owner: Platform Data Status: Ready for review Context: - accounts 有 1800 万行。 - status 当前由 closed_at 和 billing_pause_until 推导。 - reporting 通过 nightly export 读取 accounts。 Goal: - 添加 account_status,且不阻塞账号写入。 - 从现有字段回填 status。 - parity check 通过后再切换读取。 Non-goals: - 不重设计 billing state。 - 本次不改 report schema。 - 暂不删除 closed_at 或 billing_pause_until。
添加 nullable account_status 和必要索引,不改变应用读取逻辑。
分批回填 status,并记录行数、mismatch 数量和运行时间。
只有 staging 和生产抽样 parity 通过后,才从 account_status 读取。
等 reporting 和回滚窗口结束后,再单独移除旧推导逻辑。
Status rules: - closed_at not null -> closed - billing_pause_until future -> paused - otherwise -> active Compatibility: - expand 和 backfill 期间现有读取继续使用旧推导。 - API response 只有在 switch flag 开启后才包含 account_status。 - 本次 release 中报表继续使用 nightly export 字段。
- [ ] 添加 nullable account_status 字段。 - [ ] 如 query plan 需要,添加非阻塞索引。 - [ ] 编写可重复运行的分批回填脚本。 - [ ] 添加对比新旧 status 的 parity check。 - [ ] 在 feature flag 后切换 API 读取。 - [ ] 记录 cleanup follow-up。
- Given account 有 closed_at When backfill 运行 Then account_status 是 closed。 - Given switch flag 关闭 When API 读取 account status Then 使用现有推导逻辑。 - Given parity mismatch > 0.1% When 发布评审运行 Then 阻止 switch。
Required: - migration dry-run output - batch size 和锁风险说明 - query plan before/after - parity check result - API flag test - switch 后的回滚边界
第一轮评审看迁移能否在不阻塞生产写入的情况下运行。代码生成器能添加字段和更新 model,但如果规格没有写表规模、锁行为、索引策略和批量大小,它并不知道这个 migration 是否适合线上执行。这些上下文必须先进入规格包,再生成迁移文件。
第二轮评审看新字段是否真的等价于旧行为。旧 status 可能是隐式的、混乱的、分散在多个字段里的。规格会把隐式规则变成可测试映射,再要求 parity check 通过后才切换读取。
第三轮评审看回滚边界。switch 之前,回滚通常是关闭新 reader,暂停或重跑 backfill。switch 之后,回滚就可能需要不同方案,因为下游消费者已经看到 account_status。规格包必须把这个边界写清楚。
拒绝 billing 重设计、report schema 变更和旧字段删除。这些应在 parity 和消费者评审后单独处理。
要求 backfill dry-run 输出、query plan、mismatch 数量,以及证明 feature flag 控制读取的测试。
停止信号是写延迟升高、锁等待、回填错误率,或 parity mismatch 超过规格里写明的阈值。
只实现 account_status 迁移规格包。 允许修改: - nullable column 的 migration 文件 - 分批回填脚本 - status mapping 测试 - parity check - API read flag - 回滚边界文档 不要做: - 删除 closed_at - 删除 billing_pause_until - 重设计 billing status - 修改 reporting schema - 新增 admin status editor - 重写和 status 读取无关的 account model
这条护栏防止一个常见 AI 编码问题:把 schema 需求理解成“顺便清理领域模型”。更安全的迁移会让旧字段继续存在,直到新字段证明 parity,且下游消费者有时间适配。
当新字段代表某个已经隐式存在的行为时,可以套用这个案例:账号状态、订阅等级、权限状态、用量分类、 onboarding 阶段或支付风险。模式是先 expand,再安全 backfill,对比新旧逻辑,通过 flag 切换读取,最后等回滚风险降低后再 cleanup。
复制规格包前,要替换行数、锁风险说明、状态映射和消费者列表。行数重要,因为 5 万行安全的迁移不一定适合 1800 万行。消费者列表也重要,因为 reporting、export、search index 和客服工具经常用不同方式读取数据。
使用 AI 时,不要在规格写清允许文件和停止信号前就要求它“写迁移”。可以让 AI 分别生成 migration 和测试。如果它在 switch flag 存在前就改旧推导逻辑,应该退回规格,而不是接受 diff。
非空字段和立即写入可能锁表或破坏旧路径。除非表很小且隔离,否则先 nullable expand。
“看起来对”不够。规格必须写明阻止 switch 的 mismatch 阈值。
下游消费者稳定前就删除旧字段,会让回滚更贵,也让事故响应变慢。
数据库迁移的风险通常不在“字段能不能加上”,而在“它上线后是否还能安全暂停”。提交发布前,评审者应该确认三件事:第一,migration 是否能重复运行或安全中断;第二,backfill 失败后是否能从最后一批继续;第三,switch flag 关闭后,API、报表和后台任务是否都会回到旧读取路径。
如果团队无法回答这些问题,说明规格还没准备好交给 AI 或人工实现。此时继续写代码只会把不确定性推到发布窗口里。更好的做法是先补 row count、batch size、query plan、消费者列表和停止信号,再拆任务。
Schema migration 合并后仍需要继续产出证据。最稳的团队会把首个发布窗口写进规格,而不是把监控当成临时提醒。
这个案例基于数据库迁移常见失败模式:锁风险、缺少回填数量、过早切换读取,以及 cleanup 太早导致回滚困难。