🔖

migration、fixtureと開発時の起動スクリプトに関する私見

に公開

この記事は、開発時の docker-compose up で Django の migrate と fixture 読み込みを毎回実行するべきか、という運用の話を「判断できる形」に整理します(あくまで私見です)。

  • 対象: Django を docker-compose で開発しているチーム
  • この記事で分かること: migrate/fixture を自動実行する/しないの判断軸と、現実的な落としどころ
  • 結論: docker-compose upmigrate + 基本 fixture を自動実行し、例外用に別の compose を用意します
  • 前提: migration と fixture は Django のもの、起動スクリプトは docker-compose up を想定します
  • 限界: 規模/起動時間/ローカル DB の扱いなどで最適解は変わります

先に結論

結論としては、次の方針が実務では楽だと思います。

  • 開発用の docker-compose up では、migrate と「全員が必要な基本 fixture」を自動で実行します
  • それで困る少数(ローカル DB を手でいじりたい等)向けには、実行しない docker-compose.yml を別で用意します
  • 人数や期間が増えるなら、migration/fixture/テストで再現性を担保します(手動運用に寄せません)

この記事でいう「基本 fixture」は、開発環境で全員が共通で必要になる初期データ(Rails でいう seed に近いもの)を指します。テストだけに必要なデータセット(狭義の fixture)とは分けて考えます。

以下、用語整理→理想像→選択肢比較の順で理由を書きます。

migration と fixture について

migration とは何ですか?

migration は「移行」という意味の英単語で、ソフトウェア開発においては、DB スキーマの変更と、変更を実現する(大抵フレームワークによって用意された)手法のことです。

Django のプロジェクトでは

python manage.py migrate --noinput

というコマンドを起動前に実行するようにしていることがあります。
このコマンドは./{プロジェクト名}/migrations/ 以下にあるファイルたちに書かれた情報と、今接続されているデータベース内の django_migrations というテーブル内のデータを元に
自動的に今接続されているデータベースのテーブルスキーマを変更し、./{プロジェクト名}/migrations/ に書かれた最後の状態にします。

この migrate という操作は django に限らず、web フレームワークにはよくあるものです。
何のためにあるのか、どんな仕組みのものなのかについては少し引用します。

Rails ガイド での説明は次の通りです。

マイグレーションは、データベーススキーマの継続的な変更 (英語) を、統一的かつ簡単に行なうための便利な手法です。

とのことで、今回の話題は Wikipedia 内のバージョン管理システムとの関係(英語)の以下の部分と関わりが深いです。

  • ソースコードのすべてのバージョンは、少なくとも 1 つのデータベーススキーマに関連付けることができます。
  • スキーマ移行ツールは通常テストの前提条件としてのビルドの一部として呼び出されます。
  • このアプローチにより、特定のコードブランチと互換性のあるデータベーススキーマを再現するために必要な情報がソースツリー自体から回復可能になります。
  • このアプローチのもう 1 つの利点は、競合するスキーマの同時変更を処理できることです。開発者は、通常のテキストベースの競合解決ツールを使用して、違いを調整することができます。

と書かれています。

fixture とは何ですか?

fixture は「備品」という意味の英単語で、ソフトウェア開発においては、テストを毎回同じ条件で実行するための事前準備を提供するものです。

一般的にはデータベース内のデータに限りませんが、Django において fixture といえばデータベースのデータを提供する機能です。
(公式ドキュメントでも、fixture は開発用の初期データ投入に利用できるとされています。)

