用途
django-safe-migration 是一个用于编写、审查和重写 Django 迁移的技能,专门面向 PostgreSQL,目标是在滚动部署(rolling deploy)等场景下保证零停机。当用户提到迁移、Django schema 变更,或询问“这个迁移安全吗”“帮我写一个迁移”“把迁移重写成安全的”等问题时,该技能会被触发。
在滚动部署中,新数据库 schema 先应用,然后应用实例逐个重启。部署期间旧代码和新代码会同时针对同一数据库运行,因此每个迁移都必须能在旧版本应用仍在服务流量时安全执行。对大表加 ACCESS EXCLUSIVE 锁会阻塞所有读写,即使只有几秒也会造成停机;更严重的是等待队列:一个 50ms 的 ALTER TABLE 会排在长事务之后,后续所有查询又排在迁移之后,在繁忙表上可能级联导致连接池耗尽。
主要能力
技能提供三种工作模式:
- 审查(Review):读取迁移文件全文,运行迁移命令获取 Django 实际执行的 SQL(以生成的 SQL 为准),加载操作指南和 PostgreSQL 锁参考,逐条检查 SQL 语句,输出结构化报告,区分 Errors(会导致停机或被运行时守卫阻止)、Warnings(可能造成长时间锁或部署问题)、Structural Issues(结构问题)和 Safe(安全项)。
- 编写(Write):根据描述生成正确的零停机迁移。会先澄清操作细节(模型与字段、新增列还是修改现有列、外键引用哪张表、类型变更的前后类型、Django 版本等),判断需要几个迁移文件,并按示例模式生成文件。
- 重写(Rewrite):将已有的不安全迁移转换为一个或多个安全迁移,保留迁移编号前缀、依赖关系以及原本安全的操作不变,并在代码后输出“本次重写改变了什么”的说明。
关键模式与规则
- NOT VALID + VALIDATE:用于外键和 CHECK 约束的两步技术。ADD CONSTRAINT … NOT VALID 立即对新写入生效但跳过扫描现有行,锁窗口极短;VALIDATE CONSTRAINT 扫描现有行,使用 SHARE UPDATE EXCLUSIVE,不阻塞读写。危险的是全表扫描而非约束创建本身。
- lock_timeout:任何需要 ACCESS EXCLUSIVE 的操作前应加 SET LOCAL lock_timeout,使迁移快速失败而不是无限等待锁。默认 2 秒,需与用户确认。SET LOCAL 作用域限于当前事务。
- atomic = False:当操作使用 CONCURRENTLY 或 VALIDATE CONSTRAINT 时,Migration 类必须设置 atomic = False。在 atomic = False 的迁移中,必须把 SET LOCAL 和 DDL 放在同一个 RunSQL 调用中,否则 SET LOCAL 会在下个操作前重置而失效。
- SeparateDatabaseAndState 拆分:RemoveField / DeleteModel 始终拆成两个独立文件。
- RunPython:至少提供 reverse_code=migrations.RunPython.noop,且必须用 apps.get_model("app", "ModelName") 访问模型,不能直接导入。
- RunSQL:始终提供 reverse_sql;确实不可逆时使用 migrations.RunSQL.noop 并说明原因。
检测清单要点
Errors(无论是否有运行时守卫都不安全):AddField 使用 null=False 且无 db_default;RemoveField / DeleteModel 未放在 SeparateDatabaseAndState 中;AddIndex 未用 AddIndexConcurrently;RemoveIndex 未用 RemoveIndexConcurrently;AddConstraint(FK 或 CHECK)未用 NOT VALID + VALIDATE;AlterField 移除 null=True 且无 CHECK 路径;AlterField(FK 字段)的 sqlmigrate 输出包含 DROP CONSTRAINT 后跟 ADD CONSTRAINT FOREIGN KEY;AddIndexConcurrently / RemoveIndexConcurrently 但 Migration 类未设 atomic=False;RunPython 直接导入模型。
若项目配置了运行时守卫(如 zero_downtime_migrations、django-pg-zero-downtime-migrations、django-migration-linter),RenameField、RenameModel、AlterField 列类型变更、AddConstraint(ExclusionConstraint)也应标记为 Errors;若未配置守卫,这些同样需要安全重写。
Warnings:AddConstraint(UNIQUE)未用索引优先模式;AlterField 给现有列添加 null=True(DROP NOT NULL);RunPython 缺少 reverse_code;RunSQL 缺少 reverse_sql。
使用方式
技能通过项目指令文件(如 AGENTS.md 或 CLAUDE.md)中的配置块读取项目参数,包括 Django 版本、部署策略、运行时守卫、迁移命令和文档 URL。默认迁移命令为 python manage.py sqlmigrate,默认部署策略为 rolling deploy,默认无运行时守卫。审查时始终运行 sqlmigrate 获取真实 SQL,因为仅看 ORM 操作类不够——例如对 FK 字段的 AlterField 添加 null=True 会同时删除并重新添加外键约束。
适用场景与限制
适用于使用 Django 与 PostgreSQL、需要在不中断线上流量的前提下变更数据库结构的团队,尤其是采用滚动部署的项目。技能依赖项目配置的准确性;若未提供 Django 版本、部署策略等信息,将使用默认值。部分操作(如 ACCESS EXCLUSIVE)在安全版本中可能仍然需要,重写主要改善的是锁持续时间(全表扫描变为毫秒级元数据操作)、失败模式(静默排队变为快速超时错误)或两者兼有,并不会完全消除锁。NOT VALID 会引入数据完整性窗口,现有行在第二个迁移运行前未经验证。