どうもこんばんわ、日々お世話になったり、便利だと感じたOSSを紹介していく、OSS紹介 Advent Calenderの21日目の記事です。
GitDDLとは
- あるDBスキーマから別のDBスキーマに変更を行う際(=migration)を行う時に使うCPANモジュール
- Railsなどのmigrationとは違ったアプローチで行う
- 中に使われている魔法のようなモジュールSQL::Translator::Diffの紹介もします
DB migrationとは
MySQLなどのRDBMSを用いたアプリケーションを運用していくと、「新しくテーブルを追加したい」「カラムを増やしたい」「インデックスを足したい」などの場面に遭遇することがあります。テーブルを追加するだけであれば、新しくCREATE
文を発行するだけで良いのですが、カラムを増やしたいだとかインデックスを足したいとなると、ALTER
文を打たなければなりません。
ところで、普段ORMを使っていると、SQLからの距離が遠くなっていくのを感じます(個人差があります)。SELECT
などのDML文はORMに精通していれば、このステートメントを実行すると裏でどういったSQLが発行されるかを想像することが出来ますし、そうでなくてもログをとったり、中には特定ページ内で発行されたSQLが下ににょろっと出て来るフレームワークもありますよね? そうやって我々はSQLとの距離をつなぎとめることが出来るのです。
しかし、CREATE
文やALTER
文などのDDLとなると話は違ってきます。早々頻繁に打たないものですから、彼らとの距離はどんどん離れていきます。何故離れていくか。大体のフレームワークは彼らを勝手に手なづけてこちらに寄越してくれないからです。
このフレームワークやORMが持つDDLを自動で発行する仕組みのことを全般的にmigrationと呼びます(呼ばれている気がします)。
ほとんどのORMはDBスキーマを知っています。でなければスムーズなObject-Relational Mappingは実現できないからです。DBスキーマを知っているということは、彼らはスキーマの管理を自分の手元に置きたいと思うでしょう。自分の管理外で勝手にカラム追加なんかされてしまっては、とたんにパニクってしまうからです。
重厚なORMであればあるほど、スキーマはDSL上で管理されているように思えます。PerlであればDBIx::Schema::DSLが挙げられます。これはDSLからDDLを吐く単体のモジュールですが、これをORMであるAnikiが利用しています。DBIx::ClassにもそういったDSLが存在します。
ところで実際に彼らのMigrationはどのように行われているでしょうか。ここで一例としてRails migrationを紹介します。
Rails migrationのDDL管理はいわば積み上げ式です。基本のCREATE文を発行するcreate_table
があります。あなたがもし何か変更を加えたい時、例えばカラムを追加したい時には、ファイルを作って独自のDSLでadd_column :table_name, :column_name, :text, after: :before_column_name
と書きます。これを元にrails migration
コマンドはALTER文を生成してあなたのアプリケーションのデータベースに適用するのです。
GitDDLのmigration方法
僕が見てきた限りほとんどの変更時のDDL文を生成できるライブラリやORMは、この積み上げ方式でしたが、GitDDLはこの方式とはまったく違う方法を取ります。
GitDDLは現在適用されている完全な形のCREATE
文と、適用したい今のテーブルの形を示したCREATE
文から直接ALTER
文を生成します。これは2つのCREATE
文同士の差分をALTER
文として出力するとも言えます。
GitDDLの仕組みを順を追って説明すると、
- 適用先DBの中の
git_ddl_version
からコミットハッシュを取り出し、gitコマンドを実行してそのリビジョン時のDDL(完全な形のCREATE
文が連なった形式)を取り出します - 適用後の形を示した完全な形の
CREATE
文が入ったDDLを読み取ります - この2つを
SQL::Translator::Diff
に投げます - すると
ALTER
文になって帰ってきます - これをDBに対して適用します
- 現在のgitのコミットハッシュを
git_ddl_version
に書き込みます
まあつまり、SQL::Translator::Diff
が魔法のようなことをしているわけです。
SQL::Translator::Diffの中身
どうやってそれやるのかというと、実際にDDLをパースして、ASTに変換し、2つのASTの間の差分をとりだして、ALTER文に戻すというのをやっています。文字にすると簡単ですが、途中で追っかけるのを諦めるぐらい結構大変なことになっています。
r7kamuraさんが書かれたSQL::Translator::Diffを読んだ記事があったのですが、失われていました……。
GitDDL/SQL::Translator::Diffの欠点
で、これで僕は4年ぐらいWebアプリを運用しているんですが、ありがたく使わせていただいた上で色々欠点があります。
- テーブルが多くなると差分を計算するのにかなり時間がかかる
- だいたい現在400ぐらいテーブルがあって、スキーマ適用に5分か10分ぐらいかかっている。これはインデックスを貼る時間とかではなくDDLを計算して出す時間がほとんど
- 本番でやべってなって、手でインデックスを貼ったりすると、GitDDLが知っているDDLとの差分が出てしまって戻すのに手こずる
それで?
で、ここまで書いてアレなんですが、OSS紹介 Advent Calendarの5日目に書かれた、schemalexが思想を引き継いだ上で欠点が解消されている(と思われる)ので、今僕の中で移行計画を練ってます。とはいえ、GitDDLはかなり実績のあるものですし、あれだけ多いテーブル数で変なDDLを吐くとか(カラムのリネームはちょっと厳しいんですけれど)は無いので、これからもある程度の規模のアプリには使えると思われます。