schemalex による DB のスキーマ管理
Adventarを支える技術 Advent Calendar 2019 の15日目です。
今日は DB のスキーマ管理について書きます。
Rails の DB マイグレーションと Ridgepole
Adventar は昨年まで Rails で作っていて、DB の マイグレーションも Rails デフォルトの機能を使っていました。Rails の DB マイグレーションは、それなりによくできてはいますが、差分を積み上げていくので大量のマイグレーションファイルができて煩雑になる、多人数での開発の場合にコンフリクトしやすいなど、いくつか問題があります。
個人的にはこういった問題もあるので、Ridgepole のように DB のスキーマ定義だけを管理し、現在のスキーマとの差分を計算して ALTER 文を発行してくれるような仕組みのほうが好きです。
最初は Ridgepole を使おうと思ったのですが、API サーバーは Go で書こうと思っていたので、スキーマ管理のためだけに Ruby 依存を入れるのは微妙かな、と思い他のツールを検討しました。
schemalex と sqldef
Go 製の Ridgepole 的なツールは
の2つがあるのを知っていたので、どちらかを使うことにしました。これらのツールは、Ridgepole と違って、Ruby の DSL でなく、SQL でスキーマを管理できる点、Ruby のランタイムなどが不要でバイナリ単体で実行できて便利です。
ほとんど機能の違いはなのですが、大きい違いは schemalex が MySQL だけをサポートしているのにたいして、sqldef は PostgreSQL もサポートしています。今回は MySQL なのでどちらでもよかったのですが、schemalex のほうが歴史が深く、どちらかといえば安定していそうだったので schemalex を選びました。
ちなみに、偶然にも Ridgepole, schemalex, sqldef は全部同僚 or 元同僚が作っています。
schemalex による DB のマイグレーション
スキーマの管理には、以下のような普通の SQL の DDL を使います。
CREATE TABLE `users` ( `id` int unsigned NOT NULL AUTO_INCREMENT, `name` varchar(255) NOT NULL, `auth_uid` varchar(255) NOT NULL, `auth_provider` varchar(20) NOT NULL, `icon_url` text NOT NULL, `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, `updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `index_users_on_uid_and_provider` (`auth_uid`,`auth_provider`) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
実際の Adventar のスキーマは以下のような感じです。
https://github.com/adventar/adventar/blob/619f222b9348e1cbfcfe50cc731fb8184e84ab2d/db/schema.sql
これはただの SQL なので、schemalex を使わずとも、初回のテーブル作成はできます。
$ mysql -u root adventar_dev < schema.sql
ここで新しくカラムを追加したい場合、例えば以下のような変更を加えます。
--- a/db/schema.sql +++ b/db/schema.sql @@ -1,6 +1,7 @@ CREATE TABLE `users` ( `id` int unsigned NOT NULL AUTO_INCREMENT, `name` varchar(255) NOT NULL, + `description` text NOT NULL, `auth_uid` varchar(255) NOT NULL, `auth_provider` varchar(20) NOT NULL, `icon_url` text NOT NULL,
schemalex を使うと、既存の DB のスキーマと上記 SQL の diff を計算して ALTER 文を作ってくれます。
$ schemalex 'mysql://root@tcp(127.0.0.1:13306)/adventar_dev' schema.sql BEGIN; SET FOREIGN_KEY_CHECKS = 0; ALTER TABLE `users` ADD COLUMN `description` TEXT NOT NULL AFTER `name`; SET FOREIGN_KEY_CHECKS = 1; COMMIT;
schemalex がやってくれるのは ALTER 文の生成だけなので、MySQL に食わせるのは自分でやります。
$ schemalex 'mysql://root@tcp(127.0.0.1:13306)/adventar_dev' | mysql -u root adventar_dev
実際の運用
複数人で運用するときのフローや、デプロイ時に適用場合のフローなども紹介できればよかったのですが、Adventar はほぼ一人で開発しているし、プロとして恥ずべき行為ではありますが、デプロイも手元から手動でやっているので、特に運用について言及できることはありませんでした(つまり手動 ALTER でも十分そうということですね...!)