Alembicマイグレーション失敗時の対処法:「relation already exists」エラーの解決
はじめに
Docker環境でFastAPI + Alembicを使用している際に、コンテナ再起動後にマイグレーションが失敗する問題と、その完全な解決方法をまとめました。
特に、Firebase認証への移行に伴うデータベーススキーマ変更時に発生した実例を基に、実践的な解決方法を解説します。
発生したエラー
エラー内容
sqlalchemy.exc.ProgrammingError: (psycopg2.errors.DuplicateTable) relation "users" already exists
[SQL:
CREATE TABLE users (
id UUID NOT NULL,
email VARCHAR(255) NOT NULL,
password VARCHAR(255) NOT NULL, ← 古いスキーマ
role INTEGER NOT NULL,
...
)
エラーの根本原因
- マイグレーション履歴の不整合: Alembicの管理テーブル(alembic_version)とデータベースの実際の状態が合っていない
- 古いマイグレーションファイル: 古いスキーマ定義(passwordカラムあり)が残っている
- モデル更新後の競合: 現在のモデル(firebase_uidあり)と既存マイグレーションの不一致
環境構成
Docker構成
# docker-compose.yml
services:
web:
build: .
command: [
"/bin/sh",
"-c",
"alembic upgrade heads && uvicorn app.main:app --host 0.0.0.0 --port 80 --reload"
]
volumes:
- .:/backend # ホスト側ファイルがマウント
depends_on:
db:
condition: service_healthy
db:
image: postgres:16
environment:
POSTGRES_DB: baseball_note
POSTGRES_USER: docker
POSTGRES_PASSWORD: docker
healthcheck:
test: ["CMD-SHELL", "pg_isready -U docker -d baseball_note"]
モデル変更内容
# 変更前のモデル(base.py)
class Users(Base, ModelBaseMixin):
__tablename__ = "users"
id: Mapped[UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
email: Mapped[str] = mapped_column(String(255), nullable=False, unique=True)
password: Mapped[str] = mapped_column(String(255), nullable=False) # 削除対象
role: Mapped[int] = mapped_column(Integer, nullable=False, default=UserRole.PLAYER)
# 変更後のモデル(base.py)
class Users(Base, ModelBaseMixin):
__tablename__ = "users"
id: Mapped[UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
firebase_uid: Mapped[str] = mapped_column(String(128), nullable=False, unique=True) # 追加
email: Mapped[str] = mapped_column(String(255), nullable=False, unique=True)
role: Mapped[int] = mapped_column(Integer, nullable=False, default=UserRole.PLAYER)
解決方法
Method 1: 完全リセット(開発環境推奨)
最もクリーンで確実な方法です。開発環境でのみ使用してください。
# 1. 完全停止とボリューム削除
docker-compose down -v
# 2. 古いマイグレーションファイルを全削除
rm ./alembic/versions/*.py
# 3. コンテナを再起動
docker-compose up -d --build
# 4. 新しいマイグレーションを作成
docker exec -it コンテナ名 bash
alembic revision --autogenerate -m "create_initial_tables_with_firebase"
alembic upgrade head
Method 2: マイグレーションファイル修正(データ保持)
既存のデータを保持したい場合の方法です。
Step 1: 問題のマイグレーションファイルを特定
# マイグレーションファイルの確認
ls -la ./alembic/versions/
cat ./alembic/versions/20250220-1645_recreate_all_tables.py
Step 2: 最初のマイグレーションファイルを修正
修正前(問題のあるファイル):
"""recreate_all_tables
Revision ID: aa3febf53565
Revises:
Create Date: 2025-02-20 16:45:38.248220
"""
def upgrade():
op.create_table('users',
sa.Column('id', sa.UUID(), nullable=False),
sa.Column('email', sa.String(length=255), nullable=False),
sa.Column('password', sa.String(length=255), nullable=False), # 問題:古いスキーマ
sa.Column('role', sa.Integer(), nullable=False),
sa.Column('created_at', sa.TIMESTAMP(timezone=True), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=False),
sa.Column('updated_at', sa.TIMESTAMP(timezone=True), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=False),
sa.Column('deleted_at', sa.TIMESTAMP(timezone=True), nullable=True),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('email'),
sa.UniqueConstraint('id')
)
修正後:
"""recreate_all_tables
Revision ID: aa3febf53565
Revises:
Create Date: 2025-02-20 16:45:38.248220
"""
def upgrade():
op.create_table('users',
sa.Column('id', sa.UUID(), nullable=False),
sa.Column('email', sa.String(length=255), nullable=False),
sa.Column('firebase_uid', sa.String(length=128), nullable=False), # 修正:新しいスキーマ
sa.Column('role', sa.Integer(), nullable=False),
sa.Column('created_at', sa.TIMESTAMP(timezone=True), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=False),
sa.Column('updated_at', sa.TIMESTAMP(timezone=True), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=False),
sa.Column('deleted_at', sa.TIMESTAMP(timezone=True), nullable=True),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('email'),
sa.UniqueConstraint('firebase_uid'), # 追加:新しい制約
sa.UniqueConstraint('id')
)
Step 3: 重複マイグレーションファイルを削除
# 後続の重複するマイグレーションファイルを削除
rm ./alembic/versions/20250228-1039_add_img_path_to_profiles.py # profiles.image_path追加(統合済み)
rm ./alembic/versions/20250331-2233_add_firebase_uid_to_users.py # firebase_uid追加、password削除(統合済み)
rm ./alembic/versions/20250411-1135_add_user_id_to_trainings.py # trainings.user_id追加(統合済み)
# 残るのは修正した最初のファイルのみ
ls -la ./alembic/versions/
# 期待される出力:20250220-1645_recreate_all_tables.py のみ
Step 4: コンテナ再起動とマイグレーション実行
# 1. 完全停止
docker-compose down
# 2. リビルド付きで起動(マイグレーションファイルの変更を確実に反映)
docker-compose up -d --build
# 3. マイグレーション実行確認
docker exec -it ed_std_web bash
alembic current
alembic upgrade head
成功の確認方法
1. Alembicの状態確認
docker exec -it ed_std_web bash
alembic current
# 期待される出力: aa3febf53565 (head)
alembic history
# 期待される出力: マイグレーション履歴の一覧
2. データベース構造の確認
# データベースコンテナに入る
docker exec -it ed_std_db psql -U docker -d baseball_note
# usersテーブルの構造確認
\d users
# 全テーブルの一覧確認
\dt
# 終了
\q
期待されるusersテーブル構造:
Table "public.users"
Column | Type | Collation | Nullable | Default
-------------+--------------------------+-----------+----------+---------
id | uuid | | not null |
firebase_uid| character varying(128) | | not null |
email | character varying(255) | | not null |
role | integer | | not null |
created_at | timestamp with time zone | | not null | CURRENT_TIMESTAMP
updated_at | timestamp with time zone | | not null | CURRENT_TIMESTAMP
deleted_at | timestamp with time zone | | |
Indexes:
"users_pkey" PRIMARY KEY, btree (id)
"users_email_key" UNIQUE CONSTRAINT, btree (email)
"users_firebase_uid_key" UNIQUE CONSTRAINT, btree (firebase_uid)
3. APIの動作確認
# APIサーバーの起動確認
curl http://localhost:8080/
# 期待される出力例
# {"title": "FastAPI Application", "version": "1.0.0"}
よくある追加の問題と対処法
Seedファイルのエラー
マイグレーション成功後に以下のエラーが出ることがあります:
seeds import failed. detail=type object 'Users' has no attribute 'password'
原因: テストデータ(seedファイル)が古いpasswordフィールドを参照している
対処法: seeder/seeds_json/users.jsonファイルを更新
// 修正前
{
"email": "test@example.com",
"password": "hashedpassword",
"role": 0
}
// 修正後
{
"email": "test@example.com",
"firebase_uid": "firebase_test_uid_123",
"role": 0
}
test_dbのヘルスチェックエラー
# docker-compose.ymlのtest_db設定を修正
test_db:
healthcheck:
test: ["CMD-SHELL", "pg_isready -U test -d test"] # -d test を追加
予防策
1. 段階的なマイグレーション作成
大きなスキーマ変更は段階的に行う:
# 悪い例:一度に大きな変更
alembic revision --autogenerate -m "replace_password_with_firebase_uid"
# 良い例:段階的な変更
alembic revision --autogenerate -m "add_firebase_uid_column"
alembic revision --autogenerate -m "remove_password_column"
2. マイグレーション作成前の確認
# 現在の状態を確認
alembic current
alembic history
# モデルとマイグレーションの整合性確認
alembic check
3. 開発環境での安全な再起動
# データを保持してコンテナのみ再起動
docker-compose restart
# 完全リビルドが必要な場合
docker-compose down
docker-compose up -d --build # ボリュームは保持される
# データも完全にリセットする場合
docker-compose down -v
docker-compose up -d --build
4. チーム開発での注意点
- マイグレーション変更は必ずチーム全体で共有
- 本番環境では絶対にMethod 1(完全リセット)を使用しない
- 本番データがある場合は必ずバックアップを取る
- マイグレーションのテストは開発環境で十分に行う
まとめ
このエラーは Docker + Alembic の組み合わせでよく発生する問題ですが、適切な手順で確実に解決できます。
開発環境の場合:
- Method 1(完全リセット)が最も確実で簡単
本番環境の場合:
- Method 2(マイグレーションファイル修正)でデータを保持
- 必ずバックアップを取ってから実行
重要なポイント:
- マイグレーションファイルとモデル定義の整合性を保つ
- 段階的な変更を心がける
- チーム開発では変更内容を必ず共有
この記事が同様の問題に直面している方の助けになれば幸いです。