generate-migration について
Sentry のバックエンド開発者向けの Django マイグレーションガイドです。生成、レビュー、テストに加え、フィールドやモデルを安全に段階削除する手順を説明します。
主な機能
- 01通常・空のマイグレーション生成
- 02SQLと安全性の確認
- 03データ補完マイグレーションのテスト
- 04フィールドとモデルの段階的な削除
- 05マイグレーションロックファイルの競合解決
活用シーン
- 01モデル変更のマイグレーション作成
- 02データ変換マイグレーションのテスト
- 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
生成後
- 新しいモデルを追加した場合は、アプリの
__init__.pyでインポートされていることを確認してください - 生成されたマイグレーションが正しいかレビューしてください
sentry django sqlmigrate <app_name> <migration_name>を実行して SQL を検証してください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 を生成できなくなるため、逆の順序で行うと、それを含めずに気付かないうちにリリースしてしまいます。
- フィールドをモデルに残したまま、その場で編集します。FK の場合は
db_constraint=False、nullable でなくdb_defaultもない場合はnull=Trueを指定します。makemigrationsを実行してAlterFieldを生成します。 - フィールドとそれへのすべてのコード参照を削除し、再度
makemigrationsを実行します。生成されたRemoveFieldをSafeRemoveField(..., deletion_action=DeletionAction.MOVE_TO_PENDING)に置き換えます。これはカラムではなく Django の状態を削除します。 - 両方を 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 を生成できなくなるため、逆の順序で行うと、それを含めずに気付かないうちにリリースしてしまいます。
- モデルの各外向き FK フィールドに
db_constraint=Falseを追加します(HybridCloudForeignKeyの場合は代わりにnull=True)。その後、AlterFieldオペレーションのためにmakemigrationsを実行します。 - モデルとすべてのコード参照を削除し、再度
makemigrationsを実行して、生成されたDeleteModelをSafeDeleteModel(..., deletion_action=DeletionAction.MOVE_TO_PENDING)に置き換えます。 - 両方を 1 つのマイグレーションにマージします。
AlterFieldを先にします。 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>
これはマイグレーションの名前を変更し、依存関係を更新し、ロックファイルを修正します。