Skip to content

マイグレーション戦略

概要

本プロジェクトでは、Python の Alembic をマイグレーションツールとして採用し、 SQLAlchemy ORM と組み合わせてスキーマ管理を行う。


技術スタック

項目技術
データベースPostgreSQL 16+
ORMSQLAlchemy 2.x
マイグレーションAlembic 1.x
コンテナDocker Compose(ローカル開発)

ディレクトリ構成

backend/
├── alembic/
│   ├── alembic.ini
│   ├── env.py
│   ├── script.py.mako
│   └── versions/
│       ├── 001_initial_schema.py
│       ├── 002_add_xxx.py
│       └── ...
├── app/
│   ├── models/
│   │   ├── __init__.py
│   │   ├── project.py
│   │   ├── user.py
│   │   ├── issue.py
│   │   ├── assignment.py
│   │   ├── review.py
│   │   ├── reward.py
│   │   └── budget_snapshot.py
│   └── ...
└── ...

初期セットアップ手順

1. Alembic の初期化

bash
cd backend
alembic init alembic

2. alembic.ini の設定

ini
[alembic]
script_location = alembic
# データベースURLは環境変数から取得(env.pyで上書き)
sqlalchemy.url = postgresql://user:pass@localhost:5432/issue_outsource

3. env.py の設定

python
from logging.config import fileConfig
import os
from sqlalchemy import engine_from_config, pool
from alembic import context

# モデルの Base をインポート
from app.models import Base

config = context.config

# 環境変数からDB URLを取得
database_url = os.environ.get("DATABASE_URL")
if database_url:
    config.set_main_option("sqlalchemy.url", database_url)

if config.config_file_name is not None:
    fileConfig(config.config_file_name)

target_metadata = Base.metadata


def run_migrations_offline() -> None:
    url = config.get_main_option("sqlalchemy.url")
    context.configure(
        url=url,
        target_metadata=target_metadata,
        literal_binds=True,
        dialect_opts={"paramstyle": "named"},
    )
    with context.begin_transaction():
        context.run_migrations()


def run_migrations_online() -> None:
    connectable = engine_from_config(
        config.get_section(config.config_ini_section, {}),
        prefix="sqlalchemy.",
        poolclass=pool.NullPool,
    )
    with connectable.connect() as connection:
        context.configure(
            connection=connection,
            target_metadata=target_metadata,
        )
        with context.begin_transaction():
            context.run_migrations()


if context.is_offline_mode():
    run_migrations_offline()
else:
    run_migrations_online()

4. 初期マイグレーションの作成

bash
# 自動生成
alembic revision --autogenerate -m "initial schema"

# 適用
alembic upgrade head

初期スキーマ作成手順

ステップ1: ローカルDB起動

bash
docker compose up -d db

docker-compose.yml の DB 定義例:

yaml
services:
  db:
    image: postgres:16
    environment:
      POSTGRES_DB: issue_outsource
      POSTGRES_USER: app
      POSTGRES_PASSWORD: localdev
    ports:
      - "5432:5432"
    volumes:
      - postgres_data:/var/lib/postgresql/data

volumes:
  postgres_data:

ステップ2: SQLAlchemy モデル定義

テーブル定義書(schema.md)に基づき、各モデルクラスを実装する。

python
# app/models/base.py
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
from sqlalchemy import func
from datetime import datetime
import uuid


class Base(DeclarativeBase):
    pass


class TimestampMixin:
    created_at: Mapped[datetime] = mapped_column(
        default=func.now(), nullable=False
    )
    updated_at: Mapped[datetime] = mapped_column(
        default=func.now(), onupdate=func.now(), nullable=False
    )

ステップ3: マイグレーション生成と適用

bash
# マイグレーション生成
alembic revision --autogenerate -m "initial schema"

# マイグレーション内容を確認(必ず目視レビュー)
cat alembic/versions/xxxx_initial_schema.py

# 適用
alembic upgrade head

# 確認
alembic current

ステップ4: 初期データ投入(シードデータ)

開発用のシードデータを alembic/seed.py として準備し、開発環境でのみ実行する。

bash
python -m alembic.seed

スキーマ変更ポリシー

基本ルール

  1. 全てのスキーマ変更はAlembicマイグレーションで管理する

    • 手動でのDDL実行は禁止
    • alembic revision --autogenerate を起点とし、必ず手動レビューを行う
  2. マイグレーションファイルの命名規則

    • 自動生成のリビジョンIDに加え、説明的なメッセージを付与
    • 例: alembic revision --autogenerate -m "add_payment_method_to_rewards"
  3. 後方互換性の維持

    • カラム追加: nullable=True または server_default を指定
    • カラム削除: 2段階で行う(非推奨化 -> 次リリースで削除)
    • テーブル名変更: 禁止(新テーブル作成 + データ移行で対応)
  4. downgrade の実装

    • 全マイグレーションに downgrade() を実装する
    • データ損失を伴う downgrade は明示的にコメントで警告する

変更プロセス

1. ブランチ作成(feature/xxx)
2. SQLAlchemy モデルを修正
3. alembic revision --autogenerate -m "説明"
4. 生成されたマイグレーションファイルを目視レビュー
5. ローカルで alembic upgrade head を実行して検証
6. テストを実行
7. MR を作成してレビュー
8. マージ後、CI/CD パイプラインでステージング環境に適用
9. 問題なければ本番環境に適用

禁止事項

  • マイグレーションファイルの事後編集(適用済みファイルの改変)
  • alembic stamp による履歴の改ざん(障害対応時を除く)
  • 本番DBへの直接DDL実行

データマイグレーション

スキーマ変更に伴うデータ変換が必要な場合:

  1. スキーマ変更とデータ変換を 別のマイグレーションファイル に分離
  2. データマイグレーションにはバッチ処理を使用(大量データ対応)
  3. 実行前にバックアップを取得
python
# データマイグレーション例
def upgrade():
    # バッチ処理でデータ変換
    conn = op.get_bind()
    result = conn.execute(text("SELECT id FROM old_table"))
    for batch in chunked(result, 1000):
        # 変換処理
        pass

環境別マイグレーション運用

環境適用方法タイミング
ローカル開発alembic upgrade head 手動実行開発者が任意で実行
ステージングCI/CD パイプラインで自動適用マージ時
本番CI/CD パイプライン + 承認ゲートリリース時

CI/CD パイプラインでの実行

yaml
# .gitlab-ci.yml の例
migrate:
  stage: deploy
  script:
    - alembic upgrade head
  environment:
    name: staging
  only:
    - main

バックアップとリカバリ

マイグレーション前のバックアップ

本番環境でのマイグレーション実行前に、必ず pg_dump でバックアップを取得する。

bash
pg_dump -Fc issue_outsource > backup_$(date +%Y%m%d_%H%M%S).dump

ロールバック手順

bash
# 1つ前のリビジョンに戻す
alembic downgrade -1

# 特定のリビジョンに戻す
alembic downgrade <revision_id>

障害時の対応フロー

  1. マイグレーション失敗を検知
  2. alembic downgrade -1 でロールバック試行
  3. ロールバック不可の場合、バックアップからリストア
  4. 原因調査・修正後に再適用