Upgrade to Pro — share decks privately, control downloads, hide ads and more …

ariga/atlas の内部処理追ってみた

Avatar for mrymam mrymam
July 01, 2025
370

ariga/atlas の内部処理追ってみた

CA.go#16 での発表内容です

Avatar for mrymam

mrymam

July 01, 2025
Tweet

Transcript

  1. ariga/atlasとは データベーススキーマ管理ツール 宣言的なスキーマ定義(HCL、SQL、ORMスキーマ) entやgormなどのORMのスキーマ定義を使える 自動的な差分計算とマイグレーション生成 マスターのスキーマ定義を宣言的に記述 table "users" { schema

    = schema.main column "id" { null = false type = int auto_increment = true } // 編集箇所 ここから column "name" { null = false type = varchar(255) } // 編集箇所 ここまで } ↓ 差分DDLが生成される ALTER TABLE `users` ADD COLUMN `name` varchar(255) NOT NULL; CA.go#16 2025.07.01 4 / 22
  2. atlasのスキーママイグレーションの流れ 1. スキーマ定義ファイルを更新 2. atlas migrate diff で差分マイグレーションを生成 i. migrations/

    にDDLファイルが生成される 3. atlas migrate apply でマイグレーションを適用 i. migrations/ のDDLのうち、未適用のDDLがDB に適用される myproject/ ├── atlas.sum # チェックサムファイル ├── schema.hcl # スキーマ定義 └── migrations/ ├── 20231201120000_create_users_table.sql # 初回マイグレーション └── 20231201130000_add_users_timestamps.sql # 差分マイグレーション 嬉しいこと DDLファイルが migrations/ に生成されるため マイグレーションの履歴が残る 実行前に明確に実行されるDDLが確認できる ORM(entなど)のマイグレーション機能では、差分DDLを生成してくれない ことが多い CA.go#16 2025.07.01 5 / 22
  3. Usersテーブルを作成 スキーマを書く (schema.hcl) table "users" { schema = schema.main column

    "id" { null = false type = int auto_increment = true } column "name" { null = false type = varchar(255) } column "email" { null = false type = varchar(255) } primary_key { columns = [column.id] } index "idx_email" { columns = [column.email] unique = true } } atlas migrate diff でDDLを生成 ディレクトリ myproject/ ├── atlas.sum # チェックサムファイル ├── schema.hcl # スキーマ定義 └── migrations/ └── 20231201120000_create_users_table.sql # 初回マイグレーション migrations/20231201120000_create_users_table.sql -- create "users" table CREATE TABLE `users` ( `id` int NOT NULL AUTO_INCREMENT, `name` varchar(255) NOT NULL, `email` varchar(255) NOT NULL, PRIMARY KEY (`id`), UNIQUE INDEX `idx_email` (`email`) ) CHARSET utf8mb4 COLLATE utf8mb4_0900_ai_ci; CA.go#16 2025.07.01 7 / 22
  4. Usersテーブルにカラムを追加 スキーマを更新 (schema.hcl) table "users" { # ... 既存の定義 ...

    column "created_at" { null = false type = timestamp default = sql("CURRENT_TIMESTAMP") } column "updated_at" { null = false type = timestamp default = sql("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP") } } 差分マイグレーションの生成 ディレクトリ myproject/ ├── atlas.sum # チェックサムファイル ├── schema.hcl # スキーマ定義 └── migrations/ ├── 20231201120000_create_users_table.sql # 初回マイグレーション └── 20231201130000_add_users_timestamps.sql # 差分マイグレーション migrations/20231201130000_add_users_timestamps.sql -- modify "users" table ALTER TABLE `users` ADD COLUMN `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP; ALTER TABLE `users` ADD COLUMN `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP; CA.go#16 2025.07.01 8 / 22
  5. マイグレーションの適用 DBへの適用 migration/ にあるマイグレーションファイルをデータベースに適用 atlas migrate apply \ --url "mysql://user:pass@db:3306/myapp"

    \ --dir file://migrations 実行ログ: Migrating to version 20231201130000 (2 migrations to apply): -> 20231201120000_create_users_table.sql -> 20231201130000_add_users_timestamps.sql CA.go#16 2025.07.01 9 / 22
  6. (おさらい) atlasのマイグレーションフロー 1. schema.hcl を更新して、Usersテーブルを追加 2. 差分マイグレーションの生成 atlas migrate diff

    1. schema.hcl を更新して、Usersテーブルにカラムを追加 2. 差分マイグレーションの生成 atlas migrate diff 3. マイグレーションの適用 atlas migrate apply atlas migrate diff は、どうやって差分を計算しているか CA.go#16 2025.07.01 11 / 22
  7. Q. atlas migrate diff は、どうやって差分を計算しているか A. スキーマ定義ファイル(schema.hcl)と、migrations/ のDDLを比較している myproject/ ├──

    atlas.sum # チェックサムファイル ├── schema.hcl # スキーマ定義 └── migrations/ ├── 20231201120000_create_users_table.sql # 初回マイグレーション └── 20231201130000_add_posts_and_timestamps.sql # 差分マイグレーション (← 20231201120000_create_users_table.sqlと schema.hclの差分) 目的状態 table "users" { # ... 既存の定義 ... column "created_at" { null = false type = timestamp default = sql("CURRENT_TIMESTAMP") } column "updated_at" { null = false type = timestamp default = sql("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP") } } 現在状態 migrations/20231201120000_create_users_table.sql -- create "users" table CREATE TABLE `users` ( `id` int NOT NULL AUTO_INCREMENT, `name` varchar(255) NOT NULL, `email` varchar(255) NOT NULL, PRIMARY KEY (`id`), UNIQUE INDEX `idx_email` (`email`) ) CHARSET utf8mb4 COLLATE utf8mb4_0900_ai_ci; CA.go#16 2025.07.01 12 / 22
  8. 比較例として、sqldefをご紹介 sqldefは、SQLのスキーマ定義を元に、宣言的にデータベースのマイグレーションを行うツール sqldefのスキーママイグレーションの流れ 1. スキーマ定義を記述 (SQLファイル) 2. マイグレーションコマンド実行時に、データベースの状態を差分比較して、実行 目的状態 CREATE

    TABLE `users` ( `id` int NOT NULL AUTO_INCREMENT, `name` varchar(255) NOT NULL, `email` varchar(255) NOT NULL, PRIMARY KEY (`id`), UNIQUE INDEX `idx_email` (`email`), CREATED AT timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, UPDATED AT timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) CHARSET utf8mb4 COLLATE utf8mb4_0900_ai_ci; 現在状態 データベースのスキーマ状態 SELECT table_schema, table_name, column_name, data_type, column_default FROM information_schema.columns WHERE table_schema = 'public'; CA.go#16 2025.07.01 13 / 22
  9. 目的状態取得 = スキーマ定義の解析 スキーマ定義を直接パースして読みこむ → 共通のスキーマ定義構造体 schema.Realm を生成する [HCLファイル] →

    [パース] → [スキーマ定義構造体を生成] スキーマ定義 table "users" { column "id" { type = int auto_increment = true } } スキーマ構造体 hoge := &schema.Schema{ Name: "public", Realm: &r, Tables: []*schema.Table{ { Name: "users", Columns: []*schema.Column{ { Name: "id", }, }, }, }, } CA.go#16 2025.07.01 15 / 22
  10. 現在状態取得 = マイグレーションファイルの再生 マイグレーションファイルから現在スキーマを生成するのは難しい → DBに適用して、DBから状態を取得 [migrations/のDDL] → [開発DB適用] →

    [DBの状態を取得] → [スキーマ定義構造体を生成] マイグレーション再生処理 // sql/migrate/migrate.go func (e *Executor) Replay(ctx context.Context, r StateReader, opts ...ReplayOption) (*schema.Realm, error) { // 1. マイグレーションを順次実行 err = e.ExecuteN(ctx, 0) // 全マイグレーション実行 // 2. 実行後のDBスキーマ状態を検査 return r.ReadState(ctx) } データベース検査(PostgreSQL例) -- テーブル情報取得 SELECT table_schema, table_name, table_type FROM information_schema.tables CA.go#16 2025.07.01 16 / 22
  11. atlas_schema_revisions テーブル atlas.sum マイグレーションファイルに対して、ハッシュが割り振られている。 h1:K7kM9eH8L2vR3pQ4xN5wY6zA7bB8cC9dD0eE1fF2gG3= 20231201120000_create_users_table.sql h1:K7kM9eH8L2vR3pQ4xN5wY6zA7bB8cC9dD0eE1fF2gG3= h1:2fd4e1c67a2d28fced849ee1bb76e7391bfsf93eb12= 20231201130000_add_users_timestamps.sql h1:2fd4e1c67a2d28fced849ee1bb76e7391bfsf93eb12=

    atlas_schema_revisions テーブル どのハッシュのマイグレーションが適用されたかを atlas_schema_revisions テーブルに記 func (r *RevisionReadWriter) ReadRevisions(ctx context.Context) ([]*Revision, error) { rows, err := r.conn.QueryContext(ctx, ` SELECT version, description, type, applied, total, executed_at, execution_time, error, error_stmt, hash, partial_hashes, operator_version FROM atlas_schema_revisions ORDER BY version `) // 結果をRevision構造体に変換 return revisions, nil } CA.go#16 2025.07.01 20 / 22