mcpField
G
generate-migration

generate-migration

github.com/getsentryデータ・データベース45.7k90 回インストール今日更新
リポジトリ

generate-migration について

Sentry のバックエンド開発者向けの Django マイグレーションガイドです。生成、レビュー、テストに加え、フィールドやモデルを安全に段階削除する手順を説明します。

主な機能

  1. 01通常・空のマイグレーション生成
  2. 02SQLと安全性の確認
  3. 03データ補完マイグレーションのテスト
  4. 04フィールドとモデルの段階的な削除
  5. 05マイグレーションロックファイルの競合解決

活用シーン

  1. 01モデル変更のマイグレーション作成
  2. 02データ変換マイグレーションのテスト
  3. 03データベースのフィールドやテーブルの安全な削除

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/getsentry/sentry --skill generate-migration

インストール後、Claude Code で/generate-migrationを入力して読み込まれたか確認します。

詳細

Django データベースマイグレーションの生成

コマンド

モデルの変更に基づいてマイグレーションを自動生成します:

sentry django makemigrations

特定のアプリの場合:

sentry django makemigrations <app_name>

空のマイグレーションを生成します (データマイグレーションやカスタム作業向け):

sentry django makemigrations <app_name> --empty

生成後

  1. 新しいモデルを追加した場合は、アプリの __init__.py でインポートされていることを確認してください
  2. 生成されたマイグレーションが正しいかレビューしてください
  3. sentry django sqlmigrate <app_name> <migration_name> を実行して SQL を検証してください
  4. sentry django migrate <app_name> でマイグレーションをローカルに適用してください — Sentry のマイグレーションフレームワークは適用時に安全性チェックを実行するため、CI より先に安全でない操作 (is_post_deployment の欠落、安全でないカラム変更など) を検出できます。

生成されたマイグレーションを編集する場合 (たとえば DeleteModel を SafeDeleteModel に置き換える場合)、自動生成された is_post_deployment コメントブロックはそのまま残してください。これは自明ではないフラグについて、将来のマイグレーション作成者向けの具体的な指針を示すものです — 有用なコンテキストであり、無駄なものではありません。コメントが古くなっているか、コードと矛盾している場合にのみ削除してください。

ORM をテストしない

Django の ORM だけを対象とするテストは書かないでください。標準的な操作 — 作成/更新/削除、カスケード削除、一意制約の適用 — は Django と Postgres によって提供されており、動作するものと見なされています。フレームワークではなく、あなたの ロジック (ビジネスルール、シグナルレシーバー、カスタムマネージャー/バリデーション) をテストしてください。

データマイグレーションとバックフィルはテストする

上記の例外: データをバックフィルまたは変換するマイグレーションはあなたのロジックであり、テストが必須です。sentry.testutils.cases の TestMigrations 基底クラスを使用してください; テストは tests/sentry/migrations/ に置きます。

app、migrate_from (あなたのマイグレーションの直前のマイグレーション)、migrate_to (あなたのマイグレーション) を設定してください。setup_before_migration(self, apps) でマイグレーション前の行をシードするには、履歴モデルレジストリ (apps.get_model("sentry", "MyModel")) を使用してください — from sentry.models... からの直接インポートは使用しないでください。現在のモデルは migrate_from 時点のスキーマと一致しない可能性があるためです。その後、マイグレーション後の状態を検証してください。

test_* メソッドはちょうど 1 つだけ書いてください。 setUp は すべての テストメソッドで migrate-down → seed → migrate-up のサイクル全体を実行するため、メソッドを増やすたびにカバレッジを増やさずに往復コストがかかります。複数のケースをカバーするには、setup_before_migration ですべてをシードし、単一のテスト本体でそれぞれを検証してください。

from sentry.testutils.cases import TestMigrations


class BackfillFooTest(TestMigrations):
    app = "sentry"
    migrate_from = "0123_before"
    migrate_to = "0124_backfill_foo"

    def setup_before_migration(self, apps):
        Foo = apps.get_model("sentry", "Foo")
        self.empty = Foo.objects.create(value=None)
        self.already_set = Foo.objects.create(value="kept")

    def test_backfill(self):
        self.empty.refresh_from_db()
        self.already_set.refresh_from_db()
        assert self.empty.value == "expected"
        assert self.already_set.value == "kept"

app と connection: app はテスト対象のマイグレーションが属する Django アプリラベルです — デフォルトは "sentry" ですが、マイグレーションがそのアプリの migrations/ ディレクトリにある場合は、たとえば "workflow_engine" に設定してください。connection はデータベースエイリアスで、デフォルトは "default" です; モデルのテーブルが実際に存在する接続に設定してください。どちらもマイグレーションとそのテーブルが実際に存在する場所と一致している必要があります。そうでないと、migrate up/down が誤ったデータベースに対して実行されます。

これらのテストは --migrations と --reuse-db フラグを付けてローカルで実行してください。初回実行時は、データベースを良好な状態にするために --reuse-db と併せて --create-db を使用する必要があります。

ガイドライン

履歴モデルと保存フック

apps.get_model() は、カスタムの save() メソッドを持たない履歴モデルクラスを返します。それが発行するシグナルは送信元として履歴クラスを使用するため、キャッシュ無効化フックなど、ライブモデルをスコープとするレシーバーは実行されません。

