0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

既存マイグレーション33本を「移行しない」と決めた話|.sql baseline と phinx の境界

0
Posted at

はじめに

後からマイグレーションツール(phinx 等)を入れて、既存スキーマも全部そのツールに書き直すべきか迷っている方へ。

マイグレーションツールを後から入れると、必ず一度は迷います。「じゃあ今まで手書きの .sql で積んできた既存スキーマも、全部このツールの migration に書き直すべきか?」

僕が運用しているサービス(自作フレームワーク NENE2 の上に載った広告配信系のバックエンド、nene-serve)でも、まさにこの分岐に来ました。既存スキーマは連番の生 SQL ファイルで 0001_create_organizations.sql … と積み上がっていて、その数ちょうど 33 本。ここに新しく phinx を配線するにあたって、「33 本を全部 phinx の PHP migration に移す」か「移さない」かを決める必要がありました。

結論から言うと、移しませんでした。33 本は生 SQL の baseline のまま据え置き、phinx は「これから増える分」だけを担当します。過去と未来で書式が違う状態を、負債として放置するのではなく 「境界」として README に明文化して確定させました。この記事は、その判断の理由と、.sql.php が同じディレクトリで安全に共存できる仕組みを、実際の設定ファイル付きでまとめます。

この記事で分かること:

  • 「これからツールを使う」と「過去も全部そのツールに揃える」を別の意思決定として分ける考え方
  • .sql baseline と .php migration を同じディレクトリで安全に共存させる phinx 設定
  • やらなかった判断(33本を移さない/phinx を require に上げない)を README で境界として宣言する運用

前提: これまでの baseline は「生 SQL のループ適用」だった

phinx を入れる前、nene-serve のスキーマ適用はいたってシンプルでした。database/migrations/ に連番の .sql を置き、まっさらな DB にはそれを順番に流すだけです。

