DBのスキーマ、皆様どのように管理されているでしょうか。
Railsを利用されている方の多くは、ActiveRecordのマイグレーションを利用して管理をされているかと思います。
私もいままでいくつかのRailsプロジェクトに関わってきましたが、
ほぼ全てのプロジェクトでActiveRecordのDBマイグレーションを利用してきました。
(一部のプロジェクトはActiveRecordを使っていないため、マイグレーションも独自のものを利用しています)
ActiveRecordのマイグレーションでは、DBスキーマ変更の差分情報をマイグレーションスクリプトとして保存しておきます。例えば、新しいテーブル「users」を作成する場合は、下記のようなマイグレーションスクリプトを作成します。
class AddUsers < ActiveRecord::Migration
def up
# ここにマイグレーション適用時の操作を書く
create_table :users do |t|
t.string :name
t.datetime :created_at
t.datetime :updated_at
end
end
def down
# ここにマイグレーション破棄した時の操作を書く
drop_table :users
end
end
テーブル作成/削除やカラム変更を行う際には、マイグレーションスクリプトを1つ作成します。
このように変更を管理することによって、DBスキーマを最新版へ移行させたり、任意の時点のスキーマに戻したりすることが可能です。また、スキーマの一貫性を保つことができます。
便利な機能なのですが、プロジェクトが始まったばかりの初期開発フェーズで、このマイグレーション運用をするのはかなり辛いです。
プロジェクト初期の段階では、DBの設計もちゃんと固まっていないため、スキーマ変更が頻繁に発生します。
スキーマ変更のたびにマイグレーションスクリプトを書かないといけないのは手間がかかるもので、DB修正を気軽に行うことができなくなります。
運用保守フェーズに入ったシステムで、ActiveRecord標準のマイグレーションを利用することには、今のところ大きな不満はありません。
しかし、システム一次リリース前の開発フェーズで、DBマイグレーションを利用することは苦痛でしかありません。
私の所属している会社はSIerということもあり、新規にシステムを作成する、ということが間々あります。
開発初期の段階ではDBのスキーマ定義が頻繁に変わります。
テーブル定義をはじめからきっちり設計している、と思っていても、変わります。
このカラムにはINDEXを張っておかないと、とか、負荷対策の為にここにも同じデータ入れておこう、とか
テーブル分割しよう、とか。
下図は、とあるプロジェクトのマイグレーションファイルの一部です。
開発初期の段階では、あっという間にスクリプトファイルが増加していきます。
日付のついた、よく分からないマイグレーションファイルが大量にリポジトリにコミットされてるのは、なんだか違和感があります。
また、DBスキーマの今の状態がどうなっているのか、マイグレーションファイルから推測するのは難しいです。
なにより辛いのは、あるテーブルに1カラム足すだけでも、マイグレーションファイルを作成しないといけないことです。
このため、気軽にテーブル定義が変更できません。
DB設計が固まっていない段階でのマイグレーション運用は辛いです。
なので、ちゃんと使うことを諦めました。
前回とは別のプロジェクトのマイグレーションファイル一覧です。
ファイル数がかなり少ないです。
initial_schema.rb というマイグレーションファイルを作り、そこをひたすら編集していきます。
運用段階に入ったシステムでリリース済みのマイグレーションファイルを編集する、というのは絶対にやってはいけないことです。
ですが、システムリリース前だと、このような適当な運用でもなんとかなります。
これでdb/migrate以下にマイグレーションファイルが大量にできてしまう問題を回避できますね。
DBスキーマの定義に変化があったとき、開発環境のDBをどうやって最新の状態にするか。
開発者は皆、以下のコマンドを叩いてDBを作り直します!
bundle exec db:drop db:create db:migrate db:seed
システム1次リリースまではずっとこの調子です。
上記コマンドを毎回入力するのはちょっと辛いので、以下のようなrake taskも用意しています。
namespace :db do
task :overhaul do
Rake::Task["db:migrate:reset"].invoke
Rake::Task["db:seed"].invoke
end
end
これで
bundle exec rake db:overhaul
と入力したらDB更新完了です。便利!
、、、じゃないです。辛いです。データなくなっちゃいます。
開発をするにあたり、開発者は各々自分用の秘伝のテストデータを作っていたりします。
また、本番に近いテスト用環境を構築し、そこで画面を叩いてテストデータを作るケースもあります。
それなのに、DBスキーマの更新が発生するたびにテスト用データが消えてしまうんですよ。これは辛いです。
「すみません、今まで作ったデータですが、DB更新するので消えて無くなります」ってなんかおかしくないですか?
こんな感じの開発スタイルを最近まで続けておりました。
2014年8月の終わり頃、いつものようにはてなブックマーク巡回をしていると、以下の記事が流れてきました。
上記ブログの中で、Ridgepoleと呼ばれるスキーマ管理ツールが紹介されています。
このツールでは、DBスキーマはSchemafileと呼ばれるファイルに以下のようなDSLで記述しておきます。(DSLはActiveRecord SchemaのDSLと同じです)
create_table "publishers", force: true do |t|
t.string "name", limit: 100, null: false
t.text "url"
end
カラムを修正する場合、Schemafileの定義を修正するだけです。
例えば、created_atカラムを足す場合を考えてみます。
Ridgepoleでは、まず以下のようにSchemafileを書き換えます。
# created_at カラムを追加
create_table "publishers", force: true do |t|
t.string "name", limit: 100, null: false
t.text "url"
t.datetime "created_at"
end
その後、以下のコマンドを叩くだけでDB定義の更新が完了します。
ridgepole -c config.yml --apply
# alter table publishers add column datetime DEFAULT NULL; というクエリが発行される
スキーマ更新の際にわざわざマイグレーションファイルを作る必要がありません。
テーブル定義更新もdrop, createじゃなくて、alter文を発行してくれます。データが消える心配もありません。
RidgepoleがDBの現在の定義と、Schemafileとの定義を見比べて、差分更新を行ってくれるためです。
これは便利!是非導入しよう。
と思って、Ridgepoleをあれこれ試していましたが、色々足りない機能がでてきました。
などなど。
正直この程度なら、Ridgepoleに手を加えれば直せます。実際、Ridgepoleを直そうと、色々試行錯誤をしておりました。
例えばカラムコメント追加は、以下のようにちょっと手直しすれば解決できました。
https://gist.github.com/tim-nishio/5912b60d2ee8e5059532
ですが、私の悪い癖がでてしまって、、、
あれこれいじっているうちに、既存のものを改良するより、自分で一から作ってみたくなってしまったんですよね。
突如自分でもスキーマ管理ツールを作ってみたくなったので、自分の手になじむツールを自作してみました。
Convergenceという名前のスキーマ管理ツールです。
フルスクラッチで作っているため、ActiveRecord等他のgemには依存しない作りになっております。
なので、Railsじゃないプロジェクトでも利用できるかと思います。
使い方は、上記のgithubを見ていただければわかるかと思います。
コマンド体系はRidgepoleにインスパイア!されたので、ほぼ同じです。
DSLはActiveRecordのDSLとは似てるようで違うので、注意が必要です。
今あるDBスキーマをConvergenceのDSLにダンプしたい場合は、以下のコマンドでDSL出力が可能です。
$ convergence -c database.yml --export > example.schema
database.yml には以下のようなDB接続情報を記述しておきます。
adapter: mysql
database: example_database
host: 127.0.0.1
username: root
password:
これで、例えば以下のようなDSLがexample.schemaファイルに出力されます。
create_table "authors", collate: "utf8_general_ci", comment: "著者" do |t|
t.int "id", primary_key: true, extra: "auto_increment"
t.varchar "name", limit: 110, comment: "著者名"
t.datetime "created_at", null: true
t.datetime "updated_at", null: true
t.index "created_at", name: "index_authors_on_created_at"
end
create_table "papers", collate: "utf8_general_ci", comment: "論文" do |t|
t.int "id", primary_key: true, extra: "auto_increment"
t.varchar "title1", limit: 300, comment: "タイトル1"
t.varchar "title2", limit: 300, comment: "タイトル2"
t.text "description", null: true, comment: "概要"
end
create_table "paper_authors", collate: "utf8_general_ci", comment: "論文/著者関連" do |t|
t.int "id", primary_key: true, extra: "auto_increment"
t.int "paper_id", comment: "論文ID"
t.int "author_id", comment: "著者ID"
t.foreign_key "author_id", reference: "authors", reference_column: "id"
t.foreign_key "paper_id", reference: "papers", reference_column: "id"
end
DBの定義を変更したくなったら、example.schemaファイルを編集します。
例えば、papersテーブルにcreated_atカラムとupdated_at カラムを足してみます。
--- a/example.schema
+++ b/example.schema
@@ -12,6 +12,8 @@ create_table "papers", collate: "utf8_general_ci", comment: "論文" do |t|
t.varchar "title1", limit: 300, comment: "タイトル1"
t.varchar "title2", limit: 300, comment: "タイトル2"
t.text "description", null: true, comment: "概要"
+ t.datetime "created_at", null: true
+ t.datetime "updated_at", null: true
end
上記カラムを足した後に、以下コマンドでDryrunが可能です。
$ convergence -c database.yml -i example.schema --dryrun
# ALTER TABLE `papers` ADD COLUMN `created_at` datetime DEFAULT NULL AFTER `description`;
# ALTER TABLE `papers` ADD COLUMN `updated_at` datetime DEFAULT NULL AFTER `created_at`;
applyコマンドで、上記ALTER文をDBに対して発行することができます。
$ convergence -c database.yml -i example.schema --apply
SET FOREIGN_KEY_CHECKS=0;
--> 0.00072s
ALTER TABLE `papers` ADD COLUMN `created_at` datetime DEFAULT NULL AFTER `description`;
--> 0.040411s
ALTER TABLE `papers` ADD COLUMN `updated_at` datetime DEFAULT NULL AFTER `created_at`;
--> 0.02394s
SET FOREIGN_KEY_CHECKS=1;
--> 0.000158s
これでスキーマ変更も気軽に行えるようになりました。
ActiveRecordの標準DBマイグレーションを、開発初期フェーズで利用するのは辛いものです。
Ridgepole や Convergence 等のツールを利用することで、スキーマ変更にかかる手間を最小限に抑えることができます。
DBマイグレーションの運用に辛くなったら、これらのツールの導入を検討してみてはいかがでしょうか。