Atlas (Ariga): コードとしてのデータベーススキーマ管理とマイグレーション自動化

Database tutorial - IT technology blog
Database tutorial - IT technology blog

かつてstagingとproductionの間でSQLマイグレーションファイルを一つひとつ手動で比較して「何が足りないか」を探したことがあるなら、その苦痛はよく分かるだろう。自分も何度もこの経験をした——特に複雑なスキーマを持つPostgreSQLプロジェクトで、ちゃんとした管理ツールなしにチーム全員がマイグレーションをコミットしていた時のことだ。

ArigaのAtlasはまさにその問題を解決するために生まれた:データベーススキーマをコードとして管理(Schema as Code / IaC)し、バージョン管理でき、自動化でき、どの環境でも一貫した結果が得られる。

3つのスキーマ管理方法:実践比較

Atlasの違いをはっきり理解するために、まず3つの一般的なアプローチを見ておこう:

1. 手動マイグレーションファイル(SQL/ORM)

このアプローチが最も一般的だ——Flyway、Liquibase、またはORMマイグレーション(Alembic、Django migrations、Rails ActiveRecord)を使う。SQLファイルを順番に書いてgitにコミットし、デプロイ時にmigrateを実行する。

  • メリット:馴染みやすく、ドキュメントが豊富で、ORMとの統合が標準装備
  • デメリット:マイグレーションファイルが時間とともに積み重なり、現在のスキーマの監査が難しく、複数人で作業する際にconflictが起きやすく、コードと実際のDB間のdriftを自動検出できない

2. 宣言的スキーマ(望ましい状態を記述する)

「何をするか」(ALTER TABLE…)を書く代わりに、「何が欲しいか」——最終的なスキーマがどうあるべきかを記述する。ツールが自動的にdiffを計算してマイグレーションを生成する。TerraformがインフラでこれをやっているようにAtlasも同じ思想をデータベースに適用する。

  • メリット:スキーマが常にsource of truth、読みやすく、レビューしやすく、driftが発生しない
  • デメリット:ツールの理解が必要で、一部のエッジケースは手動対応が必要

3. Atlas:ハイブリッドアプローチ——両方のいいとこ取り

Atlasは両方のモードをサポートしている:declarative(schema.sql/HCLで最終状態を記述)とversioned migration(監査可能な.sqlファイルを生成)。プロジェクトのステージに合わせてどちらかを選ぶか、両方を組み合わせることができる。

長所・短所の分析:Atlas vs Flyway/Liquibase

