Go製DBスキーマ管理ツールのAtlasを触ってみた
はじめに
個人で開発をする際にDBのバージョン管理・マイグレーションに結構苦労したのもありGo製DBマイグレーションツールのAtlasを導入してみました。
使い勝手が良いツールで仕事でも使いたいなと思ったので、Quick Startの内容を追いかけながら特徴をまとめていきます。
Atlasとは
Databaseのスキーマを管理・変更・可視化するためのツールです。
HCLもしくはSQLを使用し理想状態のスキーマを作成することで、Atlasが現在との差分を比較し変更してくれます。
対応するデータベースの種類も多く、またCLIでの利用・CICDでの利用の両方が想定されているためローカル開発と本番運用の両方に適用することが出来ます。
また、Atlas Cloudというクラウドサービスを利用することで作成したスキーマを可視化することが出来ます。
ログインしなくても他の人が作成したスキーマを見ることが出来るので、ちょっと見てみると雰囲気が分かると思います。
なぜAtlasを選んだのか
上述した可視化が出来るところも良いなと感じたのですが、採用を決めたのは主に2つの理由からです。
1. マイグレーションファイルの管理がいらない
Atlasはスキーマの理想状態を宣言的に管理することが出来、バージョン間の差分を積み重ねるマイグレーションの方式を取りません。
そのため、マイグレーションファイル(バージョン間の差分のDDL)の管理を不要とし、管理が必要なのは理想状態のスキーマのみとなっています。
またマイグレーションファイルを用いたバージョン管理にも対応しているので、マイグレーションファイルを用いてバージョンという形で状態を管理することもできます。
このあたりはTerraformの思想に非常に近いと感じており、ライフサイクル的にはDBのスキーマはアプリケーションほど頻繁に更新されない特性からインフラリソースに近いという印象を持っていたためTerraform同様宣言的に管理することには納得感があります。
2. 特定の言語のランタイムに依存しない
PythonやJava等で作られたマイグレーションツールも見かけたのですが、実行のためにその言語のランタイムを必要とする場合があり、言語のバージョンとツールのバージョンを管理することに抵抗がありました。
AtlasはGo製のツールではあるものの、実行にGoを必要としません。JavaやPython等他の言語への依存を増やしたくなかったことからAtlasを選択しました。
またGoにも依存しないという特徴から、Go以外の言語でアプリを書いている人にとっても使い勝手が良いツールであると感じています。
クイックスタート
ここからはAtlas公式サイトのQuick Introductionを進めていきます。
1. Atlasのインストール
サイト記載のとおりAtlas CLIをインストールします。インストール方法はOSによって異なり、下記はLinuxでの例です。
curl -sSf https://atlasgo.sh | sh
Installation successful!
と出たらインストール成功です。atlas version
を使って正常にインストールできているか確認してください。
atlas version
正常にインストールされている場合、下記のような表示が出ます。
atlas version v0.20.1-a4257be-canary
https://github.com/ariga/atlas/releases/latest
2. テスト用のMySQLコンテナを起動し、テーブルを作成する
まずはatlas-demo
コンテナを起動します。
docker run --rm -d --name atlas-demo -p 3306:3306 \
-e MYSQL_ROOT_PASSWORD=pass -e MYSQL_DATABASE=example mysql:latest
次に、下記のSQLで定義されるusers
テーブルを作成します。
docker exec atlas-demo mysql -ppass \
-e 'CREATE table example.users(id int PRIMARY KEY, name varchar(100))'
上記docker exec
コマンドはdocker run
の実行後すぐ打つとエラーになるので、docker logs
で立ち上がったコンテナのログを見つつMySQLの初期化を少し待ってから実行してください。
docker logs atlas-demo
CREATE table users (
id int PRIMARY KEY,
name varchar(100)
);
正常に作成された場合、わりとあっさりしたメッセージが出るので心配な人は何度も打ってもOKです。
既に正常に実行されていると、下記のようにテーブルが存在するエラーが出ます。
mysql: [Warning] Using a password on the command line interface can be insecure.
ERROR 1050 (42S01) at line 1: Table 'users' already exists
3. スキーマの取込み
先ほどMySQLにて作成したusers
テーブルの内容をAtlasに取り込んでいきます。
atlas schema inspect -u "mysql://root:pass@localhost:3306/example" > schema_users.hcl
すると、取り込んだ結果のschema_users.hcl
が作成されます。
table "users" {
schema = schema.example
column "id" {
null = false
type = int
}
column "name" {
null = true
type = varchar(100)
}
primary_key {
columns = [column.id]
}
}
schema "example" {
charset = "utf8mb4"
collate = "utf8mb4_0900_ai_ci"
}
SQLで取り出すことも可能です。記事が長くなるので、興味がある方は↓をご参照ください。
SQLで実行する場合の方法はこちら
下記のコマンドを実行します。
atlas schema inspect -u "mysql://root:pass@localhost:3306/example" --format '{{ sql . }}' > schema.sql
するとSQLファイルが生成されます
-- Create "users" table
CREATE TABLE `users` (`id` int NOT NULL, `name` varchar(100) NULL, PRIMARY KEY (`id`)) CHARSET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
HCLファイルでインポートした際にはスキーマとして定義されていた文字コードの情報がSQLでインポートした場合にはテーブルに紐づく情報として取り出されているのはちょっと面白いなと感じます。MySQLのdumpファイル等を見る限り通常のSQLでスキーマレベルの文字コード定義は表現できないようで、それが原因なのかなと思います。
4. スキーマの変更
次にHCLファイル上に新しいテーブルblog_posts
の定義をを追加します。tableブロックを1つ追加することになりますが、どのスキーマを使っているのかわかりやすいという解説の都合上schema_users.hcl
をコピーしschema_users_and_blogposts.hcl
という別ファイルを作成し、そちらに追記します。
table "users" {
schema = schema.example
column "id" {
null = false
type = int
}
column "name" {
null = true
type = varchar(100)
}
primary_key {
columns = [column.id]
}
}
+table "blog_posts" {
+ schema = schema.example
+ column "id" {
+ null = false
+ type = int
+ }
+ column "title" {
+ null = true
+ type = varchar(100)
+ }
+ column "body" {
+ null = true
+ type = text
+ }
+ column "author_id" {
+ null = true
+ type = int
+ }
+ primary_key {
+ columns = [column.id]
+ }
+ foreign_key "author_fk" {
+ columns = [column.author_id]
+ ref_columns = [table.users.column.id]
+ }
+}
schema "example" {
charset = "utf8mb4"
collate = "utf8mb4_0900_ai_ci"
}
作成したschema_users_and_blogposts.hcl
をデータベースに適用していきます。atlas schema apply
コマンドを打つことで、AtlasがDBの状態と現在のスキーマファイルの内容を見比べて差分を埋めるためのSQLを作成してくれます。
atlas schema apply -u "mysql://root:pass@localhost:3306/example" --to file://schema_users_and_blogposts.hcl
実行結果を見るとblog_posts
を作成するためのSQLが表示されていることが分かります。FOREIGN KEYも適切に設定されています。
変更を適用するか聞かれますが、今回はApplyを選択し、マイグレーションを実行します。
-- Planned Changes:
-- Create "blog_posts" table
CREATE TABLE `blog_posts` (
`id` int NOT NULL,
`title` varchar(100) NULL,
`body` text NULL,
`author_id` int NULL,
PRIMARY KEY (`id`),
CONSTRAINT `author_fk` FOREIGN KEY (`author_id`) REFERENCES `users` (`id`)
);
? Are you sure?:
▸ Apply
Lint and edit (requires login)
Abort
HCLではなくSQLを使っている場合はCREATE TABLE文を1つ追加するのみです。下記にSQLを使ってスキーマを作成している場合の例も示しておきます。
SQLでスキーマを定義している場合はこちら
-- Create "users" table
CREATE TABLE `users` (`id` int NOT NULL, `name` varchar(100) NULL, PRIMARY KEY (`id`)) CHARSET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
+
+-- create "blog_posts" table
+CREATE TABLE `blog_posts` (
+ `id` int NOT NULL,
+ `title` varchar(100) NULL,
+ `body` text NULL,
+ `author_id` int NULL,
+ PRIMARY KEY (`id`),
+ CONSTRAINT `author_fk` FOREIGN KEY (`author_id`) REFERENCES `example`.`users` (`id`)
+);
HCLファイルの場合と同様なコマンドで実行します。
atlas schema apply -u "mysql://root:pass@localhost:3306/example" --to file://schema_users_and_blogposts.sql --dev-url "docker://mysql/8/example"
実行結果も同様なものとなります。
-- Planned Changes:
-- Create "blog_posts" table
CREATE TABLE `blog_posts` (
`id` int NOT NULL,
`title` varchar(100) NULL,
`body` text NULL,
`author_id` int NULL,
PRIMARY KEY (`id`),
INDEX `author_fk` (`author_id`),
CONSTRAINT `author_fk` FOREIGN KEY (`author_id`) REFERENCES `users` (`id`) ON UPDATE NO ACTION ON DELETE NO ACTION
) CHARSET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
? Are you sure?:
▸ Apply
Lint and edit (requires login)
Abort
ちなみに差分がないとこんなメッセージが出てコマンドが終了します。
Schema is synced, no changes to be made
4. スキーマの可視化
作成したスキーマをAtlas Cloudにアップロードして可視化します。
atlas schema inspect \
-u "mysql://root:pass@localhost:3306/example" \
--web
? Where would you like to share your schema visualization?:
▸ Publicly (gh.atlasgo.cloud)
Your personal workspace (requires 'atlas login')
非公開な形でパーソナルワークスペースにアップロードするためには、事前にサインアップした上でatlas login
コマンドを打ってログインしておく必要があります。今回は機密情報など含まれていないためPublicly
を選択し、パブリックに公開します。
公開したスキーマ:https://gh.atlasgo.cloud/explore/b2e21987
サインインするとprivateな形で公開も可能ですが、特に機能などに違いはなさそうでした。
5. バージョンを管理しながらマイグレーション
Atlasはマイグレーションファイルのバージョン管理を必要としませんが、migration用のSQLを作成しマイグレーションファイルのバージョン管理運用も可能です。
チュートリアルでやった流れの通り、usersテーブルのみのマイグレーションを作成してみます。
atlas migrate diff create_blog_posts --dir "file://migrations" --to "file://schema_users.hcl" --dev-url "docker://mysql/8/example"
migrations
配下にusers
テーブルのみを作成するためのSQLが作成されます。atlas migrate diff [出力ファイル名]
となっているようで、20240324052603_create_blog_posts.sql
というファイル名でマイグレーション用のSQLファイルが作成されました。
また、atlas.sum
というファイルも作成されました。このファイルはマイグレーションファイルを作成するために更新されるため、複数のブランチでマイグレーションを作成してしまった場合にもGitのマージのタイミングで検知できるようになっているとのことです。
作成されたSQL
-- Create "users" table
CREATE TABLE `users` (`id` int NOT NULL, `name` varchar(100) NULL, PRIMARY KEY (`id`)) CHARSET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
次に、blogpostsを含むスキーマからマイグレーションを作成します。
users
テーブルは既に定義されているため、差分となるblogposts
を作成するためのSQLのみが作成されることが分かります。
atlas migrate diff create_blog_posts --dir "file://migrations" --to "file://schema_users_and_blogposts.hcl" --dev-url "docker://mysql/8/example"
作成されたSQL
-- Create "blog_posts" table
CREATE TABLE `blog_posts` (`id` int NOT NULL, `title` varchar(100) NULL, `body` text NULL, `author_id` int NULL, PRIMARY KEY (`id`), INDEX `author_fk` (`author_id`), CONSTRAINT `author_fk` FOREIGN KEY (`author_id`) REFERENCES `users` (`id`) ON UPDATE NO ACTION ON DELETE NO ACTION) CHARSET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
その他試したこと
Getting Startedの内容は以上で終わりですが、他にもいくつか動作を確認しました。
6. 環境設定の保存
今までのチュートリアルでは毎回DBのURLとドライバの情報をコマンドに含めていましたが、atlas.hcl
という名前のファイルに環境設定を入れておくことで都度URL等を指定する必要がなくなります。
また、ファイル内で環境変数を指定し実行時に埋め込むことが可能です。
例えば上記のように環境設定を指定することで、環境変数DB_PASSを用いてパスコードを指定することが可能となります。
// https://atlasgo.io/atlas-schema/projects
env "local" {
// Declare where the schema definition resides.
// Also supported: ["file://multi.hcl", "file://schema.hcl"].
src = "file://schema_users_and_blogposts.hcl"
// Define the URL of the database which is managed
// in this environment.
url = "mysql://root:{DB_PASS}@localhost:3306/example"
// Define the URL of the Dev Database for this environment
// See: https://atlasgo.io/concepts/dev-database
dev = "docker://mysql/8/dev"
}
実行してみましょう。事前にexport DB_PASS=pass
にてパスワードを指定しておきます。
atlas
コマンドを実行する際には今までのように引数としてURL等は指定せず、atlas.hcl
で定義したlocal
環境を指定するのみです。
export DB_PASS=pass
atlas schema apply --env local
# -> Schema is synced, no changes to be made
上記の実行結果から、正常にスキーマとDBの状態が読み取れ比較されていることが分かります。
7. スキーマファイルの分割
大規模なプロジェクト等では1つのスキーマのなかにたくさんのテーブル定義が含まれることから、スキーマの定義ファイルを1つにすると非常に長くなってしまうことが想定されます。
先ほど作成した環境設定には複数のスキーマファイルを指定することが出来るため、環境設定にて指定しておくことでファイルの分割が可能です。例えば、authors.hcl
というファイルを作成し、これを先ほど作成した環境設定atlas.hcl
に追加します。
table "authors" {
schema = schema.example
column "id" {
null = false
type = int
}
column "name" {
null = false
type = varchar(100)
}
column "age" {
null = true
type = int
}
primary_key {
columns = [column.id]
}
}
// https://atlasgo.io/atlas-schema/projects
env "local" {
// Declare where the schema definition resides.
// Also supported: ["file://multi.hcl", "file://schema.hcl"].
- src = ["file://schema_users_and_blogposts.hcl"]
+ src = ["file://schema_users_and_blogposts.hcl", "file://schema_authors.hcl"]
// Define the URL of the database which is managed
// in this environment.
url = "mysql://root:pass@localhost:3306/example"
// Define the URL of the Dev Database for this environment
// See: https://atlasgo.io/concepts/dev-database
dev = "docker://mysql/8/dev"
}
この状態でatlas apply
してみるとと、作成したauthors.hcl
も認識さていることが分かります。
atlas schema apply --env local
-- Planned Changes:
-- Create "authors" table
CREATE TABLE `authors` (
`id` int NOT NULL,
`name` varchar(100) NOT NULL,
`age` int NULL,
PRIMARY KEY (`id`)
) CHARSET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
? Are you sure?:
▸ Apply
Lint and edit (requires login)
Abort
おわりに
Go製DBスキーマ管理ツールのAtlasを試してみました!
特定のプログラミング言語のランタイム言語に使いやすいツールなので、ぜひとも試してみてください!
今回試せなかった/試さなかったこと
時間の関係やドキュメントが見つからなかったことから自分では試せなかったことを記載しておきます。
良い記事があればリンク記載させていただきたい&試してみたいので教えてください。
1. CICDでAtlasを利用する
AtlasはCICDでのスキーマ管理も得意としています。詳しくはAtlas公式から記事が出ているのでこちらを参照してください。
2. Terraformでのスキーマ管理
Terraformのプロバイダーも提供されており、Terraformを用いてDBスキーマの管理が可能となっています。
試さなかったですがterraformを使ってスキーマを管理しつつ、local環境とdev環境で環境設定を切り替えるような使い方をする場合には少々設定が大変そうと感じました。特段制約が無ければterraform
ではなくatlas
コマンドを利用することも視野に入れ事前に比較検討すべきかと思います。
詳しくはこちらの記事が参考になりそうです。
4. 自作ドライバでのスキーマ管理
External Schemasという機能でGo等の言語のORMと連携できる様子です。
5. PostgreSQLにおけるDatabaseとSchemaの指定
今回はMySQLだったのでdatabase=schema(=今回はexampleを指定)のみを指定しました。
postgreSQLだとdatabaseのなかにschemaがあり、その中にtableがある構造となるはずなので、スキーマファイルやCLI上での指定がどう変わるか検証しておきたかったです。
特に、"コストを抑えつつローカル環境構築コストを抑えるためクラウド上にローカル開発用のDBインスタンスは1つ構築し、個人ごとにdatabaseを作成して使用する"といった使い方をする場合にはdatabaseの指定だけ切り替えてschema以下の指定は同じするようなモチベーションはあるはずです。
少なくとも環境設定atlas.hcl
のなかで環境変数を使ってデータベースのみを切り替えつつ各スキーマ、テーブルの定義をPostgreSQLに反映するようなことはできるのではないかと感じるので、上記ユースケースにおいてもAtlasを利用することはできそうと感じます。
Discussion