# CI (.github/workflows/ci.yml) がやっていること、の本質
for f in database/migrations/*.sql; do
  mysql … < "$f"
done

この 33 本が、既存スキーマの **単一の正典(SSOT)**です。MySQL で書かれていて、PostgreSQL 向けには scripts/mysql-to-pgsql.php*.sql を glob して DDL を変換します。つまり「生 SQL の baseline」という前提が、CI にも変換スクリプトにも染み込んでいます。

database/migrations/
├── 0001_create_organizations.sql
├── 0002_create_users.sql
├── 0003_create_placements.sql
│   … (中略)
├── 0032_create_assets.sql
└── 0033_add_custom_domain_to_organizations.sql   ← 33 本目

動いています。CI もグリーン。PostgreSQL 変換も回っています。ここに手を入れる理由が「ツールを統一したいから」だけなら、それは触る理由として弱いです。

なぜ phinx を入れたのか(そして何のために入れなかったのか)

phinx を入れたい動機は「これから」にありました。新しいテーブルやカラムを足すたびに手書き SQL を積むより、up() / down() が書けて status で適用状況が見えて rollback もできるツールに乗りたい。sibling の NENE2 が既に phinx 規約を持っていたので、それに揃えます。

ここで大事なのは、「これから phinx にしたい」と「過去の 33 本も phinx にすべき」は別の問いだということです。前者は素直に yes。後者を一緒くたに yes にすると、動いている 33 本の SSOT を、機能的な利得ゼロのために全部書き換えることになります。

33 本を PHP migration に移すと何が起きるか、を具体的に並べると割に合わなさが見えてきます。

  • リスクが大きいです。 既存スキーマの正典を丸ごと別書式に書き換える= SSOT の作り直しです。移し間違い(型・デフォルト・インデックス・外部キー)が 1 箇所でもあれば、既存 DB との差分になって表面化しにくいバグを生みます。
  • 利得がありません。 既に本番も CI も生 SQL の baseline で回っています。移したところで「今動いているものが、別の書き方で動く」だけ。ユーザにも運用にも新しい価値は出ません。
  • PostgreSQL 変換系も道連れになります。 scripts/mysql-to-pgsql.php*.sql 前提です。33 本を .php にしたら、この変換パイプラインごと作り直しになります。

というわけで、移行は意図的に見送りました(deferred)。「今後は phinx、既存は生 SQL の baseline」という二層構成を、その場のノリではなく 設定ファイルと README に書いて確定させました。

仕組み: phinx は *.php しか見ない。だから .sql と共存できる

「同じディレクトリに .sql.php が混ざったら phinx が誤爆しないか?」——ここが技術的な肝で、答えは しない、です。phinx はマイグレーションとして *.php しか走査しません。連番の .sql baseline は phinx から見て完全に不可視で、同じ database/migrations/ に置いたまま安全に共存します。

設定 phinx.php は、この方針をそのままコメントに書いてあります。接続情報はアプリと同じ NENE2 の ConfigLoader 経由なので、phinx は .env が指す DB をそのまま叩きます。

<?php
// phinx.php (抜粋)
$database = (new ConfigLoader(__DIR__))->load()->database;

return [
    'paths' => [
        'migrations' => 'database/migrations',   // .sql baseline と同居
        'seeds' => 'database/seeds',
    ],
    'environments' => [
        'default_environment' => $database->environment,
        $database->environment => $database->usesUrl()
            ? ['url' => $database->url]
            : [
                'adapter' => $database->adapter,
                'host' => $database->host,
                'name' => $database->name,
                'user' => $database->user,
                'pass' => $database->password,
                'port' => $database->port,
                'charset' => $database->charset,
            ],
    ],
    'version_order' => 'creation',
];

migrations パスを baseline と同じ database/migrations に向けているのがポイントです。ディレクトリを分けなくていい。phinx が .sql を無視してくれるので、「過去の 33 本」と「これから積む PHP migration」が 1 つのディレクトリに時系列で並びます。

コマンドは composer script に寄せました。

// composer.json (抜粋)
"scripts": {
  "migrations:status":   "phinx status   -c phinx.php",
  "migrations:migrate":  "phinx migrate  -c phinx.php",
  "migrations:rollback": "phinx rollback -c phinx.php",
  "migrations:create":   "phinx create   -c phinx.php",
  "migrations:seed":     "phinx seed:run -c phinx.php"
}

これで新しいスキーマ変更はこう積みます。

composer migrations:create -- CreateWidgetsTable   # PHP migration を scaffold
composer migrations:status                         # 適用/未適用を一覧
composer migrations:migrate                        # 未適用を適用
composer migrations:rollback                       # 直近バッチを rollback

境界を「宣言」する: fresh DB のブートストラップ順

二層構成のいちばんの落とし穴は、次にこのコードを触る人が「baseline も phinx で管理されている」と誤解することです。誤解すると、migrations:status に 33 本が出てこないのを見て「マイグレーションが欠けている」と勘違いしたり、baseline を phinx 化しようとして SSOT を壊したりします。

だから境界は、コードの挙動として存在するだけでは足りません。言語化して README に置きます。nene-serve では database/README.md に、ブートストラップの順序と各層の役割をこう固定しました。

  1. 生 SQL の baseline をループ適用します(前述の for f in … )。これで DB が「出荷時点のスキーマ」になります。
  2. composer migrations:migrate を回します。baseline 以降に phinx で足した分だけが適用されます。

phinx は自分が適用した migration だけを phinxlog テーブルに記録します。baseline は phinx の管理対象ではなく、一度きりのブートストラップであって、phinxlog には載りません。この非対称を README に書いておかないと、必ず誰かがハマります。

そして「33 本を phinx に移さなかった」ことも、判断として README に明記しました。「大きいし、機能的利得なしに SSOT を危険にさらすので、意図的にやらなかった」——こう書いておけば、半年後の自分や次の担当が「なぜ揃っていないんだ、揃えよう」と善意で地雷を踏むのを防げます。やらなかったことの理由こそ、コメントに残す価値があります。

積み残し: phinx を require に上げるかは「その時」まで保留

正直に deferred をもう一つ書いておきます。robmorgan/phinx は今 require-dev に入れてあります。本番を composer install --no-dev で作ると phinx が入らないので、本番デプロイで composer migrations:migrate を直接回す運用になったら、phinx を require に移す必要があります

今はそうなっていません。本番のスキーマ適用は生 SQL のループなので、phinx は開発時にしか要らず require-dev で足ります。だから移していません。これも「今やらない理由」を README の注意書きに残して、必要になった瞬間に気づけるようにしました。この手の「将来必要になるかもしれない移動」は、先回りしてやると使いもしない依存を本番に積むことになります。トリガー条件を書いて保留するのが、いちばん安いです。

検証したこと

配線が本当に無害かは、機能を実際に動かして確かめました。

  • phinx を sqlite で create → migrate → status(up) → rollback まで一通り実機で回しました(検証用の temp migration は確認後に削除)。
  • .sql baseline が phinx の走査対象外であることを実際に確認しました。33 本の .sqlmigrations:status に一切現れません。
  • composer check はグリーン(PHPUnit / PHPStan level 8 / php-cs-fixer clean)。既存の生 SQL 適用も PostgreSQL 変換も挙動不変です。

「設定を足しただけ、既存には触っていない」を口で言うだけでなく、baseline が phinx から見えないことを目視で確かめてから確定させました。

学び

  • 「これからツールを使う」と「過去も全部そのツールに揃える」は、別の意思決定として分けて判断します。 前者に yes でも、後者は「動いている SSOT を利得なしに作り直す」コストを別途量ります。まとめて yes にしません。
  • 混在は、宣言すれば負債になりません。 .sql の過去と .php の未来が同居していても、境界(どっちが何を担当し、どういう順で適用されるか)を README とコメントで明文化すれば、それは「未整理の負債」ではなく「意図された構成」になります。
  • ツールの走査規則を味方につけます。 phinx が *.php しか見ないという性質のおかげで、ディレクトリを分けずに共存できました。ツールの「見ないもの」を理解すると、無理な移行をせずに境界を引けます。
  • やらなかったことにこそ理由を書きます。 「33 本を移さなかった」「phinx を require に上げなかった」は、コードには現れない判断です。README に理由とトリガー条件を残しておかないと、次の人が善意で蒸し返します。

マイグレーションツールを後入れするとき、「既存も全部移すべきか」で手が止まっているなら、一度「移さないと何が困るか」を具体的に書き出してみてほしいです。困らないなら、境界を宣言して据え置くのが、いちばん安全で安いです。


一次資料

  • nene-serve PR #141 chore(test,db): adopt NENE2 DatabaseTestKit + wire phinx for future migrations(Closes #140)
  • 設定: phinx.php(NENE2 ConfigLoader 経由・migrations パスは baseline と同居)/ composer.jsonmigrations:* scripts(robmorgan/phinx ^0.16.11・現状 require-dev)
  • 運用契約: database/README.md(baseline=生 SQL の SSOT・不変/今後= phinx/fresh DB のブートストラップ順/require-dev 保留の注意書き)
  • 変換系: scripts/mysql-to-pgsql.php*.sql のみ glob・phinx PHP migration には触れない)

シリーズ/相互リンク(いずれも執筆中・公開前)

  • 運用系: (DatabaseTestKit のテスト配線統一 / nene-serve #141 の同 PR)、(1 ホスト多スタックのポート設計)。

── 筆者: 森 秀之(彩音インターナショナル)— 自己ホストの業務ツール群を実運用中。
中小企業向けの業務システムを料金公開・固定価格で開発しています。
🔗 ayane.co.jp

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?