比較項目 Flyway / Liquibase Atlas
Schema drift detection なし あり(atlas schema diff
Declarative mode なし あり
CI/CD integration 普通 優秀(GitHub Actions ネイティブ)
Lint migration なし あり(atlas migrate lint
Multi-database あり(多数) PG、MySQL、SQLite、MS SQL
スキーマフォーマット SQL / XML / YAML HCL + SQL

Atlasで一番気に入っているのはatlas migrate lintだ——apply前にマイグレーションを分析し、破壊的な操作(DROP TABLE、DROP COLUMN)やforeign keyにindexが欠如している場合に警告してくれる。Flywayにはこの機能がなく、誰もちゃんとレビューしなかったせいでstagingでDROP COLUMNをミスしたことが一度あった。

どんな時にAtlasを選ぶべきか?

いくつかのPostgreSQL productionプロジェクトでAtlasを使った経験から、このツールが最も適しているのは以下の場合だと思う:

  • 3人以上のチームが同時にスキーマを変更していて、マイグレーションのレビューと承認が必要な場合
  • 複数の環境(dev/staging/prod)があり、スキーマの一貫性を保つ必要がある場合
  • 問題がある時にマージをブロックするためにCI/CDパイプラインにマイグレーションチェックを組み込みたい場合
  • 比較的複雑なスキーマを持つPostgreSQLまたはMySQLを使っているプロジェクト

逆に、Django/Railsでソロの小規模プロジェクトをやっているなら、フレームワークのORMマイグレーションで十分だ——over-engineerする必要はない。

Atlasのインストール

Atlasはシングルバイナリで、ランタイムの依存関係は不要だ:

# macOS
brew install ariga/tap/atlas

# Linux(ワンライナー)
curl -sSf https://atlasgo.sh | sh

# バージョン確認
atlas version

実践的なデプロイ:PostgreSQLでのバージョン管理マイグレーション

以下のワークフローはproductionで実際に使っているものだ。すべてのデモはPostgreSQL 15を使用。

ステップ1:プロジェクト構造を作成する

mkdir myapp-db && cd myapp-db
mkdir -p migrations

ステップ2:schema.sql(source of truth)を作成する

-- schema.sql: データベースの「あるべき状態」
CREATE TABLE users (
  id          BIGSERIAL PRIMARY KEY,
  email       VARCHAR(255) NOT NULL UNIQUE,
  username    VARCHAR(100) NOT NULL,
  created_at  TIMESTAMPTZ  NOT NULL DEFAULT NOW()
);

CREATE TABLE posts (
  id          BIGSERIAL PRIMARY KEY,
  user_id     BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
  title       VARCHAR(500) NOT NULL,
  body        TEXT,
  published   BOOLEAN NOT NULL DEFAULT FALSE,
  created_at  TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE INDEX idx_posts_user_id ON posts(user_id);
CREATE INDEX idx_posts_published ON posts(published) WHERE published = TRUE;

ステップ3:最初のマイグレーションファイルを生成する

atlas migrate diff initial_schema \
  --dir "file://migrations" \
  --to "file://schema.sql" \
  --dev-url "docker://postgres/15/dev"

フラグ--dev-url "docker://postgres/15/dev"はかなり便利だ——Atlasが自動的に一時的なPostgreSQLコンテナを起動してdiffを計算してくれるので、ローカルにPostgresをインストールしておく必要がない。実行後:

migrations/
  20260521000001_initial_schema.sql
  atlas.sum   # checksumファイル——マイグレーションと一緒にコミットする

atlas.sumファイルは重要だ:コミット済みのマイグレーションを誰かが編集した場合に検出するためにAtlasが使用する(go.sumと同様の仕組み)。必ずこのファイルをgitにコミットすること。

ステップ4:実際のデータベースにマイグレーションを適用する

atlas migrate apply \
  --dir "file://migrations" \
  --url "postgres://user:pass@localhost:5432/myapp?sslmode=disable"

# 出力:
# Migrating to version 20260521000001 (1 migration in total):
#   -- migrating version 20260521000001
#     -> CREATE TABLE users ...
#     -> CREATE TABLE posts ...
#   -- ok (23ms)

ステップ5:新しいカラムの追加——スキーマ変更のワークフロー

プロダクトからusersテーブルにbioカラムを追加する要件が来た。ALTER TABLEを手書きする代わりに、schema.sqlを修正するだけでいい:

-- usersテーブルの定義に追加:
  bio  TEXT,

そして再びmigrate diffを実行する:

atlas migrate diff add_user_bio \
  --dir "file://migrations" \
  --to "file://schema.sql" \
  --dev-url "docker://postgres/15/dev"

# Atlasが自動生成:
# ALTER TABLE "users" ADD COLUMN "bio" text;

実践Tips

1. PRをマージする前にmigrate lintを使う

atlas migrate lint \
  --dir "file://migrations" \
  --dev-url "docker://postgres/15/dev" \
  --latest 1

# Atlasがdestructiveな変更を警告:
# L1: Dropping non-virtual column "email" (MF103)
# L2: Adding a non-nullable column "phone" to existing table (MF103)

destructiveな変更が十分にレビューされていない場合にマージをブロックするために、これをGitHub Actionsに統合する:

# .github/workflows/atlas-lint.yml
name: Atlas Migration Lint
on:
  pull_request:
    paths: ['migrations/**', 'schema.sql']

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: ariga/setup-atlas@v0
      - name: Lint migrations
        uses: ariga/atlas-action/migrate/lint@v1
        with:
          dir: 'file://migrations'
          dev-url: 'docker://postgres/15/dev'

2. スキーマコードと実際のDB間のdriftを検出する

# DBの現在のスキーマをschema.sqlと比較
atlas schema diff \
  --from "postgres://user:pass@prod-host:5432/myapp?sslmode=require" \
  --to "file://schema.sql"

# driftがある場合、DBがコードからどこで乖離しているかを出力で明確に表示

Flywayにはこの機能がない。誰かがproductionで直接ALTER TABLEをやっていないか早期に検出するためにCIで定期的にこれを実行している——特に深夜2時のホットフィックスの時など、これは思っているよりもよく起きることだ。

3. atlas.hclを使ってコマンドを簡潔にする

# atlas.hcl
env "dev" {
  src = "file://schema.sql"
  url = "postgres://user:pass@localhost:5432/myapp?sslmode=disable"
  dev = "docker://postgres/15/dev"
  migration {
    dir = "file://migrations"
  }
}

env "prod" {
  url = getenv("DATABASE_URL")
  migration {
    dir = "file://migrations"
  }
}

このファイルにより、コマンドが大幅に短くなる:

atlas migrate apply --env dev
atlas migrate apply --env prod

よくあるエラーと対処法

  • Checksum mismatch:誰かがコミット済みのマイグレーションファイルを編集した。変更が意図的であることを確認してからatlas migrate hash --forceを実行してchecksumを再生成する。
  • docker://が動作しない:DockerがすでにAtlasのsocket権限を持って起動していることが必要。Dockerがない場合は実際のdev用データベースURLで代替する。
  • advisory lockでマイグレーションが止まる:前のプロセスが途中でkillされた場合、PostgreSQLでSELECT pg_advisory_unlock_all();を実行してロックを解放する。

まとめ

Atlasはデータベースマイグレーションの唯一のツールではない。しかしdrift検出、destructiveな変更のlint、CI/CD統合はFlywayやLiquibaseには実現できないことだ。PostgreSQLプロジェクトをAlembicからAtlasに移行した——マージ前にチーム全員でマイグレーションをレビューできるようになった今、戻る理由はまったくない。

新しいプロジェクトのセットアップをするなら、最初からAtlasを試してみよう——最初の30分のセットアップで、スキーマが複雑になっていく中で後々の多くの頭痛を防ぐことができる。

Share: