0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

dockerで再起動した際のマイグレーションエラーの解決方法

0
Posted at

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, 
      ...
)

エラーの根本原因

  1. マイグレーション履歴の不整合: Alembicの管理テーブル(alembic_version)とデータベースの実際の状態が合っていない
  2. 古いマイグレーションファイル: 古いスキーマ定義(passwordカラムあり)が残っている
  3. モデル更新後の競合: 現在のモデル(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(マイグレーションファイル修正)でデータを保持
  • 必ずバックアップを取ってから実行

重要なポイント:

  • マイグレーションファイルとモデル定義の整合性を保つ
  • 段階的な変更を心がける
  • チーム開発では変更内容を必ず共有

この記事が同様の問題に直面している方の助けになれば幸いです。

参考情報

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?