
Summary
この文書の要点
- スキーマ変更は追加、移行、削除の段階に分ける。
- TypeScript型の変更とDBマイグレーションの順序を合わせる。
- expand、backfill、contractの段階に分け、アプリとDBの互換期間を作る。
- Drizzleの型は現在形を表すため、移行途中のデータ状態は別途検証する。
どこが設計の難所か
Drizzleを使うと、テーブル定義から型を得られるため実装は明確になります。一方で、列名変更、型変更、NOT NULL制約追加、参照関係の追加は、既存データがある環境では単純に適用できません。
本番ではアプリの旧バージョンと新バージョンが一時的に混在することがあります。マイグレーション直後に古いコードが動く可能性、途中で失敗して戻す可能性、データ量が多くALTERが重い可能性を考慮します。
PostgreSQLのスキーマはアプリケーションの実装より長く残ります。カラム追加、型変更、NOT NULL化、インデックス追加は小さく見えても、既存データ、ロック時間、デプロイ順序に影響します。
境界をどう切るか
安全な変更は、まず列を追加し、アプリを両対応にし、バックフィルし、制約を足し、最後に古い列を消す順番で進めます。Drizzleの型は最終形だけでなく移行途中の状態も意識し、リリースをまたいで互換性を保ちます。
Drizzleではスキーマ定義とクエリの型が近いため、変更の意図をコードレビューしやすくなります。ただし、マイグレーションは型の問題ではなく時間の問題でもあります。古いアプリと新しいDB、新しいアプリと古いデータが同時に存在する期間を前提にします。
実装で効く細部
マイグレーションファイルには、既存データの更新、インデックス作成、制約追加を分けて書きます。大きなバックフィルは同期マイグレーションに入れず、ジョブとして分割実行します。アプリ側では新旧列の読み書き期間を短くし、完了条件をログで確認します。
例えば必須カラムを追加する場合、まずNULL許容で追加し、アプリで新規書き込みを始め、バックフィルを実行し、最後にNOT NULL制約を足します。Drizzleのschema変更とSQLマイグレーションを同じPRで見せつつ、実行順序はデプロイ手順に明記します。
- 大きなテーブルへのインデックス追加はCONCURRENTLYを検討し、トランザクション設定に注意する。
- バックフィルは一括更新ではなく、主キー範囲やlimitで分割できるようにする。
- アプリ側は移行期間中のNULLや旧値を読めるようにしてからDB制約を強める。
壊れ方を観測する
検証では、本番に近いデータ量でマイグレーション時間を測ります。ロールバックできる変更か、戻せない変更かを事前に分類し、戻せない場合はバックアップとメンテナンス手順を用意します。
検証では、空DBだけでなく本番に近いデータ量のDBへマイグレーションを当てます。実行時間、ロック、ロールバック可否、旧アプリとの互換性を確認し、型チェックだけでは見えない移行リスクを潰します。
捨てた選択肢とトレードオフ
段階移行は手順が増えます。小さなアプリでは一度に変えたくなりますが、データを失う変更や長時間ロックを伴う変更では、段階を分けるコストの方が小さく済みます。
段階移行はPR数やデプロイ回数が増えます。一度で変更すれば速いものの、失敗時の復旧が難しくなります。業務停止を避けたいテーブルほど、コードのきれいさより互換期間を優先する判断が必要です。
現場に残す判断軸
Drizzleの型安全性は、スキーマ変更の見通しを良くする道具です。しかし安全な移行には、リリース順序、既存データ、ロールバックを含めた運用設計が欠かせません。
Drizzleの価値は、スキーマ変更を型で見えるようにすることです。ただし安全な変更には、型、データ量、ロック、デプロイ順序を同時に見る視点が欠かせません。


