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 と違って、RubyDSL でなく、SQLスキーマを管理できる点、Ruby のランタイムなどが不要でバイナリ単体で実行できて便利です。

ほとんど機能の違いはなのですが、大きい違いは schemalex が MySQL だけをサポートしているのにたいして、sqldef は PostgreSQL もサポートしています。今回は MySQL なのでどちらでもよかったのですが、schemalex のほうが歴史が深く、どちらかといえば安定していそうだったので schemalex を選びました。

ちなみに、偶然にも Ridgepole, schemalex, sqldef は全部同僚 or 元同僚が作っています。

schemalex による DB のマイグレーション

スキーマの管理には、以下のような普通の SQLDDL を使います。

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 でも十分そうということですね...!)

まとめ

DB のスキーマ管理とマイグレーションについて書きました。明日は Adventar のインフラ構成について書きます。