convex-migration-helper について
Convex のスキーマ変更と既存データ移行を安全に進めるスキルで、破壊的変更を扱う開発者向けです。スキーマの拡張、データのバッチ移行、スキーマの縮小という流れを示し、ドライランと検証も案内します。
主な機能
- 01複数回デプロイする移行手順
- 02オンライン移行のガイド
- 03@convex-dev/migrations によるバッチ処理
- 04ドライランと進捗監視
- 05一般的なスキーマ変更パターン
活用シーン
- 01既存ドキュメントへの必須フィールド追加
- 02フィールド型やテーブル構造の変更
- 03既存データの分割や統合
- 04フィールドの改名や非推奨化
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ get-convex/ agent-skills --skill convex-migration-helperインストール後、Claude Code で/convex-migration-helperを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
agents/openai.yamlassets/icon.svgreferences/migration-patterns.mdmigrations-component.md詳細
Convex 移行ヘルパー
破壊的変更を行う際に、Convex のスキーマとデータを安全に移行します。
使用する場面
- 既存のテーブルに新しい必須フィールドを追加するとき
- フィールドの型や構造を変更するとき
- テーブルを分割または統合するとき
- フィールドの名前を変更または削除するとき
- ネストされたデータからリレーショナルデータに移行するとき
使用しない場面
- 本番環境または開発環境に既存データがない新規スキーマの場合
- バックフィルが不要なオプションフィールドを追加するとき
- 移行対象の既存データがない新しいテーブルを追加するとき
- 正確性に懸念のないインデックスを追加または削除するとき
- 移行の必要がない Convex のスキーマ設計に関する質問の場合
重要な概念
スキーマ検証がワークフローを決定します
Convex では、保存済みデータと一致しないスキーマをデプロイできません。 これがすべての移行を左右する基本的な制約です。
- 既存のドキュメントにフィールドがない場合、必須フィールドを追加できません
- 既存のドキュメントに古い型のフィールドがある場合、フィールドの型を変更できません
- 既存のドキュメントにフィールドが残っている場合、スキーマからそのフィールドを削除できません
つまり、移行は予測可能なパターンに従います。スキーマを拡張し、データを移行してから、スキーマを縮小します。
オンライン移行
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"]);
破壊的変更:デプロイワークフロー
破壊的な移行はすべて、同じ複数回のデプロイパターンに従います。
デプロイ 1 — スキーマを拡張する:
- 古い形式と新しい形式の両方を許可するようにスキーマを更新します(例:新しいオプションフィールドを追加)
- 読み取り時に両方の形式を処理できるよう、コードを更新します
- 新しいドキュメントには新しい形式を書き込むよう、コードを更新します
- デプロイします
デプロイの間 — データを移行する:
- 既存のドキュメントをバックフィルする移行を実行します
- すべてのドキュメントが移行されたことを確認します
デプロイ 2 — スキーマを縮小する:
- 新しい形式のみを必須にするよう、スキーマを更新します
- 古い形式を処理するコードを削除します
- デプロイします
Migrations コンポーネントの使用
複雑な移行には、@convex-dev/migrations コンポーネントを使用してください。バッチ処理、カーソルベースのページネーション、状態追跡、失敗からの再開、ドライラン、進捗の監視に対応しています。
インストール、セットアップ、npx convex run migrations:myMigration による移行の直接定義と実行、ドライラン、ステータスの監視、設定オプションについては、references/migrations-component.md を参照してください。
一般的な移行パターン
コード例を含む完全なパターンについては、references/migration-patterns.md を参照してください。以下を取り上げています。
- 必須フィールドの追加
- フィールドの削除
- フィールドの型の変更
- ネストされたデータの別テーブルへの分割
- 孤立したドキュメントのクリーンアップ
- ゼロダウンタイム戦略(デュアル書き込み、デュアル読み取り)
- 小規模なテーブル向けの簡易策(コンポーネントを使わない単一の internalMutation)
- 移行が完了したことの確認
よくある落とし穴
- データ移行の前にフィールドを必須にすること:既存のドキュメントにフィールドがないため、Convex はデプロイを拒否します。まず必ずスキーマを緩和してください。
- 大きなテーブルで
.collect()を使うこと:トランザクションの上限に達したり、タイムアウトが発生したりします。適切なバッチ処理付きページネーションには、migrations コンポーネントを使ってください。.collect()を使っても安全なのは、小さいとわかっているテーブルだけです。 - 移行前に新しい形式で書き込まないこと:移行期間中に作成されたドキュメントが見落とされ、移行が「完了」した後も移行されていないデータが残ります。
- ドライランを省略すること:本番データに変更を適用する前に、
dryRun: trueを使って移行ロジックを検証してください。実際のドキュメントに影響する前にバグを検出できます。 - フィールドを早まって削除すること:コメントを付けて
v.optionalで非推奨にする方法をおすすめします。データが不要になり、そのフィールドを参照するコードがないと確信できてから削除してください。 - 移行バッチに crons を使うこと:migrations コンポーネントは、再帰的なスケジューリングによって内部でバッチ処理を行います。crons を使う場合は手動でのクリーンアップが必要で、削除するために追加のデプロイも必要になります。
移行チェックリスト
- 破壊的変更を特定し、複数回に分けたデプロイのワークフローを計画する
- 古い形式と新しい形式の両方を許可するようにスキーマを更新する
- 読み取り時に両方の形式を処理できるようコードを更新する
- 新しいドキュメントには新しい形式で書き込むようコードを更新する
- 拡張したスキーマと更新したコードをデプロイする
-
@convex-dev/migrationsコンポーネントを使って移行を定義する -
npx convex run migrations:myMigration '{"dryRun": true}'を使ってテストする -
npx convex run migrations:myMigrationで移行を直接実行し、 状態を監視する - すべてのドキュメントが移行されたことを確認する
- 新しい形式のみを必須にするようスキーマを更新する
- 古い形式を処理するコードをクリーンアップする
- 最終的なスキーマとコードをデプロイする
- 安定性を確認したら移行コードを削除する