python manage.py loaddata ./{プロジェクト名}/fixtures/*.json

というコマンドを実行することで ./{プロジェクト名}/fixtures/*.json に書かれたデータを接続されているデータベースに挿入できます。

Rails においては

  • 基本となるデータセット(結構でかい DB データそのままのダンプとかも想定)を入れる場合
  • あるテストが動けば良いというだけの小さいデータセットを入れる場合

で別の仕組みが用意されています(前者が seed, 後者が fixture)が、Django の場合、公式にあるのは fixture のみで前者も fixture でまかなうことが多いです。
(あまりにも大きいデータの場合はそもそも git 管理しにくいので、データベース自身の機能(ダンプとリストア)を利用するとよさそうです。)

migration と fixture についての理想像

というところで、migration と fixture の運用方法の理想像を考えました。

コミット=再現できる DB 状態にします。 開発の起点となるブランチの各コミットで、ゼロから migration(スキーマ)と fixture(データ)を使って同じ状態で始められるのが理想です。

スキーマを変えるなら、まず migration をコミットします。 最新の migration と違うスキーマでの開発を許容しない、という前提に寄せます(レビューや支援ができなくなるためです)。

データは複数状態を許容します。 1つのスキーマに対して fixture を複数用意し、「このデータがないときはこう」「あるときはこう」を確認できるようにします。

マージ/リバートでもスキーマ矛盾を解消します。 コードのコンフリクトを解消するのと同様に、DB スキーマの矛盾も解消します。そのためにテストを書き、複数の fixture を使い分けて確認します(スキーマ自体は最新のみで良いです)。

前述の理想を守ると起きること

この理想を守ると、レビュー時に常に実装者と同じ状態で動作確認を行えます。また、別 PC で開発することになっても(新規参加者が増えても)状態を再現しやすく、問題発生時に任意の commit へロールバックできます。commit と DB スキーマが結びつくので、意図しない migrate が起きた場合も「checkout した commit が違う」という形で原因を切り分けやすくなります。

これは厳しい理想で、もちろん手で少し変更を加えるなどして、commit が指定する状態とは違う状態で開発を進める場合も多々あって当然です。
ただ、そこで加えた変更は今の自分以外誰にも分からないので、いわゆるサポート外として扱うべきだと思います。
他の人のレビューやサポートが欲しければ、それを表す migration や fixture を書く、ということです。
(それでも開発速度を優先させるために migration, fixture なしで回避策のようなものを用意して進めるのは、小さい規模だったり、一時的なものと分かっている部分ならありかもしれません。ただ、一般的には関わる人数が増えたり開発が進むにつれて、逆にどんどん時間がかかるようになってしまいます。)

デフォルトの開発時の起動スクリプトはどうあるべきか

これは前述の理想論に比べるとどちらでもよいと感じますが、2つの選択肢を比べて考えてみます。
ここでは loaddata fixture は複数種類あるうちのみんなが必要とする基本データが読み込まれるものとします。

選択肢 1

migrateloaddata fixturedocker-compose up では起きないようにして、README に、

  • (開発始めやレビューなどで) commit を移動した際には、まず migrateloaddata fixture をしてください。(あるいはそれが起こるスクリプトを起動してください。)
  • その後開発・動作確認には docker-compose up をしてください。

と書きます。

選択肢 2

migrate と loaddata fixturedocker-compose up起きるようにして、README に、

  • 開発・動作確認には docker-compose up をしてください。

と書きます。

選択肢 1 の利点・欠点

  • 利点: 自分の DB に変更を加えている人にとって、意図せず変更されることがなく安心できます(前述の理想では「サポート外」として扱う想定です)。
  • 欠点: 気にしなければならない操作が増えます。特に新規参加者がつまずきやすく、支援コストが増えやすいです。

選択肢 2 の利点・欠点

選択肢 1 の逆で

  • 利点: 気にしなければならない操作が減ります。
  • 欠点: 自分の DB に変更を加えている人にとって意図しない DB の改変が行われます。

なお migrate は適用済みの migration があれば基本的に追加の変更は入らないため、2 回目以降のコストは小さくなりがちです。
一方で loaddata は毎回処理されるので、基本 fixture は小さく保ち、何度実行しても状態が収束する(同じ状態に近づく)ように設計しておくのが前提になります。

ということで、選択肢 2 が良さそうです。

まとめ

規模が大きくなるほど、手動運用は避けます。 migration/fixture/テストで再現性を担保し、「誰がどこで動かしても同じ状態になる」前提に寄せます。

開発用の docker-compose up では migrate と基本 fixture を自動実行します。 多数が迷わない導線を優先し、レビューや支援の前提を揃えます。

少数の例外には回避策を用意します。 ローカル DB を手でいじりたい人向けに、migrate/loaddata を走らせない docker-compose.yml を別で用意し、docker-compose up -f ファイル名 で起動できるようにします。

実務メモ(落とし穴・代替案・検証)

落とし穴

  • 自動実行にすると「ローカルで手でいじった DB」が意図せず変わります(例外用の compose を用意します)
  • fixture が大きいと起動が遅くなり、結局回さなくなります(全員が必要な最小データに絞ります)
  • loaddata を再実行すると、fixture で作った行に加えた変更は消えます(= 手でいじった DB と相性が悪いです)
  • fixture を「毎回入れても壊れない」前提にするなら、基本 fixture を「上書きされても困らない最小の初期データ」に絞ります
  • fixture から消したデータは自動では消えません(完全に揃えたい場合は DB リセット等の手段が必要です)

代替案と比較軸

  • 自動実行(選択肢2): 多数が迷わない導線を優先したい場合に合います
  • 手動実行(選択肢1): ローカル DB を頻繁に手でいじる人が多い場合に合います
  • 比較軸: チーム規模、レビュー頻度、起動時間、ローカル DB の自由度、支援コスト

判断フロー(最短)

  • 新規参加者やレビューが増えやすい: 自動実行 + 例外用 compose が合います
  • 個人開発/少人数で DB を手でいじる前提が強い: 手動実行(README に手順を固定)が合います

検証(最小)

  • docker-compose up だけで動作確認できます(手順が迷子になりません)
  • ブランチ切替や git pull 後に、migrate/fixture を含めた状態でテストが通ります

参考(一次情報)

更新: 2025-12-29(冒頭に対象/結論を追加、先に結論を固定、要点を段落中心に整理、参考(一次情報)を追記、語調を調整、落とし穴、代替案、判断フロー、検証観点を追記。migrate/loaddata の挙動補足と Django の一次情報リンクを追記)

Discussion