ぼくらのかんがえた さいきょうの DBスキーマうんよう(Rails版)

既に花粉症が出てきた下條です。タイトルは大げさです。すみません。

Ruby on RailsではDBスキーマの管理方法として、マイグレーション機能が標準で備わっています。これは小規模なシステムではうまく運用できますが、プロジェクトが増えたり、多くの開発者が参加すると運用が難しくなってくることがあります。最近よく遭遇するのは複数のプロジェクトが同一のDBを利用する場合です。例えばCMS, API, SPAといった別々のプロジェクトが同一のDBを利用し、特にRails開発者とフロントエンドの開発者が一緒のDBを見るような場合に問題になることが多いです。こういった場合にRails標準のマイグレーション運用をすると下記のような課題・問題が発生します。

  • どのプロジェクトでマイグレーションをすればよいのか。各プロジェクトでマイグレーションファイルが分散すると、開発のDBスキーマ管理が面倒なことになるので避けたい。
  • どれか1つのプロジェクトのみでマイグレーションをする場合、各プロジェクトでいかにDBスキーマを最新に保つか。マイグレーションを実行したプロジェクトで生成されるスキーマファイル(db/schema.rb)をその他プロジェクトにも反映させるべきだが、忘れがち。
  • マイグレーションについて通常意識しないフロントエンドの開発者が一緒に開発する際に、開発環境DBのマイグレーションでつまることが多い。(きちんとドキュメント化できていないという話もありますが。)
  • 複数プロジェクトに関わらない話だが、開発の初期段階にはDBスキーマが頻繁に変わることがあるため、膨大な数のマイグレーションファイルができてしまう。

クックパッドにおける最近のActiveRecord運用事情には、モノリシックアプリケーションでも早々にマイグレーション運用をあきらめたとの記述がありました。実際のところマイグレーション運用をしていないプロジェクトは世の中に多くあるのではないかと思っています。しかし、それではどうやってDBスキーマを管理すればよいのかというところを調べましたが、あまり情報は見当たりません。弊社では従来から基本的にはマイグレーションによるDBスキーマ管理を行ってきました。ただ、上記のような問題が頻発するようになったため、開発環境においてサーバーサイド開発者・フロントエンド開発者皆が常に最新のスキーマを容易に適用して開発でき、開発効率をアップすることを目的として、新たなDBスキーマ管理方法を運用しており、今回ご紹介したいと思います。

なお、本記事は全くRails標準のマイグレーションを否定するものではなく、社内でも一般的なRailsアプリケーションではマイグレーションを利用しています。

設計時には特に以下の点を考慮しました。

  • アプリケーションのデプロイ時には、アプリケーションの修正とDBスキーマの修正は同時に適用されるようにすること。
  • 変更されたDBスキーマを開発環境DBに適用する手順を容易にすること。
  • DBスキーマ変更時に開発環境DBのデータをクリアしてしまわないこと。

DBスキーマ変更を伴うアプリケーション改修フロー

発想としてはシンプルで、マスターとなるDBスキーマファイルを持たせて、それを実際のDBにも同期させるという方針です。
各Railsプロジェクトの親となるプロジェクトを作成し、DBスキーマは親プロジェクトで管理します。子プロジェクトはGitのサブモジュールとして管理します。(プロジェクトリポジトリと読みかえていただいてもかまいません。)
スキーマファイルのDBへの適用にはRidgepole gemを利用します。これは、DBスキーマファイルと実際のDBのスキーマの差分を適用してくれるというgemです。

まず親プロジェクトで下記図の通りDBスキーマの変更を行い、プルリクエスト(PR)します。

補足

  • DBスキーマ更新コマンドでは以下の3つの処理を行っています。
  1. 開発環境DBにSchemafile(Ridgepole gemのスキーマファイル)をもとにDBスキーマの変更を適用。
  2. 最新のDBスキーマをstructure.sqlの形でダンプ。structure.sqlは、正しくDB構造が変更されているかを確認するために出力し、PRに含めています。
  3. 親プロジェクトのSchemafileを各Railsプロジェクト配下にコピー。

次に、サブモジュールであるRailsプロジェクトでアプリケーションの修正を行い、PRします。

これで2つのPRができて、レビュー完了後にマージするわけですが、まずは親プロジェクトのスキーマ変更PRをマージした後で子プロジェクトのPRをマージします。子プロジェクトのマージ後にはCircleCIで自動的にテスト・デプロイ・DBスキーマ適用する処理となっているのですが、そこに親プロジェクトとのスキーマ差分をチェックする処理を入れており、子プロジェクトを先にマージするとデプロイ・DBスキーマ適用がコケるようにしてあります。これは親プロジェクトのスキーマファイルが必ず最新のものになっていることを保証するための仕組みです。

DBスキーマ変更の開発環境への適用手順

上記のように変更されたDBスキーマの開発環境DBへの適用手順は以下です。

  1. 親プロジェクトで最新のmasterを取得する。
  2. 親プロジェクトでDBスキーマ更新コマンドを実行する。

簡単ですね!

具体的なディレクトリ構造について

詳細なディレクトリ構造のイメージは以下です。ただ、今回の話題とは外れるので書いていませんが、実際には開発環境はDocker化されています。この親プロジェクトにdocker-compose.ymlが配置されていて、開発時には必要に応じたRailsプロジェクトのコンテナを立ち上げられるようになっています。

1
親プロジェクト
│── db
│   ├── ridgepole(適用するスキーマファイル郡を管理するディレクトリ。DBスキーマの管理はこのディレクトリですべて管理する)
│   │   ├── Schemafile
│   │   ├── 各テーブル名.schema
│   │   └── ...
│   └──  structure.sql(スキーマ更新後に出力された確認用スキーマファイル)
│── scripts
│   ├── update_schema.sh(DBスキーマ更新用スクリプト)
│   ├── init_db.sh(DB初期化スクリプト)
│   └── insert_mock.sh(モックデータ格納用スクリプト)
├── RailsプロジェクトA(子プロジェクト)
│   ├── db
│   │   └── Schemafile(スキーマ更新時に自動で更新する。テストDBのスキーマ適用時、デプロイ時のスキーマ適用時に使用する。)
├── RailsプロジェクトB(子プロジェクト)
│   ├── db
│   │   └── Schemafile(スキーマ更新時に自動で更新する。テストDBのスキーマ適用時、デプロイ時のスキーマ適用時に使用する。)

まとめ

DBスキーマの変更は若干ややこしい手順ですが、DBスキーマ変更の適用手順はとても簡単になり、開発者同士で最新のDB構造を同期するのが簡単になりました。

ポイントは親プロジェクトにDBスキーマの管理機能をもたせたこと、DBスキーマ管理にRidgepoleを使うようにしたことです。Ridgepoleでは、スキーマファイルと実際のDBとの差分のみを実行するSQLを発行するため、マスターとなるスキーマファイルを持っておけば、スキーマファイルと実際のDBが可能となります。

まだいろいろと改善の余地はあると思います。今後もよい運用を考えていきたいと思っています。

Ruby on Railsを活用したWebサービスや業務システム開発をご検討の企業様は、是非MMMにご相談下さいませ!

このエントリーをはてなブックマークに追加