この記事はカヤックアドベントカレンダー19日目の記事です。
はじめに
こんにちは @Konboi です。
皆さん忘年してますか? 私は今のところ順調に忘年できており、今年何をしてたのか大分忘れました。
ちなみに先日行われたカヤック技術部の忘年会の様子です。
肉がきたぞー! pic.twitter.com/w4VIdmMNmO
— Masatoshi Kawazoe (@acidlemon) 2016年12月16日
忘年してます pic.twitter.com/T7EW0Z5ZVm
— jigya♧kkuma (@jigyakkuma_) 2016年12月16日
ということで表題の通り、今日の記事ではカヤックでのこれからのGoプロジェクトでのmigrationについて紹介しようと思います。
本記事であつかう migrationはDB更新を差し
- 新規テーブルの作成
- テーブル/カラム名の変更
- テーブルへのカラムの追加/削除
- テーブルへのindexの追加/削除
などがあげられます。
TL;TR
migrationの方法
migrationの方法については大きく分けて3つあると思います (細かい所ではもっと分かれるとは思います。 異論は認めます)
- 更新分をSQLファイルで追加していく方法
- Rails like な DSLを記述し差分をファイルとして追加していく方法
- 現在のDBの状況と定義ファイルの差分を適用する方法
更新分をSQLファイルで追加していく方法
- ベースとなるSQLファイル (サービスリリース時点のSQL) が存在する
- DB更新時には更新分のSQLを作成し、 ベースファイルに更新を適用する
- 本番ヘは、更新分のSQLのみ適用する
- 新しく入ってきたメンバーはベースファイルを適用し、更新分を範囲していく
メリット
- 更新分がわかりやすい
- 書かれたSQLが適用されるのでレビューがし易い
- migration実行時に別途ツールが必要ない
デメリット
- 管理がベースファイルと新規ファイル両方に適用しないと行けないので煩雑
※ 個人の意見です
rails likeなmigration方式
- 基本的な方針はSQLを追加してく方式と同じ
- SQLファイルを追加していくのではなくDSLで定義されたファイルを追記していく
- 定義ファイルを作成/編集 (
rails g migration <Name>
) - 定義ファイルを反映 (
rails db:migrate
)
メリット
- アプリケーション開発と同じ言語で書ける
- 反映分を切り戻す戻すことも可能 (
rails db:rollback
)
デメリット
- DSLを覚える必要がある
- 複数人で開発しているとコンフリクトする
- どのようなSQLが吐かれるかをきちんと確認する必要がある
※ 個人の意見です
現在のDBの状況と定義ファイルの差分を適用する方法
カヤックのゲーム事業部ではこの方法でmigrationを行っています。
GitDDLというツールを使用しています。
- その名の通りgitで管理されていることが前提
- 反映時のコミットハッシュをdatabase上のテーブルで管理
- 現在のschemaファイルとdbに保存されているコミットハッシュ時のschemaの状態から差分を生成
- 差分生成はSQL::Translator::Diff を使用
- 差分を適用し、DBに保存されているコミットハッシュを更新
GitDDL以外で似たようなツールには Anego があげらると思います。
Perl以外ではridgepole などがあります。
メリット
- 管理がし易い
- コンフリクトが起きにくい
デメリット
- 差分のSQLを確認する必要がある
- 定義ファイルを何かしらの方法で管理する必要がある
- どのようなSQLが吐かれるかをきちんと確認する必要がある
Goでのmigrationの話
長々と既存のmigrationの話をしましたが、Goの話をします。
Go製マイグレーションツールの現状確認 や Githubで調べてみるとGoではrails like なDBのmigrationが多いように見受けられます。
しかし、僕たちはGitDDLを使った運用が長くその形での運用ノウハウが多く溜まっているので、GoのプロジェクトでもGitDDLの様な差分を元にmigrationを実行したいと考えていました。
そこで目をつけたのが schemalex です
schemalexはREADME.mdを見てもらえるとわかりますが、 SQL::Translator::Diff のように2つのSchemaの差分からDiffを生成してくれるツールです。
ある程度複雑な差分を与えても問題なく動いています。
GoのstructからSQLを自動生成する
既存のPerlプロジェクトではGitDDLで使用するSchemaファイルを自動生成*1しています
Goのプロジェクトでも適用するSQLファイルを自動生成したい要望があがりました。
そこで作ったのが ddl-maker です。
ddl-makerはGoのstructからSQLを生成してくれます。 詳しい使い方は README.md や example をみていただければと思いますが簡単な概要を
type User struct { Id uint64 Name string CreatedAt time.Time UpdatedAt time.Time } func (u User) PrimaryKey() dialect.PrimaryKey { return mysql.AddPrimaryKey("id") }
このような struct を ddl-maker を介すと以下の様なSQLを生成することができます
SET foreign_key_checks=0; DROP TABLE IF EXISTS `user`; CREATE TABLE `user` ( `id` BIGINT unsigned NOT NULL, `name` VARCHAR(191) NOT NULL, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARACTER SET utf8mb4; SET foreign_key_checks=1;
以下の様なメソッドを定義してあげることでテーブル名の指定しての生成ややインデックスやプライマリーキーも生成することができます。
tagではなく、メソッドにしているのはPrimaryKeyなどはパーティションを切るときなどきちんと順番を指定したい場合があるためです
func (u User) Table() string { return "player" } func (u User) Indexes() dialect.Indexes { return dialect.Indexes{ mysql.AddIndex("name_idx", "name"), } }
CREATE TABLE `player` ( `id` BIGINT unsigned NOT NULL, `name` VARCHAR(191) NOT NULL, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL, INDEX `name_idx` (`name`), PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARACTER SET utf8mb4;
これで、Goの開発においては
- struct を定義する
- ddl-makerでeructからSQLを生成
- schemalexでDBに反映
という流れができました。
最後に
各社migrationについて色々と思うところはあると思います。 今回の記事が少しでも参考にされば幸いです。
明日は 今年のISUCON6で準優勝したmorimoto組組長こと@jinnです
きっと凄いのがくると思います!! お楽しみに!!!
おまけ
カヤックでは美味しいビールと美味しいお肉で忘年したいと人を募集しています
入社はしないけど、ビールとお肉片手にカヤック社員とGo談義したい人も募集しております。 話したい/飲みたいというかたは @Konboi まで気軽にmention下さい!
- 作者: 松木雅幸,mattn,藤原俊一郎,中島大一,牧大輔,鈴木健太,稲葉貴洋
- 出版社/メーカー: 技術評論社
- 発売日: 2016/09/09
- メディア: 大型本
- この商品を含むブログ (3件) を見る
*1:DBIx::Class + GitDDLでなんかそれっぽいSQL出す。 http://hisaichi5518.hatenablog.jp/entry/2013/08/12/182356