Alembic 마이그레이션 전략 — 브랜치 관리, 데이터 마이그레이션, 롤백 설계

Alembic 초기 설정

$ alembic init alembic
# alembic/env.py 수정
from app.models.base import Base

target_metadata = Base.metadata

# DB URL을 환경변수에서 로드
config.set_main_option("sqlalchemy.url",
                        os.environ["DATABASE_URL"])

마이그레이션 파일 명명 규칙

# 날짜 + 설명으로 명명 (자동 생성 해시보다 가독성 높음)
$ alembic revision --autogenerate -m "add_family_group_table"
$ alembic revision --autogenerate -m "add_uuid_to_children"
$ alembic revision --autogenerate -m "add_soft_delete_columns"

# 생성된 파일: alembic/versions/
# 20260101_001_add_family_group_table.py
# 20260105_002_add_uuid_to_children.py

autogenerate가 놓치는 것들

# autogenerate가 감지하지 못하는 변경사항
# 1. 인덱스 이름 변경
# 2. 함수/트리거
# 3. 체크 제약조건 (DB에 따라 다름)
# 4. ENUM 타입 변경 (PostgreSQL)

# 수동으로 추가 필요
def upgrade():
    op.create_index(
        "ix_schedules_child_id_start_time",
        "schedules",
        ["child_id", "start_time"],
        unique=False
    )

def downgrade():
    op.drop_index("ix_schedules_child_id_start_time", "schedules")

데이터 마이그레이션 패턴

# 스키마 변경 + 데이터 변환을 한 마이그레이션에
def upgrade():
    # 1. 컬럼 추가 (nullable)
    op.add_column("users",
        sa.Column("family_group_id", sa.String(36), nullable=True))

    # 2. 기존 데이터 채우기
    conn = op.get_bind()
    users = conn.execute(sa.text("SELECT id FROM users WHERE family_group_id IS NULL")).fetchall()
    for user in users:
        group_id = str(uuid.uuid4())
        conn.execute(sa.text(
            "INSERT INTO family_groups (id, created_at) VALUES (:id, NOW())"
        ), {"id": group_id})
        conn.execute(sa.text(
            "UPDATE users SET family_group_id = :gid WHERE id = :uid"
        ), {"gid": group_id, "uid": user[0]})

    # 3. NOT NULL 제약 추가
    op.alter_column("users", "family_group_id", nullable=False)

def downgrade():
    op.drop_column("users", "family_group_id")
    # family_groups 데이터는 삭제하지 않음 — 안전한 downgrade

프로덕션 배포 시 마이그레이션

# Kubernetes Job으로 배포 전 마이그레이션 실행
# k8s/migrate-job.yaml
apiVersion: batch/v1
kind: Job
metadata:
  name: ivent-migrate
spec:
  template:
    spec:
      containers:
      - name: migrate
        image: ivent-backend:latest
        command: ["alembic", "upgrade", "head"]
        env:
        - name: DATABASE_URL
          valueFrom:
            secretKeyRef:
              name: ivent-secrets
              key: database-url
      restartPolicy: Never

브랜치 충돌 방지

# CI에서 마이그레이션 브랜치 검사
$ alembic heads
# 결과가 1개여야 함 — 2개면 merge 필요

# GitHub Actions
- name: Check alembic heads
  run: |
    heads=$(alembic heads | wc -l)
    if [ $heads -gt 1 ]; then
      echo "Multiple alembic heads detected!"
      exit 1
    fi

교훈

데이터 마이그레이션은 스키마 변경과 함께 하나의 트랜잭션에서 처리해야 합니다. alembic heads를 CI에서 검사하면 브랜치 충돌을 조기에 발견할 수 있습니다. downgrade는 최대한 안전하게 — 데이터 삭제를 피하고 컬럼 제거만 합니다.

글쓴이

admin

https://ivent-admin.storyqbe.top/ 를 운영하고 있는 강프로입니다.

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다