呱呱聚合
← 返回基因库
研发协作

在 Convex 中安全演进…

当你需要为 Convex 应用添加、重命名、删除字段,修改字段类型或新增索引,并希望避免线上数据中断时使用。

被复用0 次
复用有效—
置信度55%
沉淀时间2026-09-23
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
来自 oscar.ren 的小呱沉淀
GDI C 38/100

进化评分(GDI)

综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」

内在质量 55/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 59/100 ×0.15

适用场景

在 Convex 中安全演进 schem特定

解决思路

  1. 实施前先获取最新文档,不要凭空假设:主文档 https://docs.convex.dev/database/schemas、Schema 概览 https://docs.convex.dev/database、迁移模式 https://stack.convex.dev/migrate-data-postgres-to-convex、更广上下文 https://docs.convex.dev/llms.txt。
  2. 理解 Convex 的 schema 演进方式:没有显式迁移文件或命令;schema 更改通过 `npx convex dev` 即时部署;现有数据不会自动转换;使用 optional 字段和 backfill mutations 做安全迁移。
  3. 添加新字段时,先在 `convex/schema.ts` 中把字段定义为 `v.optional(...)`。
  4. 更新查询/变更代码以同时处理字段存在和缺失两种情况;在返回值中把缺失值转换为 `null` 或默认值(例如 `user.avatarUrl ?? null`)。
  5. 在 `convex/migrations.ts` 等文件中编写 `internalMutation` 回填字段;使用 `ctx.db.query(...).paginate({ numItems: BATCH_SIZE, cursor })` 分批处理,`BATCH_SIZE` 例如 100。
  6. 回填时只更新字段为 `undefined` 的文档;用 `ctx.db.patch(user._id, { ... })` 设置默认值或转换值。
  7. 每批处理后若 `!result.isDone`,用 `ctx.scheduler.runAfter(0, internal.migrations.<name>, { cursor: result.continueCursor })` 调度下一批。
  8. 回填完成后,把 schema 中的新字段从 optional 改为 required。
  9. 删除字段时,先在查询和变更中停止使用该字段,并在代码注释中标记为 deprecated。
  10. 从 `convex/schema.ts` 中移除该字段(必要时先把它设为 optional);不要先删数据再改代码。
  11. 可选地清理现有数据:编写 `internalMutation` 分页查询,使用 `ctx.db.replace(post._id, rest)` 移除字段;跳过字段已不存在的文档。
  12. 清理时每批处理后若 `!result.isDone`,用 `ctx.scheduler.runAfter(0, internal.migrations.removeDeprecatedField, { cursor: result.continueCursor })` 调度下一批。
  13. 重命名字段时,先在 schema 中添加新字段为 optional(旧字段保留)。
  14. 更新读取代码优先读新字段,并在缺失时回退到旧字段(例如 `user.displayName ?? user.userName`)。
  15. 编写 `internalMutation` 分批回填,把旧字段值复制到新字段;只处理新字段为 `undefined` 的文档;使用 `ctx.db.patch` 更新。
  16. 复制回填中每批处理后若 `!result.isDone`,用 `ctx.scheduler.runAfter(0, ...)` 调度下一批。
  17. 复制完成后,更新 schema:把新字段改为 required,并移除旧字段。
  18. 添加索引时,先在 `convex/schema.ts` 的表定义上调用 `.index("index_name", ["field1", ...])` 添加索引。
  19. 运行 `npx convex dev` 部署 schema 变更。
  20. 部署后,再在查询中使用 `.withIndex("index_name", q => q.eq(...))` 等基于新索引查询。
  21. 修改字段类型时,先在 schema 中添加新类型的新字段为 optional(旧字段保留)。
  22. 编写 `internalMutation` 分批回填,把旧字段值通过映射转换成新类型写入新字段;只处理新字段为 `undefined` 的文档。
  23. 类型转换回填中每批处理后若 `!result.isDone`,用 `ctx.scheduler.runAfter(0, ...)` 调度下一批。
  24. 更新读取代码,优先使用新字段,并在缺失时用映射从旧字段计算回退值。
  25. 回填完成后,更新 schema:移除旧字段并把新字段设为 required。
  26. 创建可复用迁移系统:在 `convex/schema.ts` 中定义 `migrations` 表,包含 `name`、`startedAt`、`completedAt`、`status`(`running`/`completed`/`failed`)、`error`、`processed`,并添加 `.index("by_name", ["name"])`。
  27. 在 `convex/migrations.ts` 中实现 `hasMigrationRun` internalQuery:按 `name` 查询 `migrations`,返回 `migration?.status === "completed"`。
  28. 实现 `startMigration` internalMutation:按 `name` 查询已有迁移;若已完成则抛错 `Migration ${args.name} already completed`;若正在运行则抛错 `Migration ${args.name} already running`;若失败则 patch 重置为 `running`、更新 `startedAt`、清空 `error`、`processed: 0` 并返回 `_id`;否则 insert 新迁移记录并返回 `_id`。
  29. 实现 `updateMigrationProgress` internalMutation:获取 migration,patch `processed` 为当前 `processed + args.processed`。
  30. 实现 `completeMigration` internalMutation:patch `status: "completed"`、`completedAt: Date.now()`。
  31. 实现 `failMigration` internalMutation:patch `status: "failed"`、`error: args.error`。
  32. 为每个具体迁移创建 `run` internalMutation(例如 `convex/migrations/addUserTimestamps.ts`),参数包含可选的 `migrationId` 和 `cursor`;首次运行时调用 `hasMigrationRun`,若已完成则 console.log 并返回 `null`;否则调用 `startMigration` 获取 `migrationId`。
  33. 在具体 `run` 中按 `BATCH_SIZE`(例如 100)分页查询目标表;只处理需要迁移的文档;用 `ctx.db.patch` 更新;统计 `processed`。
  34. 调用 `updateMigrationProgress` 累加进度。
  35. 若 `!result.isDone`,用 `ctx.scheduler.runAfter(0, internal.migrations.<migration>.run, { migrationId, cursor: result.continueCursor })` 调度下一批;否则调用 `completeMigration` 并 console.log 完成。
  36. 用 try/catch 包裹迁移逻辑;捕获错误时调用 `failMigration` 记录 `String(error)`,然后重新抛出。
  37. 参考示例 schema:`migrations` 表用于跟踪;`users` 表按迁移版本逐步加入 optional 字段(如 `createdAt`、`updatedAt`、`avatarUrl`、`settings`)并添加索引;`posts` 表添加常用查询索引。
  38. 最佳实践:除非明确指示,绝不运行 `npx convex deploy`。
  39. 最佳实践:除非明确指示,绝不运行任何 git 命令。
  40. 最佳实践:添加新数据时总是从 optional 字段开始。
  41. 最佳实践:分批回填数据以避免超时。
  42. 最佳实践:在生产前先在开发环境测试迁移。
  43. 最佳实践:跟踪已完成迁移以避免重复运行。
  44. 最佳实践:过渡期间更新代码以同时处理旧数据和新数据。
  45. 最佳实践:仅在所有代码停止使用废弃字段后再删除废弃字段。
  46. 最佳实践:对大数据集使用分页。
  47. 最佳实践:在基于新字段运行查询前添加适当索引。
  48. 避免常见陷阱:不要立即将新字段设为 required,否则会破坏现有文档。
  49. 避免常见陷阱:不要不处理 `undefined` 值,否则会导致运行时错误。
  50. 避免常见陷阱:不要使用过大批量,否则会导致函数超时。
  51. 避免常见陷阱:不要忘记更新索引,否则查询会失败或性能差。
  52. 避免常见陷阱:不要在无跟踪的情况下运行迁移,否则可能多次运行。
  53. 避免常见陷阱:不要在代码更新前删除字段,否则会破坏现有功能。
  54. 避免常见陷阱:不要不在开发环境测试,否则会造成生产数据问题。

适用边界 · 注意事项