データマイグレーションを作成またはレビューする際は、ライブモデルの保存フックを確認し、必要な副作用を明示的に実行してください。引き続き apps.get_model() を使用してください; ライブモデルをインポートすることは安全な回避策ではありません。

カラムの追加
  • デフォルト値を持つカラムには default=<value> ではなく db_default=<value> を使用してください
  • NULL 許容カラム: null=True を使用してください
  • NOT NULL カラム: db_default を設定する必要があります
インデックスの追加

大きなテーブルの場合、インデックス作成が 5 秒のタイムアウトを超える可能性があるため、マイグレーションに is_post_deployment = True を設定してください。

カラムの削除

削除には 2 つのマイグレーションが必要です。両方を最初に書き上げますが、2 つの別々の PR にする必要があり、フェーズ 2 はフェーズ 1 の上に積み重ねて、そのマイグレーションがフェーズ 1 に依存するようにします。フェーズ 1 がデプロイされるまでフェーズ 2 はマージできないことを明確に伝えてください。一緒にマージすると、古いコードがまだ実行されている間にカラムが削除されてしまいます。

フェーズ 1 — MOVE_TO_PENDING

makemigrations をこの順序で 2 回実行します。フィールドがモデルから外れると Django は AlterField を生成できなくなるため、逆の順序で行うと、それを含めずに気付かないうちにリリースしてしまいます。

  1. フィールドをモデルに残したまま、その場で編集します。FK の場合は db_constraint=False、nullable でなく db_default もない場合は null=True を指定します。makemigrations を実行して AlterField を生成します。
  2. フィールドとそれへのすべてのコード参照を削除し、再度 makemigrations を実行します。生成された RemoveField を SafeRemoveField(..., deletion_action=DeletionAction.MOVE_TO_PENDING) に置き換えます。これはカラムではなく Django の状態を削除します。
  3. 両方を 1 つのマイグレーションに手動でマージします。例:
operations = [
    migrations.AlterField(
        model_name="testmodel",
        name="project",
        field=sentry.db.models.fields.foreignkey.FlexibleForeignKey(
            db_constraint=False,
            null=True,
            on_delete=django.db.models.deletion.CASCADE,
            to="sentry.project",
        ),
    ),
    SafeRemoveField(
        model_name="testmodel", name="project", deletion_action=DeletionAction.MOVE_TO_PENDING
    ),
]

フェーズ 2 — DELETE(2 つ目の PR、フェーズ 1 のデプロイ後にマージ)

makemigrations <app> --empty を実行し、同じ SafeRemoveField に deletion_action=DeletionAction.DELETE を指定します。PR にはこれ以外何も含めません。

モデルの削除(および最終的にはそのテーブル)

テーブルの削除には 2 つのマイグレーションが必要です。両方を最初に書き上げますが、2 つの別々の PR にする必要があり、フェーズ 2 はフェーズ 1 の上に積み重ねて、そのマイグレーションがフェーズ 1 に依存するようにします。フェーズ 1 がデプロイされるまでフェーズ 2 はマージできないことを明確に伝えてください。一緒にマージすると、古いコードがまだ実行されている間にテーブルが削除されてしまいます。

まず、内向きの FK を確認します。 他のテーブルにこのテーブルを指す外部キーがある場合、それらのカラムには独自の「カラムの削除」の対応が必要であり、その両方のフェーズが、このモデルのフェーズ 1 をマージする前にデプロイされている必要があります。

フェーズ 1 — MOVE_TO_PENDING

makemigrations をこの順序で 2 回実行します。モデルがなくなると Django は AlterField を生成できなくなるため、逆の順序で行うと、それを含めずに気付かないうちにリリースしてしまいます。

  1. モデルの各外向き FK フィールドに db_constraint=False を追加します(HybridCloudForeignKey の場合は代わりに null=True)。その後、AlterField オペレーションのために makemigrations を実行します。
  2. モデルとすべてのコード参照を削除し、再度 makemigrations を実行して、生成された DeleteModel を SafeDeleteModel(..., deletion_action=DeletionAction.MOVE_TO_PENDING) に置き換えます。
  3. 両方を 1 つのマイグレーションにマージします。AlterField を先にします。
  4. src/sentry/db/router.py(または getsentry/db/router.py)の historical_silo_assignments にテーブルを追加します。モデルが使用していた silo を選択します — 通常は SiloMode.CELL です。

制約の削除は任意ではありません。テーブルはフェーズ 2 まで存続しますが、Django はそれらを認識しなくなるため、それらにカスケードできません — 存続している親テーブルに対する削除は、残った制約で失敗します。複数のモデルを一度に削除する場合は、保留中の削除対象テーブル_間_の制約も削除して、フェーズ 2 の DROP TABLE の順序が問題にならないようにします。

フェーズ 2 — DELETE(2 つ目の PR、フェーズ 1 のデプロイ後にマージ)

makemigrations <app> --empty を実行し、同じ SafeDeleteModel に deletion_action=DeletionAction.DELETE を指定します。historical_silo_assignments のエントリはそのままにしておきます — テーブル削除のマイグレーションが silo を解決するためにそれを必要とします。

カラム/テーブルの名前変更

Postgres で名前を変更しないでください。古い名前を保つには db_column または Meta.db_table を使用します。

マージコンフリクトの解決

migrations_lockfile.txt がコンフリクトする場合:

bin/update-migration <migration_name>

これはマイグレーションの名前を変更し、依存関係を更新し、ロックファイルを修正します。

コメント