用途
Convex Migration Helper 用于在对 Convex 进行破坏性变更时,安全地迁移 schema 与数据。它关注的不是如何写业务代码,而是如何把已经存在于生产或开发环境中的数据,平滑地迁移到新的结构上。
何时使用
- 给已有表添加新的必填字段
- 修改字段类型或结构
- 拆分或合并表
- 重命名字段或删除字段
- 从嵌套数据迁移为关系型数据
何时不应使用
- 全新 schema,生产或开发环境没有现存数据
- 添加无需回填的可选字段
- 添加没有存量数据需要迁移的新表
- 增删索引且不涉及正确性问题
- 只是咨询 Convex schema 设计,没有迁移需求
核心概念
Schema 校验驱动整个工作流
Convex 不允许部署与静态数据不匹配的 schema,这是塑造每一次迁移的根本约束:
- 如果现存文档没有某个字段,就不能把它设为必填
- 如果现存文档是旧类型,就不能修改该字段的类型
- 如果现存文档仍有某字段,就不能把它从 schema 中移除
因此迁移遵循可预测的模式:加宽 schema、迁移数据、收窄 schema。
在线迁移
Convex 迁移是「在线」进行的,应用在数据被分批异步更新的同时仍然持续对外服务。在迁移窗口期内,代码必须同时处理新旧两种数据格式。
优先新增字段而非修改类型
改变数据形态时,应创建新字段,而不是修改已有字段,这样过渡更安全、更容易回滚。
不要删除数据
除非确有把握,优先弃用字段而不是删除。把字段标记为 v.optional,并加代码注释说明它已弃用以及当初存在的原因。
安全变更(无需迁移)
添加可选字段:
// Before users: defineTable({ name: v.string(), });
// After - safe, new field is optional users: defineTable({ name: v.string(), bio: v.optional(v.string()), });
添加新表:
posts: defineTable({ userId: v.id("users"), title: v.string(), }).index("by_user", ["userId"]);
添加索引:
users: defineTable({ name: v.string(), email: v.string(), }).index("by_email", ["email"]);
破坏性变更:部署工作流
每一次破坏性迁移都遵循相同的多阶段部署模式:
Deploy 1 - 加宽 schema:
- 更新 schema,允许新旧两种格式(例如添加可选的新字段)
- 更新代码,读取时兼容两种格式
- 更新代码,新文档按新格式写入
- 部署
两次部署之间 - 迁移数据:
- 运行迁移回填现有文档
- 验证所有文档都已迁移
Deploy 2 - 收窄 schema:
- 更新 schema,只要求新格式
- 移除处理旧格式的代码
- 部署
使用 migrations 组件
对于任何非平凡的迁移,使用 @convex-dev/migrations 组件。它负责批处理、基于游标的分页、状态跟踪、失败续跑、dry run 以及进度监控。
关于安装、配置、用 npx convex run migrations:myMigration 定义并直接运行迁移、dry run、状态监控和配置项,参见 references/migrations-component.md。
常见迁移模式
覆盖以下带代码示例的完整模式,参见 references/migration-patterns.md:
- 添加必填字段
- 删除字段
- 修改字段类型
- 把嵌套数据拆分到独立表
- 清理孤立文档
- 零停机策略(双写、双读)
- 小表捷径(不使用组件、单个 internalMutation)
- 验证迁移已完成
常见陷阱
- 在迁移数据前就把字段设为必填:Convex 会因为现存文档缺少该字段而拒绝部署。务必先加宽 schema。
- 在大表上使用 .collect():会触及事务限制或造成超时。应使用 migrations 组件做正确的分批分页。.collect() 只对确定很小的表安全。
- 迁移前未写入新格式:迁移窗口期内创建的文档会被漏掉,导致迁移「完成」后仍有未迁移数据。
- 跳过 dry run:用 dryRun: true 在把改动提交到生产数据前验证迁移逻辑,能在影响真实文档之前发现 bug。
- 过早删除字段:优先用 v.optional 加注释的方式弃用。只有在确信数据不再需要且没有任何代码引用后才删除。
- 用 cron 做迁移批次:migrations 组件内部通过递归调度处理批处理。cron 需要手动清理,还要额外部署一次才能移除。
迁移清单
- 识别破坏性变更并规划多阶段部署工作流
- 更新 schema,允许新旧两种格式
- 更新代码,读取时兼容两种格式
- 更新代码,新文档按新格式写入
- 部署加宽后的 schema 与更新后的代码
- 使用 @convex-dev/migrations 组件定义迁移
- 用 npx convex run migrations:myMigration '{"dryRun": true}' 测试
- 用 npx convex run migrations:myMigration 直接运行迁移并监控状态
- 验证所有文档都已迁移
- 更新 schema,只要求新格式
- 清理处理旧格式的代码
- 部署最终的 schema 与代码
- 确认稳定后移除迁移代码