こんにちは。大阪楽楽開発課のdaina_rksです。
Laravelのマイグレーションを活用して、テーブル定義を更新しているサービスは多いと思います。
しかしサービスが継続するにつれ、気づけば大量のマイグレーションファイルが存在している、、、なんて経験はありませんか?
私が携わっていたプロジェクトでも同じ悩みに直面していました。
この悩みに対して、私はマイグレーションファイルを全て削除するということを行いました。
今回はそのときの経験について、なぜマイグレーションファイルを削除するに至ったのか、削除するにあたって行なったこと、削除した結果どんな効果があったのかをご紹介します!
マイグレーションファイルを全て削除するに至った理由
問題
私のプロジェクトでは、マイグレーションファイルが大量に存在することが原因で以下のような問題がありました。
マイグレーションファイルを全て実行するのに時間がかかる
実行するファイル数が多いと必然的に完了までの時間が長くなっていました。また、テーブル定義更新以外の処理も行なっているマイグレーションファイルもあり、その実行にさらに時間がかかっていました。
結果として全て完了するまでにおよそ13分もかかるため、新規DBの構築や自動テスト用のDB構築に時間がかかっていました。
マイグレーションファイルのメンテナンスコストがかかる
実行済みのマイグレーションファイルに対しても、PHPもしくはLaravelのEOL対応が必要であるため、メンテナンスコストがかかっていました。(当時で150個のマイグレーションファイルが存在していたため、1~2人日ほどコストがかかっていました。)
アイデア
この問題の解決策として、DB再構築時はダンプファイルからリストアするというアイデアを思いつきました。
そうすればマイグレーションファイルを実行する必要がなくなるため、DB構築の速度改善が見込めます。
※実際にアイデア段階で簡易的に試したところ大幅な速度改善がありました。
またダンプファイルから再構築できるようになるため、1度実行したマイグレーションファイルは不要となります。このことから既に実行済みの過去のマイグレーションファイルを削除しても問題がないため、メンテナンスコストの削減も見込めます。
アクション
上記のアイデアを実現するためにやるべきことは以下の2つです。
- DBのダンプ&リストアの仕組み構築
- 過去のマイグレーションファイルの全削除
ダンプ&リストアの仕組み構築
1つ目のアクションとして、DBのダンプファイルを生成する仕組みとリストアする仕組みが必要となります。
ダンプ
ダンプファイルはマイグレーションファイルが実行された直後のプレーンな状態のDBから、定期的に生成される必要があります。 そこでDBのダンプ処理はJenkinsのジョブで行うこととしました。
私のプロジェクトでは、Jenkinsサーバーでアプリケーションは動いていなかったため、常にプレーンな状態のDBが存在していました。またジョブを定義すれば定期的にダンプ処理を実行することができます。(Jenkinsサーバーで1バージョンの開発が完了するたびにソースコードのバックアップを取るジョブが実行されていたため、そのジョブの中でダンプ処理を実行するようにしました。)
ダンプファイルを生成したタイミングで、実行済みのマイグレーションファイルを削除する処理をジョブの中に加えると、今後追加されるマイグレーションファイルに関しても、実行後に削除されるという運用に乗せることができました。
リストア
環境構築手順や自動テストの仕組みをなるべく変更したくないため、リストアの処理はLaravelのArtisanコマンドで実行できるように作成しました。(php artisan migrate
を実行していた部分をphp artisan restoreDb
に変更するだけで済むようにしました。)
リストア後に確認しなければならないことは、ダンプ元となったDBと差分がないことです。 具体的にはスキーマとテーブルの所有者や権限が同じこと、テーブル定義およびレコードに差分がないことを確認する必要があります。
所有者と権限に関しては目視で確認、テーブルに関しては平文でダンプファイルを取得しdiff コマンドで差分がないことを確認しました。
マイグレーションファイルの削除
2つ目のアクションは不要となった実行済みのマイグレーションファイルを削除することです。
本来ならばマイグレーションファイルはテーブル定義の更新のみを行なっているはずなので、ダンプファイルが正しく生成されていれば削除しても問題はありません。
しかし冒頭の問題で挙げた通り、テーブル定義の更新以外の処理を行なっているマイグレーションファイルがいくつか存在しているため、何も対応せず削除すると不具合になり得るものがありました。
テーブル定義の更新処理以外の処理を行なっているマイグレーションに関しては、処理の内容に応じて以下の対策を行いました。
環境変数の更新
私のプロジェクトでは、環境変数に変更がある場合は環境変数のテンプレートファイル.env.example
を修正する運用でした。
しかし開発初期ではこの運用が定められていなかったため、マイグレーションファイル内でsedコマンドを実行し、環境変数を更新する運用を行なっていたようです。(かなり危ない運用)
環境変数の更新を行なっているマイグレーションファイルに関しては更新内容を確認し、最新バージョンの.env.example
に存在しない値があれば、追記してから対象のマイグレーションファイルを削除しました。
ディレクトリ操作
マイグレーションファイル内でmkdirコマンドやchmodコマンドを実行し、ディレクトリ操作や権限変更を行なっていました。
おそらく、あるバージョンでファイルアップロード機能が実装された際、マイグレーションを活用してアップロードファイルの一時置き用ディレクトリを作成していたようです。
ディレクトリ操作の処理に関しては環境構築用のAnsibleのPlaybookに、必要なディレクトリの作成処理および権限変更処理を記述し、マイグレーションファイルを削除しました。
不具合対応
画像のリサイズやファイルの移動など不具合対応用の処理が、マイグレーションファイル内で行われていました。
このような処理は今後必要になる可能性がある(実際に複数のマイグレーションファイルで同様の処理が行われていました)ため、LaravelのArtisanコマンドで実行できるように処理を切り出し、対象のマイグレーションファイルを削除しました。
結果
上記のアクションを行い、DB構築時はダンプファイルからリストアするというアイデアを実現できました。
肝心の結果ですが、DBの構築時間は13分から1.8秒(!!)まで短縮することができました。
これにより新規環境構築時間は10分以上短縮され、DB接続を伴う自動テストの実行は数時間から約10分まで短縮されました。
(DB接続を伴う自動テストに関してはテストメソッドごとに全テーブルをDROP→全マイグレーションを実行という仕組みだったので、CI完了時間が数時間かかっていました。そもそも自動テストの仕組みを見直せていなかったことが要因ですが、そのことについては今回は目を瞑リました。)
メンテナンスコストに関しても、EOL対応ごとに1~2人日分短縮されることになったため、問題を解決することができました。
まとめ
今回は過去のマイグレーションファイルの削除についてご紹介させていただきました。
マイグレーションファイルを全て削除した結果、新規環境構築時間や自動テストに係る時間が大幅に短縮でき、メンテナンスコストの削減効果もありました。
冒頭に挙げた問題と似た問題を抱えているエンジニアの方や、今後マイグレーションファイルの運用を見直したいエンジニアの方のご参考になれば幸いです。
参考情報
今回のアクションで実施したダンプ&リストアの仕組み構築ですが、実はLaravelのSquashing Migrations という機能ですでに実現されています。
アイデア段階でこの機能を利用することを検討したのですが、この機能でダンプされる対象が1スキーマのテーブル定義のみでした。
私のプロジェクトの場合、ダンプ対象は2つのスキーマのテーブル定義と初期データ用のレコードであり、Laravelの機能では実現できなかったため利用することを見送りました。
もしマイグレーションファイルの削除を行う場合、LaravelのSquashing Migrationsを利用できれば運用変更に係るコストを抑えることができるため、ぜひ選択肢に入れていただけたらと思います。